inline edit mode

This commit is contained in:
Primakov Alexandr Alexandrovich 2024-10-30 14:28:42 +03:00
parent 6b903b4d54
commit 3dfd854a4c
5 changed files with 327 additions and 183 deletions

View File

@ -1,3 +1,4 @@
/* eslint-disable react/display-name */
import React from 'react';
import ReactDOM from 'react-dom/client';

View File

@ -0,0 +1,144 @@
import React, { useEffect, useRef, useState } from 'react'
import dayjs from 'dayjs'
import { Link } from 'react-router-dom'
import { getNavigationsValue, getFeatures } from '@brojs/cli'
import {
Button,
Tr,
Td,
Menu,
MenuButton,
MenuItem,
MenuList,
useToast,
} from '@chakra-ui/react'
import { EditIcon } from '@chakra-ui/icons'
import { qrCode } from '../../../assets'
import { LessonForm } from './lessons-form'
import { api } from '../../../__data__/api/api'
const features = getFeatures('journal')
const groupByDate = features?.['group.by.date']
type ItemProps = {
id: string
date: string
name: string
isTeacher: boolean
courseId: string
setlessonToDelete(): void
students: unknown[]
}
export const Item: React.FC<ItemProps> = ({
id,
date,
name,
isTeacher,
courseId,
setlessonToDelete,
students,
}) => {
const [edit, setEdit] = useState(false)
const toastRef = useRef(null)
const toast = useToast()
const [updateLesson, updateLessonRqst] = api.useUpdateLessonMutation()
const createdLessonRef = useRef(null)
const onSubmit = (lessonData) => {
toastRef.current = toast({
title: 'Отправляем',
status: 'loading',
duration: 9000,
})
createdLessonRef.current = lessonData
if (navigator.onLine) {
updateLesson(lessonData)
} else {
toast.update(toastRef.current, {
title: 'Отсутствует интернет',
status: 'error',
duration: 3000
})
}
}
useEffect(() => {
if (updateLessonRqst.isSuccess) {
const toastProps = {
title: 'Лекция Обновлена',
description: `Лекция ${createdLessonRef.current?.name} успешно обновлена`,
status: 'success' as const,
duration: 9000,
isClosable: true,
}
if (toastRef.current) toast.update(toastRef.current, toastProps)
else toast(toastProps)
setEdit(false)
}
}, [updateLessonRqst.isSuccess])
if (edit && isTeacher) {
return (
<Tr>
<Td colSpan={5}>
<LessonForm
isLoading={updateLessonRqst.isLoading}
error={(updateLessonRqst.error as any)?.error}
onSubmit={onSubmit}
onCancel={() => {
setEdit(false)
}}
lesson={{ id, name, date }}
title={'Редактирование лекции'}
nameButton={'Сохранить'}
/>
</Td>
</Tr>
)
}
return (
<Tr>
{isTeacher && (
<Td>
<Link
to={`${getNavigationsValue('journal.main')}/lesson/${courseId}/${id}`}
style={{ display: 'flex' }}
>
<img width={24} src={qrCode} style={{ margin: '0 auto' }} />
</Link>
</Td>
)}
<Td textAlign="center">
{dayjs(date).format(groupByDate ? 'HH:mm' : 'HH:mm DD.MM.YY')}
</Td>
<Td>{name}</Td>
{isTeacher && (
<Td>
{!edit && (
<Menu>
<MenuButton as={Button}>
<EditIcon />
</MenuButton>
<MenuList>
<MenuItem
onClick={() => {
setEdit(true)
}}
>
Edit
</MenuItem>
<MenuItem onClick={setlessonToDelete}>Delete</MenuItem>
</MenuList>
</Menu>
)}
{edit && <Button onClick={setlessonToDelete}>Сохранить</Button>}
</Td>
)}
<Td isNumeric>{students.length}</Td>
</Tr>
)
}

View File

@ -0,0 +1,45 @@
import React from 'react'
import dayjs from 'dayjs'
import {
Tr,
Td,
} from '@chakra-ui/react'
import { Lesson } from '../../../__data__/model'
import { Item } from './item'
type LessonItemProps = {
date: string
lessons: Lesson[]
isTeacher: boolean
courseId: string
setlessonToDelete(lesson: Lesson): void
}
export const LessonItems: React.FC<LessonItemProps> = ({
date,
lessons,
isTeacher,
courseId,
setlessonToDelete,
}) => (
<>
{date && (
<Tr>
<Td colSpan={isTeacher ? 5 : 3}>
{dayjs(date).format('DD MMMM YYYY')}
</Td>
</Tr>
)}
{lessons.map((lesson) => (
<Item
key={lesson.id}
{...lesson}
setlessonToDelete={() => setlessonToDelete(lesson)}
courseId={courseId}
isTeacher={isTeacher}
/>
))}
</>
)

View File

@ -1,19 +1,19 @@
import React from 'react'
import { useForm, Controller } from 'react-hook-form'
import {
Box,
Card,
CardBody,
CardHeader,
Heading,
Button,
CloseButton,
VStack,
FormControl,
FormLabel,
FormHelperText,
FormErrorMessage,
Input,
Box,
Card,
CardBody,
CardHeader,
Heading,
Button,
CloseButton,
VStack,
FormControl,
FormLabel,
FormHelperText,
FormErrorMessage,
Input,
} from '@chakra-ui/react'
import { AddIcon } from '@chakra-ui/icons'
@ -22,116 +22,119 @@ import { Lesson } from '../../../__data__/model'
import { ErrorSpan } from '../style'
interface NewLessonForm {
name: string;
date: string;
name: string
date: string
}
interface LessonFormProps {
lesson?: Partial<Lesson>
isLoading: boolean
onCancel: () => void
onSubmit: (lesson: Lesson) => void
error?: string
title: string
nameButton: string
lesson?: Partial<Lesson>
isLoading: boolean
onCancel: () => void
onSubmit: (lesson: Lesson) => void
error?: string
title: string
nameButton: string
}
export const LessonForm = ({
lesson,
isLoading,
onCancel,
onSubmit,
error,
title,
nameButton,
lesson,
isLoading,
onCancel,
onSubmit,
error,
title,
nameButton,
}: LessonFormProps) => {
const {
control,
handleSubmit,
reset,
formState: { errors },
} = useForm<NewLessonForm>({
defaultValues: (lesson && { ...lesson, date: dateToCalendarFormat(lesson.date) }) || {
name: '',
date: dateToCalendarFormat(),
},
})
return (
<Card align="left">
<CardHeader display="flex">
const {
control,
handleSubmit,
reset,
formState: { errors },
} = useForm<NewLessonForm>({
defaultValues: (lesson && {
...lesson,
date: dateToCalendarFormat(lesson.date),
}) || {
name: '',
date: dateToCalendarFormat(),
},
})
return (
<Card align="left">
<CardHeader display="flex">
<Heading as="h2" mt="0">
{title}
{title}
</Heading>
<CloseButton
ml="auto"
onClick={() => {
ml="auto"
onClick={() => {
reset()
onCancel()
}}
}}
/>
</CardHeader>
<CardBody>
</CardHeader>
<CardBody>
<form onSubmit={handleSubmit(onSubmit)}>
<VStack spacing="10" align="left">
<Controller
control={control}
name="date"
rules={{ required: 'Обязательное поле' }}
render={({ field }) => (
<FormControl>
<FormLabel>Дата</FormLabel>
<Input
{...field}
required={false}
placeholder="Укажите дату лекции"
size="md"
type="datetime-local"
<VStack spacing="10" align="left">
<Controller
control={control}
name="date"
rules={{ required: 'Обязательное поле' }}
render={({ field }) => (
<FormControl>
<FormLabel>Дата</FormLabel>
<Input
{...field}
required={false}
placeholder="Укажите дату лекции"
size="md"
type="datetime-local"
/>
{errors.date ? (
<FormErrorMessage>{errors.date?.message}</FormErrorMessage>
) : (
<FormHelperText>Укажите дату и время лекции</FormHelperText>
)}
</FormControl>
)}
/>
{errors.date ? (
<FormErrorMessage>{errors.date?.message}</FormErrorMessage>
) : (
<FormHelperText>Укажите дату и время лекции</FormHelperText>
)}
</FormControl>
)}
/>
<Controller
control={control}
name="name"
rules={{ required: 'Обязательное поле' }}
render={({ field }) => (
<FormControl isRequired isInvalid={Boolean(errors.name)}>
<FormLabel>Название новой лекции:</FormLabel>
<Input
{...field}
required={false}
placeholder="Название лекции"
size="md"
<Controller
control={control}
name="name"
rules={{ required: 'Обязательное поле' }}
render={({ field }) => (
<FormControl isRequired isInvalid={Boolean(errors.name)}>
<FormLabel>Название новой лекции:</FormLabel>
<Input
{...field}
required={false}
placeholder="Название лекции"
size="md"
/>
{errors.name && (
<FormErrorMessage>{errors.name.message}</FormErrorMessage>
)}
</FormControl>
)}
/>
{errors.name && (
<FormErrorMessage>{errors.name.message}</FormErrorMessage>
)}
</FormControl>
)}
/>
<Box mt="10">
<Button
size="lg"
type="submit"
leftIcon={<AddIcon />}
colorScheme="blue"
isLoading={isLoading}
>
{nameButton}
</Button>
</Box>
</VStack>
{error && <ErrorSpan>{error}</ErrorSpan>}
<Box mt="10">
<Button
size="lg"
type="submit"
leftIcon={<AddIcon />}
colorScheme="blue"
isLoading={isLoading}
>
{nameButton}
</Button>
</Box>
</VStack>
{error && <ErrorSpan>{error}</ErrorSpan>}
</form>
</CardBody>
</Card>
)
</CardBody>
</Card>
)
}

View File

@ -1,9 +1,4 @@
import React, {
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import dayjs from 'dayjs'
import { Link, useParams } from 'react-router-dom'
import { getNavigationsValue, getFeatures } from '@brojs/cli'
@ -22,12 +17,7 @@ import {
Tr,
Th,
Tbody,
Td,
Menu,
MenuButton,
MenuItem,
Text,
MenuList,
AlertDialog,
AlertDialogBody,
AlertDialogContent,
@ -35,18 +25,18 @@ import {
AlertDialogHeader,
AlertDialogOverlay,
} from '@chakra-ui/react'
import { AddIcon, EditIcon } from '@chakra-ui/icons'
import { AddIcon } from '@chakra-ui/icons'
import { useAppSelector } from '../../__data__/store'
import { api } from '../../__data__/api/api'
import { isTeacher } from '../../utils/user'
import { qrCode } from '../../assets'
import { Lesson } from '../../__data__/model'
import { XlSpinner } from '../../components/xl-spinner'
import { LessonForm } from './components/lessons-form'
import { BreadcrumbsWrapper } from './style'
import { Bar } from './components/bar'
import { LessonItems } from './components/lesson-items'
import { BreadcrumbsWrapper } from './style'
const features = getFeatures('journal')
@ -67,7 +57,10 @@ const LessonList = () => {
const toastRef = useRef(null)
const createdLessonRef = useRef(null)
const [editLesson, setEditLesson] = useState<Lesson>(null)
const sorted = useMemo(() => [...(data?.body || [])]?.sort((a, b) => a.date > b.date ? 1 : -1), [data, data?.body])
const sorted = useMemo(
() => [...(data?.body || [])]?.sort((a, b) => (a.date > b.date ? 1 : -1)),
[data, data?.body],
)
const lessonCalc = useMemo(() => {
if (!isSuccess) {
@ -95,7 +88,7 @@ const LessonList = () => {
}
}
return lessonsData.sort((a, b) => a.date < b.date? 1 : -1)
return lessonsData.sort((a, b) => (a.date < b.date ? 1 : -1))
}, [groupByDate, isSuccess, sorted])
const onSubmit = (lessonData) => {
@ -153,8 +146,8 @@ const LessonList = () => {
if (crLQuery.isSuccess) {
const toastProps = {
title: 'Лекция создана',
description: `Лекция ${createdLessonRef.current.name} успешно создана`,
status: 'success' as 'success',
description: `Лекция ${createdLessonRef.current?.name} успешно создана`,
status: 'success' as const,
duration: 9000,
isClosable: true,
}
@ -168,8 +161,8 @@ const LessonList = () => {
if (updateLessonRqst.isSuccess) {
const toastProps = {
title: 'Лекция Обновлена',
description: `Лекция ${createdLessonRef.current.name} успешно обновлена`,
status: 'success' as 'success',
description: `Лекция ${createdLessonRef.current?.name} успешно обновлена`,
status: 'success' as const,
duration: 9000,
isClosable: true,
}
@ -180,7 +173,7 @@ const LessonList = () => {
}, [updateLessonRqst.isSuccess])
if (isLoading) {
return <XlSpinner />;
return <XlSpinner />
}
return (
@ -213,7 +206,7 @@ const LessonList = () => {
colorScheme="red"
loadingText=""
isLoading={deletingRqst.isLoading}
onClick={() => deleteLesson(lessonToDelete._id)}
onClick={() => deleteLesson(lessonToDelete.id)}
ml={3}
>
Delete
@ -240,7 +233,7 @@ const LessonList = () => {
<Box mt="15" mb="15">
{showForm ? (
<LessonForm
key={editLesson?._id}
key={editLesson?.id}
isLoading={crLQuery.isLoading}
onSubmit={onSubmit}
onCancel={() => {
@ -265,7 +258,7 @@ const LessonList = () => {
)}
</Box>
)}
{barFeature && sorted?.length && (
{barFeature && sorted?.length > 1 && (
<Box height="300">
<Bar
data={sorted.map((lesson, index) => ({
@ -285,7 +278,7 @@ const LessonList = () => {
</Th>
)}
<Th textAlign="center" width={1}>
Дата
{groupByDate ? 'Время' : 'Дата'}
</Th>
<Th width="100%">Название</Th>
{isTeacher(user) && <Th>action</Th>}
@ -294,56 +287,14 @@ const LessonList = () => {
</Thead>
<Tbody>
{lessonCalc?.map(({ data: lessons, date }) => (
<React.Fragment key={date}>
{date && <Tr><Td colSpan={isTeacher(user) ? 5 : 3}>{dayjs(date).format('DD MMMM YYYY')}</Td></Tr>}
{lessons.map((lesson) => (
<Tr key={lesson._id}>
{isTeacher(user) && (
<Td>
<Link
to={`${getNavigationsValue('journal.main')}/lesson/${courseId}/${lesson._id}`}
style={{ display: 'flex' }}
>
<img
width={24}
src={qrCode}
style={{ margin: '0 auto' }}
/>
</Link>
</Td>
)}
<Td textAlign="center">
{dayjs(lesson.date).format(groupByDate ? 'HH:mm' : 'HH:mm DD.MM.YY')}
</Td>
<Td>{lesson.name}</Td>
{isTeacher(user) && (
<Td>
<Menu>
<MenuButton as={Button}>
<EditIcon />
</MenuButton>
<MenuList>
<MenuItem
onClick={() => {
setShowForm(true)
setEditLesson(lesson)
}}
>
Edit
</MenuItem>
<MenuItem
onClick={() => setlessonToDelete(lesson)}
>
Delete
</MenuItem>
</MenuList>
</Menu>
</Td>
)}
<Td isNumeric>{lesson.students.length}</Td>
</Tr>
))}
</React.Fragment>
<LessonItems
courseId={courseId}
date={date}
isTeacher={isTeacher(user)}
lessons={lessons}
setlessonToDelete={setlessonToDelete}
key={date}
/>
))}
</Tbody>
</Table>