diff --git a/src/advanced-filtering/README.md b/src/advanced-filtering/README.md index 4420dc3..bb41b13 100644 --- a/src/advanced-filtering/README.md +++ b/src/advanced-filtering/README.md @@ -37,6 +37,6 @@ npm run build ## 🌟 Example solution -As this is an advanced challenge, there is no single correct solution. However, you can find an example solution in the `advanced-filtering-solution` branch. +As this is an advanced challenge, there is no single correct solution. However, you can find an example solution in the [Pull Request: Example solution for Advanced Filtering Challenge #30](https://github.com/przeprogramowani/typescript-challenges/pull/30). Before checking the solution, try to solve the challenge on your own! Use sorting-related code as a reference and try to apply similar typing techniques to filtering-related code. diff --git a/src/advanced-filtering/src/App.tsx b/src/advanced-filtering/src/App.tsx index 74c67d1..28c18a4 100644 --- a/src/advanced-filtering/src/App.tsx +++ b/src/advanced-filtering/src/App.tsx @@ -1,50 +1,50 @@ -import {useMemo, useState} from "react"; -import {LogFilter} from "./components/LogFilter"; -import {LogLevelBadge} from "./components/LogLevelBadge"; -import {LogSortingHeader} from "./components/LogSortingHeader"; +import { useMemo, useState } from 'react'; +import { LogFilter } from './components/LogFilter'; +import { LogLevelBadge } from './components/LogLevelBadge'; +import { LogSortingHeader } from './components/LogSortingHeader'; import { LogEntryFields, LogLevelMap, LogLevelOrder, LogSourceMap, -} from "./constants"; +} from './constants'; import { + advancedFilter, advancedSort, NO_FILTER, - simpleLogFilter, -} from "./services/logProcessing"; +} from './services/logProcessing'; import { + FilterCriteria, LogEntry, LogLevel, - SimpleLogFilterCriteria, SortCriteria, UpdateLogFilterFunction, -} from "./types"; +} from './types'; interface LogDashboardProps { logs: LogEntry[]; } -function LogDashboard({logs}: LogDashboardProps) { +function LogDashboard({ logs }: LogDashboardProps) { const [sortCriteria, setSortCriteria] = useState>({ - timestamp: {direction: "desc"}, + timestamp: { direction: 'desc' }, }); - const [filterCriteria, setFilterCriteria] = useState( - {} - ); + const [filterCriteria, setFilterCriteria] = useState< + FilterCriteria + >({}); const compareLogLevels = (a: LogLevel, b: LogLevel) => LogLevelOrder[a] - LogLevelOrder[b]; const toggleSort = (key: keyof LogEntry) => { setSortCriteria((prev) => { - const direction = prev[key]?.direction === "asc" ? "desc" : "asc"; - const baseCriteria = {direction}; + const direction = prev[key]?.direction === 'asc' ? 'desc' : 'asc'; + const baseCriteria = { direction }; return { [key]: - key === "level" - ? {...baseCriteria, customCompare: compareLogLevels} + key === 'level' + ? { ...baseCriteria, customCompare: compareLogLevels } : baseCriteria, }; }); @@ -53,36 +53,36 @@ function LogDashboard({logs}: LogDashboardProps) { const updateFilter: UpdateLogFilterFunction = (key, value) => { setFilterCriteria((prev) => ({ ...prev, - [key]: value === "" ? NO_FILTER : value, + [key]: value === '' ? NO_FILTER : value, })); }; const filteredAndSortedLogs = useMemo(() => { - const filteredLogs = simpleLogFilter(logs, filterCriteria); + const filteredLogs = advancedFilter(logs, filterCriteria); return advancedSort(filteredLogs, sortCriteria); }, [logs, filterCriteria, sortCriteria]); return ( -
-
+
+
-
- - +
+
+ - {LogEntryFields.map(({key, label, className}) => ( + {LogEntryFields.map(({ key, label, className }) => ( {filteredAndSortedLogs.map((log, index) => ( - - + - - - - + + + ))} diff --git a/src/advanced-filtering/src/components/LogFilter.tsx b/src/advanced-filtering/src/components/LogFilter.tsx index 09454a1..4b48fb9 100644 --- a/src/advanced-filtering/src/components/LogFilter.tsx +++ b/src/advanced-filtering/src/components/LogFilter.tsx @@ -1,25 +1,25 @@ import { - SimpleFilterableLogValue, - SimpleLogFilterKey, + FilterableLogValue, + LogFilterKey, UpdateLogFilterFunction, -} from "../types"; +} from '../types'; const SelectArrow = () => ( -
+
- +
); interface LogFilterProps { updateFilter: UpdateLogFilterFunction; - filterKey: SimpleLogFilterKey; - options: readonly SimpleFilterableLogValue[]; + filterKey: LogFilterKey; + options: readonly FilterableLogValue[]; allLabel: string; } @@ -29,14 +29,14 @@ export const LogFilter = ({ options, allLabel, }: LogFilterProps) => ( -
+
+
{log.timestamp.toISOString().slice(0, 19)} + {log.source}{log.message}{log.userId}{log.source}{log.message}{log.userId}
= { ERROR: 3, @@ -11,23 +11,23 @@ export const LogEntryFields: { label: string; className?: string; }[] = [ - {key: "timestamp", label: "Timestamp"}, - {key: "level", label: "Level"}, - {key: "source", label: "Source", className: "min-w-44"}, - {key: "message", label: "Message", className: "min-w-80"}, - {key: "userId", label: "User ID", className: "min-w-24"}, + { key: 'timestamp', label: 'Timestamp' }, + { key: 'level', label: 'Level' }, + { key: 'source', label: 'Source', className: 'min-w-44' }, + { key: 'message', label: 'Message', className: 'min-w-80' }, + { key: 'userId', label: 'User ID', className: 'min-w-24' }, ]; -export const LogLevelMap: {[Key in LogLevel]: Key} = { - INFO: "INFO", - WARNING: "WARNING", - ERROR: "ERROR", +export const LogLevelMap: { [Key in LogLevel]: Key } = { + INFO: 'INFO', + WARNING: 'WARNING', + ERROR: 'ERROR', }; -export const LogSourceMap: {[Key in LogSource]: Key} = { - "user-service": "user-service", - "auth-service": "auth-service", - "payment-service": "payment-service", - "notification-service": "notification-service", - "api-gateway": "api-gateway", +export const LogSourceMap: { [Key in LogSource]: Key } = { + 'user-service': 'user-service', + 'auth-service': 'auth-service', + 'payment-service': 'payment-service', + 'notification-service': 'notification-service', + 'api-gateway': 'api-gateway', }; diff --git a/src/advanced-filtering/src/data/logs.ts b/src/advanced-filtering/src/data/logs.ts index 149029e..2b6adf3 100644 --- a/src/advanced-filtering/src/data/logs.ts +++ b/src/advanced-filtering/src/data/logs.ts @@ -1,71 +1,71 @@ -import {LogEntry} from "../types"; +import { LogEntry } from '../types'; const fakeLogEntries: LogEntry[] = [ { - timestamp: new Date("2024-08-20T08:30:15Z"), - level: "INFO", - message: "User successfully logged in", - source: "auth-service", + timestamp: new Date('2024-08-20T08:30:15Z'), + level: 'INFO', + message: 'User successfully logged in', + source: 'auth-service', userId: 1001, }, { - timestamp: new Date("2024-08-20T08:31:22Z"), - level: "ERROR", - message: "Failed to process payment: Invalid card number", - source: "payment-service", + timestamp: new Date('2024-08-20T08:31:22Z'), + level: 'ERROR', + message: 'Failed to process payment: Invalid card number', + source: 'payment-service', userId: 1002, }, { - timestamp: new Date("2024-08-20T08:32:45Z"), - level: "WARNING", - message: "High CPU usage detected", - source: "api-gateway", + timestamp: new Date('2024-08-20T08:32:45Z'), + level: 'WARNING', + message: 'High CPU usage detected', + source: 'api-gateway', }, { - timestamp: new Date("2024-08-20T08:33:10Z"), - level: "INFO", - message: "New user registered", - source: "user-service", + timestamp: new Date('2024-08-20T08:33:10Z'), + level: 'INFO', + message: 'New user registered', + source: 'user-service', userId: 1003, }, { - timestamp: new Date("2024-08-20T08:34:30Z"), - level: "ERROR", - message: "Database connection lost", - source: "user-service", + timestamp: new Date('2024-08-20T08:34:30Z'), + level: 'ERROR', + message: 'Database connection lost', + source: 'user-service', }, { - timestamp: new Date("2024-08-20T08:35:12Z"), - level: "INFO", - message: "Password reset email sent", - source: "notification-service", + timestamp: new Date('2024-08-20T08:35:12Z'), + level: 'INFO', + message: 'Password reset email sent', + source: 'notification-service', userId: 1004, }, { - timestamp: new Date("2024-08-20T08:36:05Z"), - level: "WARNING", - message: "Rate limit exceeded for API key", - source: "api-gateway", + timestamp: new Date('2024-08-20T08:36:05Z'), + level: 'WARNING', + message: 'Rate limit exceeded for API key', + source: 'api-gateway', }, { - timestamp: new Date("2024-08-20T08:37:20Z"), - level: "INFO", - message: "Successful payment processed", - source: "payment-service", + timestamp: new Date('2024-08-20T08:37:20Z'), + level: 'INFO', + message: 'Successful payment processed', + source: 'payment-service', userId: 1005, }, { - timestamp: new Date("2024-08-20T08:38:40Z"), - level: "ERROR", - message: "Failed to send notification: User not found", - source: "notification-service", + timestamp: new Date('2024-08-20T08:38:40Z'), + level: 'ERROR', + message: 'Failed to send notification: User not found', + source: 'notification-service', userId: 1006, }, { - timestamp: new Date("2024-08-20T08:39:55Z"), - level: "INFO", - message: "User profile updated", - source: "user-service", + timestamp: new Date('2024-08-20T08:39:55Z'), + level: 'INFO', + message: 'User profile updated', + source: 'user-service', userId: 1007, }, ]; diff --git a/src/advanced-filtering/src/main.tsx b/src/advanced-filtering/src/main.tsx index 29659d1..1c2c89b 100644 --- a/src/advanced-filtering/src/main.tsx +++ b/src/advanced-filtering/src/main.tsx @@ -1,11 +1,11 @@ -import {StrictMode} from "react"; -import {createRoot} from "react-dom/client"; -import App from "./App.tsx"; -import fakeLogEntries from "./data/logs.ts"; -import "./index.css"; +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.tsx'; +import fakeLogEntries from './data/logs.ts'; +import './index.css'; -createRoot(document.getElementById("root")!).render( +createRoot(document.getElementById('root')!).render( - + , ); diff --git a/src/advanced-filtering/src/services/logProcessing.ts b/src/advanced-filtering/src/services/logProcessing.ts index 533cee9..cb344dc 100644 --- a/src/advanced-filtering/src/services/logProcessing.ts +++ b/src/advanced-filtering/src/services/logProcessing.ts @@ -1,12 +1,12 @@ -import {LogEntry, SimpleLogFilterCriteria, SortCriteria} from "../types"; +import { FilterCriteria, SortCriteria } from '../types'; -export function advancedSort( +export function advancedSort( arr: ItemType[], - criteria: SortCriteria + criteria: SortCriteria, ): ItemType[] { return [...arr].sort((a, b) => { for (const key in criteria) { - const {direction, customCompare} = criteria[key] || {}; + const { direction, customCompare } = criteria[key] || {}; if (a[key] !== b[key]) { let comparison: number; if (customCompare) { @@ -14,7 +14,7 @@ export function advancedSort( } else { comparison = a[key] < b[key] ? -1 : 1; } - return direction === "desc" ? -comparison : comparison; + return direction === 'desc' ? -comparison : comparison; } } return 0; @@ -23,20 +23,23 @@ export function advancedSort( export const NO_FILTER = null; -export function simpleLogFilter( - logs: any[], - criteria: SimpleLogFilterCriteria -): any[] { - return logs.filter((log) => { +export function advancedFilter( + arr: ItemType[], + criteria: FilterCriteria, +): ItemType[] { + return arr.filter((log) => { return ( - Object.entries(criteria) as [keyof LogEntry, SimpleLogFilterCriteria][] + Object.entries(criteria) as [ + keyof ItemType, + FilterCriteria[keyof ItemType], + ][] ).every(([key, condition]) => { if (condition === NO_FILTER) { return true; } const value = log[key]; - if (typeof condition === "function") { - return (condition as (value: any) => boolean)(value); + if (typeof condition === 'function') { + return condition(value); } return value === condition; }); diff --git a/src/advanced-filtering/src/types/index.ts b/src/advanced-filtering/src/types/index.ts index 2e92d7a..416c492 100644 --- a/src/advanced-filtering/src/types/index.ts +++ b/src/advanced-filtering/src/types/index.ts @@ -1,10 +1,10 @@ -export type LogLevel = "INFO" | "WARNING" | "ERROR"; +export type LogLevel = 'INFO' | 'WARNING' | 'ERROR'; export type LogSource = - | "user-service" - | "auth-service" - | "payment-service" - | "notification-service" - | "api-gateway"; + | 'user-service' + | 'auth-service' + | 'payment-service' + | 'notification-service' + | 'api-gateway'; export interface LogEntry { timestamp: Date; @@ -16,26 +16,25 @@ export interface LogEntry { export type CompareFunction = ( a: ItemTypeValue, - b: ItemTypeValue + b: ItemTypeValue, ) => number; -export type SortCriteria = { +export type SortCriteria = { [Key in keyof ItemType]?: { - direction: "asc" | "desc"; + direction: 'asc' | 'desc'; customCompare?: CompareFunction; }; }; -export type SimpleLogFilterCriteria = { - level?: LogLevel | ((value: LogLevel) => boolean); - source?: LogSource | ((value: LogSource) => boolean); +export type FilterCriteria = { + [Key in keyof ItemType]?: ItemType[Key] | ((value: ItemType[Key]) => boolean); }; -export type SimpleLogFilterKey = "level" | "source"; +export type LogFilterKey = Extract; -export type SimpleFilterableLogValue = LogLevel | LogSource; +export type FilterableLogValue = LogEntry[LogFilterKey]; export type UpdateLogFilterFunction = ( - key: SimpleLogFilterKey, - value: SimpleFilterableLogValue | "" + key: LogFilterKey, + value: FilterableLogValue | '', ) => void; diff --git a/src/advanced-filtering/vite.config.ts b/src/advanced-filtering/vite.config.ts index 5a33944..627a319 100644 --- a/src/advanced-filtering/vite.config.ts +++ b/src/advanced-filtering/vite.config.ts @@ -1,7 +1,7 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], -}) +});