3 - file final

This commit is contained in:
safronman
2024-06-15 12:15:56 +03:00
parent 86c56f2f03
commit edb8cc43ae
6 changed files with 111 additions and 20 deletions

View File

@@ -1,6 +1,7 @@
import { ChangeEvent, useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
import { ControlledCheckbox, ControlledTextField, Dialog, DialogProps } from '@/components'
import { Button, ControlledCheckbox, ControlledTextField, Dialog, DialogProps } from '@/components'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
@@ -13,33 +14,83 @@ const newDeckSchema = z.object({
type FormValues = z.infer<typeof newDeckSchema>
type Props = Pick<DialogProps, 'onCancel' | 'onOpenChange' | 'open'> & {
defaultValues?: FormValues
onConfirm: (data: FormValues) => void
}
type Props = {
defaultValues?: { cover?: null | string } & FormValues
onConfirm: (data: { cover?: File | null } & FormValues) => void
} & Pick<DialogProps, 'onCancel' | 'onOpenChange' | 'open'>
export const DeckDialog = ({
defaultValues = { isPrivate: false, name: '' },
onCancel,
onConfirm,
...dialogProps
}: Props) => {
const [cover, setCover] = useState<File | null>(null)
const [preview, setPreview] = useState<null | string>('')
useEffect(() => {
if (defaultValues?.cover) {
setPreview(defaultValues?.cover)
}
}, [defaultValues?.cover])
useEffect(() => {
if (cover) {
// создать ссылку на файл
const newPreview = URL.createObjectURL(cover)
// зачищаем старое превью чтобы не хранилось в памяти
if (preview) {
URL.revokeObjectURL(preview)
}
setPreview(newPreview)
// зачищаем новое превью чтобы не хранилось в памяти
return () => URL.revokeObjectURL(newPreview)
}
}, [cover])
const { control, handleSubmit, reset } = useForm<FormValues>({
defaultValues,
resolver: zodResolver(newDeckSchema),
})
const onSubmit = handleSubmit(data => {
onConfirm(data)
onConfirm({ ...data, cover })
dialogProps.onOpenChange?.(false)
reset()
})
const handleCancel = () => {
reset()
onCancel?.()
}
const uploadHandler = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length) {
const file = e.target.files[0]
setCover(file)
}
}
const removeCoverHandler = () => {
setCover(null)
setPreview(null)
}
return (
<Dialog {...dialogProps} onCancel={handleCancel} onConfirm={onSubmit} title={'Create new deck'}>
<form className={s.content} onSubmit={onSubmit}>
{preview && <img alt={'cover'} src={preview} width={'50px'} />}
<input accept={'image/*'} onChange={uploadHandler} type={'file'} />
{preview && (
<Button onClick={removeCoverHandler} type={'button'}>
Remove cover
</Button>
)}
<ControlledTextField control={control} label={'Deck name'} name={'name'} />
<ControlledCheckbox
control={control}

View File

@@ -1,6 +1,7 @@
import { Link } from 'react-router-dom'
import { Edit2Outline, PlayCircleOutline, TrashOutline } from '@/assets'
import Logo from '@/assets/icons/person-outline'
import {
Button,
Column,
@@ -18,6 +19,10 @@ import { formatDate } from '@/utils'
import s from './decks-table.module.scss'
const columns: Column[] = [
{
key: 'cover',
title: 'Cover',
},
{
key: 'name',
title: 'Name',
@@ -66,6 +71,10 @@ export const DecksTable = ({
<TableBody>
{decks?.map(deck => (
<TableRow key={deck.id}>
<TableCell>
{deck.cover && <img alt={'cover'} src={deck.cover} width={'30px'} />}
{!deck.cover && <Logo />}
</TableCell>
<TableCell>
<Typography as={Link} to={`/decks/${deck.id}`} variant={'body2'}>
{deck.name}

View File

@@ -47,6 +47,7 @@ export const DecksPage = () => {
const { currentData: decksCurrentData, data: decksData } = useGetDecksQuery({
authorId,
currentPage,
itemsPerPage: 3,
maxCardsCount,
minCardsCount,
name: search,

View File

@@ -14,11 +14,26 @@ const decksService = flashcardsApi.injectEndpoints({
endpoints: builder => ({
createDeck: builder.mutation<DeckResponse, CreateDeckArgs>({
invalidatesTags: ['Decks'],
query: body => ({
body,
method: 'POST',
url: `v1/decks`,
}),
query: body => {
const { cover, isPrivate, name } = body
const formData = new FormData()
formData.append('name', name)
if (isPrivate) {
formData.append('isPrivate', isPrivate.toString())
}
if (cover) {
formData.append('cover', cover)
}
return {
body: formData,
method: 'POST',
url: `v1/decks`,
}
},
}),
deleteDeck: builder.mutation<void, { id: string }>({
invalidatesTags: ['Decks'],
@@ -44,11 +59,28 @@ const decksService = flashcardsApi.injectEndpoints({
}),
updateDeck: builder.mutation<DeckResponse, UpdateDeckArgs>({
invalidatesTags: ['Decks'],
query: ({ id, ...body }) => ({
body,
method: 'PATCH',
url: `v1/decks/${id}`,
}),
query: ({ cover, id, isPrivate, name }) => {
const formData = new FormData()
if (name) {
formData.append('name', name)
}
if (isPrivate) {
formData.append('isPrivate', isPrivate.toString())
}
if (cover) {
formData.append('cover', cover)
} else if (cover === null) {
// небходимо для зануления картинки
formData.append('cover', '')
}
return {
body: formData,
method: 'PATCH',
url: `v1/decks/${id}`,
}
},
}),
}),
})

View File

@@ -64,11 +64,11 @@ export type GetDecksArgs = {
}
export type CreateDeckArgs = {
cover?: string
cover?: File | null
isPrivate?: boolean
name: string
}
export type UpdateDeckArgs = Partial<CreateDeckArgs> & { id: Deck['id'] }
export type UpdateDeckArgs = { id: Deck['id'] } & Partial<CreateDeckArgs>
export type Tab = 'all' | 'my'

View File

@@ -55,8 +55,6 @@ export const baseQueryWithReauth: BaseQueryFn<
extraOptions
)) as any
console.log('refreshResult', refreshResult)
if (refreshResult.data) {
localStorage.setItem('accessToken', refreshResult.data.accessToken)
localStorage.setItem('refreshToken', refreshResult.data.refreshToken)