mirror of
https://github.com/ershisan99/flashcards-example-project.git
synced 2025-12-16 20:59:27 +00:00
add update/delete/edit/add deck functionality
This commit is contained in:
@@ -29,6 +29,7 @@ export const DeckDialog = ({
|
|||||||
})
|
})
|
||||||
const onSubmit = handleSubmit(data => {
|
const onSubmit = handleSubmit(data => {
|
||||||
onConfirm(data)
|
onConfirm(data)
|
||||||
|
dialogProps.onOpenChange?.(false)
|
||||||
reset()
|
reset()
|
||||||
})
|
})
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
|
|||||||
5
src/components/decks/decks-table.module.scss
Normal file
5
src/components/decks/decks-table.module.scss
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.iconsContainer {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
import s from './decks-table.module.scss'
|
||||||
|
|
||||||
|
import { Edit2Outline, PlayCircleOutline, TrashOutline } from '@/assets'
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
Column,
|
Column,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -11,7 +15,6 @@ import {
|
|||||||
} from '@/components'
|
} from '@/components'
|
||||||
import { Deck } from '@/services/decks'
|
import { Deck } from '@/services/decks'
|
||||||
import { formatDate } from '@/utils'
|
import { formatDate } from '@/utils'
|
||||||
|
|
||||||
const columns: Column[] = [
|
const columns: Column[] = [
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
@@ -37,8 +40,14 @@ const columns: Column[] = [
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
decks: Deck[] | undefined
|
decks: Deck[] | undefined
|
||||||
|
onDeleteClick: (id: string) => void
|
||||||
|
currentUserId: string
|
||||||
|
onEditClick: (id: string) => void
|
||||||
}
|
}
|
||||||
export const DecksTable = ({ decks }: Props) => {
|
export const DecksTable = ({ decks, onEditClick, onDeleteClick, currentUserId }: Props) => {
|
||||||
|
const handleEditClick = (id: string) => () => onEditClick(id)
|
||||||
|
const handleDeleteClick = (id: string) => () => onDeleteClick(id)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader columns={columns} />
|
<TableHeader columns={columns} />
|
||||||
@@ -53,7 +62,23 @@ export const DecksTable = ({ decks }: Props) => {
|
|||||||
<TableCell>{deck.cardsCount}</TableCell>
|
<TableCell>{deck.cardsCount}</TableCell>
|
||||||
<TableCell>{formatDate(deck.updated)}</TableCell>
|
<TableCell>{formatDate(deck.updated)}</TableCell>
|
||||||
<TableCell>{deck.author.name}</TableCell>
|
<TableCell>{deck.author.name}</TableCell>
|
||||||
<TableCell>...</TableCell>
|
<TableCell>
|
||||||
|
<div className={s.iconsContainer}>
|
||||||
|
<Button variant={'icon'} as={Link} to={`/decks/${deck.id}/learn`}>
|
||||||
|
<PlayCircleOutline />
|
||||||
|
</Button>
|
||||||
|
{deck.author.id === currentUserId && (
|
||||||
|
<>
|
||||||
|
<Button variant={'icon'} onClick={handleEditClick(deck.id)}>
|
||||||
|
<Edit2Outline />
|
||||||
|
</Button>
|
||||||
|
<Button variant={'icon'} onClick={handleDeleteClick(deck.id)}>
|
||||||
|
<TrashOutline />
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|||||||
@@ -3,10 +3,18 @@ import { useState } from 'react'
|
|||||||
import s from './decks-page.module.scss'
|
import s from './decks-page.module.scss'
|
||||||
|
|
||||||
import { Button, Page, Slider, TextField, Typography } from '@/components'
|
import { Button, Page, Slider, TextField, Typography } from '@/components'
|
||||||
|
import { DeckDialog } from '@/components/decks/deck-dialog'
|
||||||
import { DecksTable } from '@/components/decks/decks-table'
|
import { DecksTable } from '@/components/decks/decks-table'
|
||||||
|
import { DeleteDeckDialog } from '@/components/decks/delete-deck-dialog'
|
||||||
import { Pagination } from '@/components/ui/pagination'
|
import { Pagination } from '@/components/ui/pagination'
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import { Tab, useGetDecksQuery } from '@/services/decks'
|
import {
|
||||||
|
Tab,
|
||||||
|
useCreateDeckMutation,
|
||||||
|
useDeleteDeckMutation,
|
||||||
|
useGetDecksQuery,
|
||||||
|
useUpdateDeckMutation,
|
||||||
|
} from '@/services/decks'
|
||||||
import {
|
import {
|
||||||
selectDecksCurrentPage,
|
selectDecksCurrentPage,
|
||||||
selectDecksCurrentTab,
|
selectDecksCurrentTab,
|
||||||
@@ -15,9 +23,15 @@ import {
|
|||||||
selectDecksSearch,
|
selectDecksSearch,
|
||||||
} from '@/services/decks/decks.selectors.ts'
|
} from '@/services/decks/decks.selectors.ts'
|
||||||
import { decksSlice } from '@/services/decks/decks.slice.ts'
|
import { decksSlice } from '@/services/decks/decks.slice.ts'
|
||||||
import { useAppDispatch, useAppSelector } from '@/services/store.ts'
|
import { useAppDispatch, useAppSelector } from '@/services/store'
|
||||||
|
|
||||||
export const DecksPage = () => {
|
export const DecksPage = () => {
|
||||||
|
const [showCreateModal, setShowCreateModal] = useState(false)
|
||||||
|
const [deckToDeleteId, setDeckToDeleteId] = useState<string | null>(null)
|
||||||
|
const [deckToEditId, setDeckToEditId] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const showEditModal = !!deckToEditId
|
||||||
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const currentPage = useAppSelector(selectDecksCurrentPage)
|
const currentPage = useAppSelector(selectDecksCurrentPage)
|
||||||
const minCards = useAppSelector(selectDecksMinCards)
|
const minCards = useAppSelector(selectDecksMinCards)
|
||||||
@@ -30,14 +44,19 @@ export const DecksPage = () => {
|
|||||||
const setSearch = (search: string) => dispatch(decksSlice.actions.setSearch(search))
|
const setSearch = (search: string) => dispatch(decksSlice.actions.setSearch(search))
|
||||||
const setCurrentTab = (tab: Tab) => dispatch(decksSlice.actions.setCurrentTab(tab))
|
const setCurrentTab = (tab: Tab) => dispatch(decksSlice.actions.setCurrentTab(tab))
|
||||||
|
|
||||||
|
const resetFilters = () => {
|
||||||
|
dispatch(decksSlice.actions.resetFilters())
|
||||||
|
setRangeValue([0, decks?.maxCardsCount || undefined])
|
||||||
|
}
|
||||||
|
|
||||||
const [rangeValue, setRangeValue] = useState([minCards, maxCards])
|
const [rangeValue, setRangeValue] = useState([minCards, maxCards])
|
||||||
|
|
||||||
const handleSliderCommitted = (value: number[]) => {
|
const handleSliderCommitted = (value: number[]) => {
|
||||||
setMinCards(value[0])
|
setMinCards(value[0])
|
||||||
setMaxCards(value[1])
|
setMaxCards(value[1])
|
||||||
}
|
}
|
||||||
|
const currentUserId = 'f2be95b9-4d07-4751-a775-bd612fc9553a'
|
||||||
const authorId = currentTab === 'my' ? 'f2be95b9-4d07-4751-a775-bd612fc9553a' : undefined
|
const authorId = currentTab === 'my' ? currentUserId : undefined
|
||||||
|
|
||||||
const { data: decks } = useGetDecksQuery({
|
const { data: decks } = useGetDecksQuery({
|
||||||
currentPage,
|
currentPage,
|
||||||
@@ -47,14 +66,51 @@ export const DecksPage = () => {
|
|||||||
authorId,
|
authorId,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const showConfirmDeleteModal = !!deckToDeleteId
|
||||||
|
const deckToDeleteName = decks?.items?.find(deck => deck.id === deckToDeleteId)?.name
|
||||||
|
|
||||||
|
const deckToEdit = decks?.items?.find(deck => deck.id === deckToEditId)
|
||||||
|
|
||||||
|
const [createDeck] = useCreateDeckMutation()
|
||||||
|
const [deleteDeck] = useDeleteDeckMutation()
|
||||||
|
const [updateDeck] = useUpdateDeckMutation()
|
||||||
|
const openCreateModal = () => setShowCreateModal(true)
|
||||||
|
|
||||||
if (!decks) return <div>loading...</div>
|
if (!decks) return <div>loading...</div>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
<DeleteDeckDialog
|
||||||
|
open={showConfirmDeleteModal}
|
||||||
|
onOpenChange={() => setDeckToDeleteId(null)}
|
||||||
|
deckName={deckToDeleteName ?? 'Selected deck'}
|
||||||
|
onCancel={() => setDeckToDeleteId(null)}
|
||||||
|
onConfirm={() => {
|
||||||
|
deleteDeck({ id: deckToDeleteId ?? '' })
|
||||||
|
setDeckToDeleteId(null)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<DeckDialog
|
||||||
|
open={showEditModal}
|
||||||
|
onOpenChange={() => setDeckToEditId(null)}
|
||||||
|
onConfirm={data => {
|
||||||
|
if (!deckToEditId) return
|
||||||
|
|
||||||
|
updateDeck({ id: deckToEditId, ...data })
|
||||||
|
}}
|
||||||
|
defaultValues={deckToEdit}
|
||||||
|
key={deckToEditId}
|
||||||
|
/>
|
||||||
<div className={s.root}>
|
<div className={s.root}>
|
||||||
<div className={s.header}>
|
<div className={s.header}>
|
||||||
<Typography variant="large">Decks</Typography>
|
<Typography variant="large">Decks</Typography>
|
||||||
<Button>Add new deck</Button>
|
<Button onClick={openCreateModal}>Add new deck</Button>
|
||||||
|
<DeckDialog
|
||||||
|
open={showCreateModal}
|
||||||
|
onOpenChange={setShowCreateModal}
|
||||||
|
onConfirm={createDeck}
|
||||||
|
onCancel={() => setShowCreateModal(false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={s.filters}>
|
<div className={s.filters}>
|
||||||
<TextField placeholder="Search" search value={search} onValueChange={setSearch} />
|
<TextField placeholder="Search" search value={search} onValueChange={setSearch} />
|
||||||
@@ -71,9 +127,16 @@ export const DecksPage = () => {
|
|||||||
min={0}
|
min={0}
|
||||||
max={decks?.maxCardsCount || 0}
|
max={decks?.maxCardsCount || 0}
|
||||||
/>
|
/>
|
||||||
<Button variant={'secondary'}>Clear filters</Button>
|
<Button variant={'secondary'} onClick={resetFilters}>
|
||||||
|
Clear filters
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<DecksTable decks={decks?.items} />
|
<DecksTable
|
||||||
|
decks={decks?.items}
|
||||||
|
onDeleteClick={setDeckToDeleteId}
|
||||||
|
onEditClick={setDeckToEditId}
|
||||||
|
currentUserId={currentUserId}
|
||||||
|
/>
|
||||||
<Pagination
|
<Pagination
|
||||||
count={decks?.pagination?.totalPages || 1}
|
count={decks?.pagination?.totalPages || 1}
|
||||||
page={currentPage}
|
page={currentPage}
|
||||||
|
|||||||
@@ -63,4 +63,12 @@ export type GetDecksArgs = {
|
|||||||
itemsPerPage?: number
|
itemsPerPage?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CreateDeckArgs = {
|
||||||
|
name: string
|
||||||
|
isPrivate?: boolean
|
||||||
|
cover?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateDeckArgs = Partial<CreateDeckArgs> & { id: Deck['id'] }
|
||||||
|
|
||||||
export type Tab = 'all' | 'my'
|
export type Tab = 'all' | 'my'
|
||||||
|
|||||||
Reference in New Issue
Block a user