diff --git a/src/lib/components/ContestTable.svelte b/src/lib/components/ContestTable.svelte index e06c386..70c5919 100644 --- a/src/lib/components/ContestTable.svelte +++ b/src/lib/components/ContestTable.svelte @@ -4,6 +4,12 @@ import { formatDuration } from '$lib/services/contest'; import { user } from '$lib/services/auth'; import { createEventDispatcher } from 'svelte'; import RecommendersFilter from './RecommendersFilter.svelte'; +import BaseTable from './tables/BaseTable.svelte'; +import StatusFilter from './tables/StatusFilter.svelte'; +import SourceFilter from './tables/SourceFilter.svelte'; +import SortableHeader from './tables/SortableHeader.svelte'; +import StatusButton from './tables/StatusButton.svelte'; +import FeedbackButtons from './tables/FeedbackButtons.svelte'; // Event dispatcher const dispatch = createEventDispatcher(); @@ -26,7 +32,7 @@ $: isAuthenticated = !!$user; // Filter states let difficultyFilter: number | null = null; let difficultySortDirection: 'asc' | 'desc' | null = null; -let participatedFilterState: 'participated' | 'not-participated' | 'all' = 'all'; +let participatedFilterState: 'all' | 'active' | 'inactive' = 'all'; let authorFilter: string | null = null; let typeFilterState: 'all' | 'icpc' | 'codeforces' = 'all'; @@ -37,89 +43,52 @@ $: uniqueAuthors = ? [...allAuthors].sort() : [...new Set(contests.map((contest) => contest.addedBy))].sort(); -// Apply filters to contests -$: filteredContests = contests.filter((contest) => { - // Filter by difficulty - if (difficultyFilter !== null && contest.difficulty !== difficultyFilter) { - return false; - } - - // Filter by participation status - if ( - participatedFilterState === 'participated' && - contest.id && - !userParticipation.has(contest.id) - ) { - return false; - } - if ( - participatedFilterState === 'not-participated' && - contest.id && - userParticipation.has(contest.id) - ) { - return false; +// Source filter options +const typeOptions = [ + { + value: 'icpc', + icon: `ICPC`, + color: 'rgb(34 197 94)' + }, + { + value: 'codeforces', + icon: `Codeforces`, + color: 'rgb(239 68 68)' } +]; - // Filter by author - if (authorFilter && contest.addedBy !== authorFilter) { - return false; - } - - // Filter by contest type - if (typeFilterState === 'icpc' && contest.type !== 'ICPC') { - return false; - } - if (typeFilterState === 'codeforces' && contest.type === 'ICPC') { - return false; - } - - return true; -}); - -// Handle filter changes -function handleParticipatedFilter() { - if (participatedFilterState === 'all') { - participatedFilterState = 'participated'; - } else if (participatedFilterState === 'participated') { - participatedFilterState = 'not-participated'; - } else { - participatedFilterState = 'all'; - } - - // Dispatch event to parent component - dispatch('filterParticipated', { state: participatedFilterState }); +// Handle events +function handleDifficultySort(event: CustomEvent<{ direction: 'asc' | 'desc' | null }>) { + difficultySortDirection = event.detail.direction; + dispatch('sortDifficulty', { direction: difficultySortDirection }); } -// Handle contest type filter -function handleTypeFilter() { - if (typeFilterState === 'all') { - typeFilterState = 'icpc'; - } else if (typeFilterState === 'icpc') { - typeFilterState = 'codeforces'; - } else { - typeFilterState = 'all'; - } +function handleParticipatedFilter(event: CustomEvent<{ state: 'all' | 'active' | 'inactive' }>) { + const stateMap: Record = { + all: 'all', + active: 'participated', + inactive: 'not-participated' + }; + participatedFilterState = event.detail.state; + dispatch('filterParticipated', { state: stateMap[participatedFilterState] }); +} - // Dispatch event to parent component +function handleTypeFilter(event: CustomEvent<{ source: string | null }>) { + typeFilterState = event.detail.source + ? (event.detail.source as 'all' | 'icpc' | 'codeforces') + : 'all'; dispatch('filterType', { type: typeFilterState }); } -// Handle difficulty sort -function handleDifficultySort() { - // Toggle sort direction: null -> asc -> desc -> null - if (difficultySortDirection === null) { - difficultySortDirection = 'asc'; - } else if (difficultySortDirection === 'asc') { - difficultySortDirection = 'desc'; - } else { - difficultySortDirection = null; - } - - // Dispatch event to parent component - dispatch('sortDifficulty', { direction: difficultySortDirection }); +function handleFeedback(event: CustomEvent<{ itemId: string; isLike: boolean }>) { + const { itemId, isLike } = event.detail; + onLike(itemId, isLike); } -// Function to handle author filter is now handled directly in the RecommendersFilter component +function handleStatusToggle(event: CustomEvent<{ itemId: string; isActive: boolean }>) { + const { itemId, isActive } = event.detail; + onToggleParticipation(itemId, isActive); +} // Generate star rating display function getDifficultyStars(difficulty: number | undefined): string { @@ -145,345 +114,236 @@ function getDifficultyColorClass(difficulty: number | undefined): string { return colors[Math.min(difficulty, 5) - 1]; } - -
-
- - - - - - - - - - - - - - {#each filteredContests as contest} - - - - - - - - - - {/each} - -
-
- {#if participatedFilterState === 'participated'} - - - - - - {:else if participatedFilterState === 'not-participated'} - - - - - - - {:else} - - - - - - {/if} -
-
-
- {#if typeFilterState === 'icpc'} - -
- ICPC -
-
-
- {:else if typeFilterState === 'codeforces'} - -
- Codeforces -
-
-
- {:else} - - - - - - {/if} -
-
- Contest - - Duration - -
- {#if difficultySortDirection === 'asc'} - - {:else if difficultySortDirection === 'desc'} - - {:else} - - - - - {/if} - Difficulty -
-
-
- { - authorFilter = author; - dispatch('filterAuthor', { author: authorFilter }); - }} - /> -
-
- {#if contest.id} - {@const hasParticipated = userParticipation.has(contest.id)} - - {/if} - - - {#if contest.type === 'ICPC'} - ICPC - {:else} - Codeforces - {/if} - - - - {contest.name} - - - - {formatDuration(contest.durationSeconds)} - - - {#if contest.difficulty !== undefined} - - {getDifficultyStars(contest.difficulty)} - - {:else} - - - {/if} - - - @{contest.addedBy} - - -
- {#if contest.id} - {@const hasLiked = userFeedback[contest.id] === 'like'} - {@const hasDisliked = userFeedback[contest.id] === 'dislike'} +// Apply filters to contests +$: filteredContests = contests.filter((contest) => { + // Filter by difficulty + if (difficultyFilter !== null && contest.difficulty !== difficultyFilter) { + return false; + } - - + // Filter by participation status + const stateMap: Record = { + all: 'all', + active: 'participated', + inactive: 'not-participated' + }; + const mappedState = stateMap[participatedFilterState]; - - - {/if} -
-
-
-
+ if (mappedState === 'participated' && contest.id && !userParticipation.has(contest.id)) { + return false; + } + if (mappedState === 'not-participated' && contest.id && userParticipation.has(contest.id)) { + return false; + } - diff --git a/src/lib/components/tables/BaseTable.svelte b/src/lib/components/tables/BaseTable.svelte new file mode 100644 index 0000000..835aa67 --- /dev/null +++ b/src/lib/components/tables/BaseTable.svelte @@ -0,0 +1,102 @@ + + +
+
+ + + + + + + + + + + +
+
+
+ + diff --git a/src/lib/components/tables/FeedbackButtons.svelte b/src/lib/components/tables/FeedbackButtons.svelte new file mode 100644 index 0000000..cf793a9 --- /dev/null +++ b/src/lib/components/tables/FeedbackButtons.svelte @@ -0,0 +1,83 @@ + + +
+ + + +
diff --git a/src/lib/components/tables/SortableHeader.svelte b/src/lib/components/tables/SortableHeader.svelte new file mode 100644 index 0000000..31b41fb --- /dev/null +++ b/src/lib/components/tables/SortableHeader.svelte @@ -0,0 +1,55 @@ + + + diff --git a/src/lib/components/tables/SourceFilter.svelte b/src/lib/components/tables/SourceFilter.svelte new file mode 100644 index 0000000..48ed214 --- /dev/null +++ b/src/lib/components/tables/SourceFilter.svelte @@ -0,0 +1,82 @@ + + + diff --git a/src/lib/components/tables/StatusButton.svelte b/src/lib/components/tables/StatusButton.svelte new file mode 100644 index 0000000..d4c6a1a --- /dev/null +++ b/src/lib/components/tables/StatusButton.svelte @@ -0,0 +1,48 @@ + + + diff --git a/src/lib/components/tables/StatusFilter.svelte b/src/lib/components/tables/StatusFilter.svelte new file mode 100644 index 0000000..8894b87 --- /dev/null +++ b/src/lib/components/tables/StatusFilter.svelte @@ -0,0 +1,60 @@ + + +