timetable-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit b17b0efb37d9ed9de4ece0eb1d587998474b5c12
parent ef2d224a55f1078b793a0e28315fd7c07249f0fe
Author: Yuukin256 <Yuukin256@gmail.com>
Date:   Wed,  9 Nov 2022 23:14:23 +0900

曜日時限時間割表登録ページを実装

Diffstat:
Mpackage.json | 1+
Msrc/api/timetables/_grade@number/_date@string.ts | 16++++++++--------
Msrc/constant/index.tsx | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Asrc/hooks/useRegisterDayTimetable.ts | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/hooks/useRegisterStandardTimetable.ts | 12+++++++-----
Msrc/pages/_app.tsx | 4+++-
Asrc/pages/console/index.tsx | 34++++++++++++++++++++++++++++++++++
Msrc/pages/console/standard.tsx | 41+++++++++++++++++++++++++++++++++--------
Asrc/pages/console/timetable.tsx | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/types/index.ts | 7+++----
Myarn.lock | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 606 insertions(+), 28 deletions(-)

diff --git a/package.json b/package.json @@ -24,6 +24,7 @@ "@emotion/styled": "^11.10.5", "@js-temporal/polyfill": "^0.4.1", "@mui/icons-material": "^5.10.9", + "@mui/lab": "^5.0.0-alpha.107", "@mui/material": "^5.10.12", "@mui/x-date-pickers": "^5.0.7", "@prisma/client": "^4.5.0", diff --git a/src/api/timetables/_grade@number/_date@string.ts b/src/api/timetables/_grade@number/_date@string.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; -import { ABSTRACT_LESSON_NAMES } from 'constant'; +import { ABSTRACT_LESSON_NAMES_EN } from 'constant'; import type { DayTimetableForApi } from '..'; import type { DayTimetable } from '@prisma/client'; @@ -32,12 +32,12 @@ export const getApiRequestSchema = z.object({ export const putApiRequestSchema = z.object({ query: querySchema, body: z.object<ToZod<PutApiRequestbody>>({ - period1: z.enum(ABSTRACT_LESSON_NAMES).nullable(), - period2: z.enum(ABSTRACT_LESSON_NAMES).nullable(), - period3: z.enum(ABSTRACT_LESSON_NAMES).nullable(), - period4: z.enum(ABSTRACT_LESSON_NAMES).nullable(), - period5: z.enum(ABSTRACT_LESSON_NAMES).nullable(), - period6: z.enum(ABSTRACT_LESSON_NAMES).nullable(), - period7: z.enum(ABSTRACT_LESSON_NAMES).nullable(), + period1: z.enum(ABSTRACT_LESSON_NAMES_EN).nullable(), + period2: z.enum(ABSTRACT_LESSON_NAMES_EN).nullable(), + period3: z.enum(ABSTRACT_LESSON_NAMES_EN).nullable(), + period4: z.enum(ABSTRACT_LESSON_NAMES_EN).nullable(), + period5: z.enum(ABSTRACT_LESSON_NAMES_EN).nullable(), + period6: z.enum(ABSTRACT_LESSON_NAMES_EN).nullable(), + period7: z.enum(ABSTRACT_LESSON_NAMES_EN).nullable(), }), }); diff --git a/src/constant/index.tsx b/src/constant/index.tsx @@ -37,7 +37,7 @@ export const CLASSES = [ '3-8', ] as const; -export const ABSTRACT_LESSON_NAMES = [ +export const ABSTRACT_LESSON_NAMES_EN = [ 'Mon1', 'Mon2', 'Mon3', @@ -74,7 +74,44 @@ export const ABSTRACT_LESSON_NAMES = [ 'Fri7', ] as const; -export const ABSTRACT_LESSON_NAME_MAP: Readonly<Record<AbstractLessonNameEn, AbstractLessonNameJa>> = { +export const ABSTRACT_LESSON_NAMES_JA = [ + '月1', + '月2', + '月3', + '月4', + '月5', + '月6', + '月7', + '火1', + '火2', + '火3', + '火4', + '火5', + '火6', + '火7', + '水1', + '水2', + '水3', + '水4', + '水5', + '水6', + '水7', + '木1', + '木2', + '木3', + '木4', + '木5', + '木6', + '金1', + '金2', + '金3', + '金4', + '金5', + '金6', + '金7', +] as const; + +export const ABSTRACT_LESSON_NAME_MAP_EN_TO_JA: Readonly<Record<AbstractLessonNameEn, AbstractLessonNameJa>> = { Mon1: '月1', Mon2: '月2', Mon3: '月3', @@ -111,6 +148,43 @@ export const ABSTRACT_LESSON_NAME_MAP: Readonly<Record<AbstractLessonNameEn, Abs Fri7: '金7', }; +export const ABSTRACT_LESSON_NAME_MAP_JA_TO_EN: Readonly<Record<AbstractLessonNameJa, AbstractLessonNameEn>> = { + 月1: 'Mon1', + 月2: 'Mon2', + 月3: 'Mon3', + 月4: 'Mon4', + 月5: 'Mon5', + 月6: 'Mon6', + 月7: 'Mon7', + 火1: 'Tue1', + 火2: 'Tue2', + 火3: 'Tue3', + 火4: 'Tue4', + 火5: 'Tue5', + 火6: 'Tue6', + 火7: 'Tue7', + 水1: 'Wed1', + 水2: 'Wed2', + 水3: 'Wed3', + 水4: 'Wed4', + 水5: 'Wed5', + 水6: 'Wed6', + 水7: 'Wed7', + 木1: 'Thu1', + 木2: 'Thu2', + 木3: 'Thu3', + 木4: 'Thu4', + 木5: 'Thu5', + 木6: 'Thu6', + 金1: 'Fri1', + 金2: 'Fri2', + 金3: 'Fri3', + 金4: 'Fri4', + 金5: 'Fri5', + 金6: 'Fri6', + 金7: 'Fri7', +}; + export const DATE_FORMAT = 'yyyy-MM-dd'; export const BUG_REPORT_FORM_URL = process.env['NEXT_PUBLIC_BUG_REPORT_FORM_URL']; diff --git a/src/hooks/useRegisterDayTimetable.ts b/src/hooks/useRegisterDayTimetable.ts @@ -0,0 +1,154 @@ +import { useAspidaQuery } from '@aspida/react-query'; +import { Temporal } from '@js-temporal/polyfill'; +import { useSnackbar } from 'notistack'; +import { useCallback, useState } from 'react'; +import { useMutation, useQueryClient } from 'react-query'; +import { useMap } from 'react-use'; + +import { apiClient } from 'lib/apiClient'; + +import type { AbstractLessonName, DayTimetable } from '@prisma/client'; +import type { Dispatch, SetStateAction } from 'react'; + +export type DayTimetableWithoutGradeAndDate = Omit<DayTimetable, 'grade' | 'date'>; + +type Result = { + loading: boolean; + sending: boolean; + grades: number[]; + grade: number | undefined; + setGrade: (val: number) => void; + date: Temporal.PlainDate; + setDate: Dispatch<SetStateAction<Temporal.PlainDate>>; + timetable: DayTimetableWithoutGradeAndDate; + setTimetable: (key: keyof DayTimetableWithoutGradeAndDate, value: AbstractLessonName | null) => void; + setUsualTimetable: () => void; + submit: () => void; +}; + +const emptyTimetable: DayTimetableWithoutGradeAndDate = { + period1: null, + period2: null, + period3: null, + period4: null, + period5: null, + period6: null, + period7: null, +}; + +const useRegisterDayTimetable = (): Result => { + const queryClient = useQueryClient(); + const { enqueueSnackbar } = useSnackbar(); + + const grades = [1, 2, 3]; + const [grade, setGrade] = useState<number>(); + + const [date, setDate] = useState<Temporal.PlainDate>(Temporal.Now.plainDateISO()); + + const [timetable, { set: setTimetable, setAll }] = useMap<DayTimetableWithoutGradeAndDate>(emptyTimetable); + + const dayTimetableQuery = useAspidaQuery(apiClient.timetables._grade(grade ?? -1)._date(date.toString()), { + enabled: !!grade, + onSuccess: (data) => { + if (data) { + const { grade, date, ...timetable } = data; + setAll(timetable); + } else { + setAll(emptyTimetable); + } + }, + onError: () => enqueueSnackbar('エラーが発生しました', { variant: 'error', autoHideDuration: 6000 }), + }); + + const dayTimetableMutation = useMutation( + ({ grade, date, body }: { grade: number; date: Temporal.PlainDate; body: DayTimetableWithoutGradeAndDate }) => { + return apiClient.timetables._grade(grade)._date(date.toJSON()).$put({ body }); + }, + { + onSuccess: (_, { grade, date }) => { + enqueueSnackbar('正常に保存されました', { variant: 'success', autoHideDuration: 2000 }); + queryClient.invalidateQueries(apiClient.timetables._grade(grade)._date(date.toJSON()).$path()); + }, + onError: () => enqueueSnackbar('エラーが発生しました', { variant: 'error', autoHideDuration: 6000 }), + } + ); + + const submit = () => { + if (grade) { + dayTimetableMutation.mutate({ grade, date, body: timetable }); + } + }; + + const setUsualTimetable = useCallback(() => { + switch (date.dayOfWeek) { + case 1: // 月曜日 + return setAll({ + period1: 'Mon1', + period2: 'Mon2', + period3: 'Mon3', + period4: 'Mon4', + period5: 'Mon5', + period6: 'Mon6', + period7: 'Mon7', + }); + case 2: // 火曜日 + return setAll({ + period1: 'Tue1', + period2: 'Tue2', + period3: 'Tue3', + period4: 'Tue4', + period5: 'Tue5', + period6: 'Tue6', + period7: 'Tue7', + }); + case 3: // 水曜日 + return setAll({ + period1: 'Wed1', + period2: 'Wed2', + period3: 'Wed3', + period4: 'Wed4', + period5: 'Wed5', + period6: 'Wed6', + period7: 'Wed7', + }); + case 4: // 木曜日 + return setAll({ + period1: 'Thu1', + period2: 'Thu2', + period3: 'Thu3', + period4: 'Thu4', + period5: 'Thu5', + period6: 'Thu6', + period7: null, + }); + case 5: // 金曜日 + return setAll({ + period1: 'Fri1', + period2: 'Fri2', + period3: 'Fri3', + period4: 'Fri4', + period5: 'Fri5', + period6: 'Fri6', + period7: 'Fri7', + }); + default: + return setAll(emptyTimetable); + } + }, [date.dayOfWeek, setAll]); + + return { + loading: dayTimetableQuery.isLoading, + sending: dayTimetableMutation.isLoading, + grades, + grade, + setGrade, + date, + setDate, + timetable, + setTimetable, + setUsualTimetable, + submit, + }; +}; + +export default useRegisterDayTimetable; diff --git a/src/hooks/useRegisterStandardTimetable.ts b/src/hooks/useRegisterStandardTimetable.ts @@ -11,6 +11,7 @@ import type { AbstractLessonNameEn, StandardEn } from 'types'; type Result = { loading: boolean; + sending: boolean; classes: SchoolClass[]; classId: number | undefined; setClassId: (val: number) => void; @@ -64,13 +65,13 @@ const useRegisterStandardTimetable = (): Result => { const [standard, { set: setStandard, setAll }] = useMap(emptyStandard); const classesQuery = useAspidaQuery(apiClient.classes, { - onError: () => enqueueSnackbar('エラーが発生しました', { variant: 'error' }), + onError: () => enqueueSnackbar('エラーが発生しました', { variant: 'error', autoHideDuration: 6000 }), }); const standardQuery = useAspidaQuery(apiClient.classes._classId(classId ?? -1).standard, { enabled: !!classId, onSuccess: (data) => data && setAll(data), - onError: () => enqueueSnackbar('エラーが発生しました', { variant: 'error' }), + onError: () => enqueueSnackbar('エラーが発生しました', { variant: 'error', autoHideDuration: 6000 }), }); const standardMutation = useMutation( @@ -79,11 +80,11 @@ const useRegisterStandardTimetable = (): Result => { }, { onSuccess: (_, { classId }) => { - enqueueSnackbar('正常に保存されました', { variant: 'success' }); + enqueueSnackbar('正常に保存されました', { variant: 'success', autoHideDuration: 2000 }); queryClient.invalidateQueries(apiClient.classes._classId(classId).standard.$path()); }, onError: () => { - enqueueSnackbar('エラーが発生しました', { variant: 'error' }); + enqueueSnackbar('エラーが発生しました', { variant: 'error', autoHideDuration: 6000 }); }, } ); @@ -95,7 +96,8 @@ const useRegisterStandardTimetable = (): Result => { }; return { - loading: classesQuery.isLoading || standardQuery.isLoading || standardMutation.isLoading, + loading: classesQuery.isLoading || standardQuery.isLoading, + sending: standardMutation.isLoading, classes: classesQuery.data ?? [], classId, setClassId, diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx @@ -23,7 +23,9 @@ const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWind function App(props: MyAppProps) { const { Component, emotionCache = clientSideEmotionCache, pageProps } = props; - const theme = createTheme(); + const theme = createTheme({ + palette: { secondary: { main: '#e91e63', light: '#ff6090', dark: '#c2185b', contrastText: '#fff' } }, + }); return ( <SnackbarProvider maxSnack={3}> diff --git a/src/pages/console/index.tsx b/src/pages/console/index.tsx @@ -0,0 +1,34 @@ +import NavigateNextIcon from '@mui/icons-material/NavigateNext'; +import Box from '@mui/material/Box'; +import Breadcrumbs from '@mui/material/Breadcrumbs'; +import Container from '@mui/material/Container'; +import Typography from '@mui/material/Typography'; + +import Link from 'components/common/Link'; + +import type { NextPage } from 'next'; + +const Page: NextPage = () => { + return ( + <Container maxWidth='lg' component={Box} py={2}> + <Breadcrumbs separator={<NavigateNextIcon fontSize='small' />} aria-label='breadcrumb'> + <Link underline='hover' color='inherit' href='/'> + トップ + </Link> + <Typography color='text.primary' variant='h2' fontSize={32} fontWeight={400}> + 管理 + </Typography> + </Breadcrumbs> + <ul> + <li> + <Link href={{ pathname: '/console/standard' }}>クラス別時間割表</Link> + </li> + <li> + <Link href={{ pathname: '/console/timetable' }}>学年別曜日時限時間割表</Link> + </li> + </ul> + </Container> + ); +}; + +export default Page; diff --git a/src/pages/console/standard.tsx b/src/pages/console/standard.tsx @@ -1,7 +1,9 @@ +import NavigateNextIcon from '@mui/icons-material/NavigateNext'; import SaveIcon from '@mui/icons-material/Save'; +import LoadingButton from '@mui/lab/LoadingButton'; import Backdrop from '@mui/material/Backdrop'; import Box from '@mui/material/Box'; -import Button from '@mui/material/Button'; +import Breadcrumbs from '@mui/material/Breadcrumbs'; import CircularProgress from '@mui/material/CircularProgress'; import Container from '@mui/material/Container'; import FormControl from '@mui/material/FormControl'; @@ -18,6 +20,7 @@ import TableRow from '@mui/material/TableRow'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; +import Link from 'components/common/Link'; import useRegisterStandardTimetable from 'hooks/useRegisterStandardTimetable'; import type { NextPage } from 'next'; @@ -139,16 +142,27 @@ const StandardTimetableForm: FC<{ }; const Page: NextPage = () => { - const { loading, classes, classId, setClassId, standard, setStandard, submit } = useRegisterStandardTimetable(); + const { loading, sending, classes, classId, setClassId, standard, setStandard, submit } = + useRegisterStandardTimetable(); return ( <Container maxWidth='lg' component={Box} py={2}> <Backdrop open={loading}> <CircularProgress color='inherit' /> </Backdrop> - <Typography variant='h2' fontSize={40}> - クラス別時間割表 登録・変更 - </Typography> + + <Breadcrumbs separator={<NavigateNextIcon fontSize='small' />} aria-label='breadcrumb'> + <Link underline='hover' color='inherit' href='/'> + トップ + </Link> + <Link underline='hover' color='inherit' href='/console'> + 管理 + </Link> + <Typography color='text.primary' variant='h2' fontSize={32} fontWeight={400}> + クラス別時間割表 登録・変更 + </Typography> + </Breadcrumbs> + <Box width='7rem' m={4}> <FormControl fullWidth disabled={loading}> <InputLabel>クラス</InputLabel> @@ -161,10 +175,21 @@ const Page: NextPage = () => { </Select> </FormControl> </Box> + <StandardTimetableForm standard={standard} setStandard={setStandard} disabled={loading} /> - <Button variant='contained' disabled={loading} sx={{ m: 4 }} startIcon={<SaveIcon />} onClick={() => submit()}> - 保存 - </Button> + + <Box m={4}> + <LoadingButton + variant='contained' + loading={sending} + loadingPosition='start' + disabled={loading} + startIcon={<SaveIcon />} + onClick={() => submit()} + > + 保存 + </LoadingButton> + </Box> </Container> ); }; diff --git a/src/pages/console/timetable.tsx b/src/pages/console/timetable.tsx @@ -0,0 +1,205 @@ +import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import EditIcon from '@mui/icons-material/Edit'; +import NavigateNextIcon from '@mui/icons-material/NavigateNext'; +import SaveIcon from '@mui/icons-material/Save'; +import LoadingButton from '@mui/lab/LoadingButton'; +import Autocomplete from '@mui/material/Autocomplete'; +import Backdrop from '@mui/material/Backdrop'; +import Box from '@mui/material/Box'; +import Breadcrumbs from '@mui/material/Breadcrumbs'; +import Button from '@mui/material/Button'; +import CircularProgress from '@mui/material/CircularProgress'; +import Container from '@mui/material/Container'; +import FormControl from '@mui/material/FormControl'; +import IconButton from '@mui/material/IconButton'; +import InputLabel from '@mui/material/InputLabel'; +import MenuItem from '@mui/material/MenuItem'; +import Paper from '@mui/material/Paper'; +import Select from '@mui/material/Select'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import isValid from 'date-fns/isValid'; + +import DatePicker from 'components/common/DatePicker'; +import Link from 'components/common/Link'; +import { + ABSTRACT_LESSON_NAMES_JA, + ABSTRACT_LESSON_NAME_MAP_EN_TO_JA, + ABSTRACT_LESSON_NAME_MAP_JA_TO_EN, +} from 'constant'; +import useRegisterDayTimetable from 'hooks/useRegisterDayTimetable'; +import { dateToPlainDate, plainDateToDate } from 'utils'; + +import type { NextPage } from 'next'; +import type { FC } from 'react'; +import type { AbstractLessonNameEn } from 'types'; + +const UnitInputForm: FC<{ + value: AbstractLessonNameEn | null; + onChange: (newValue: AbstractLessonNameEn | null) => void; + label: string; + disabled?: boolean; +}> = ({ value, onChange, label, disabled }) => { + return ( + <Autocomplete + disablePortal + autoSelect + disabled={disabled} + value={value ? ABSTRACT_LESSON_NAME_MAP_EN_TO_JA[value] : value} + onChange={(_, newValue) => onChange(newValue ? ABSTRACT_LESSON_NAME_MAP_JA_TO_EN[newValue] : newValue)} + options={ABSTRACT_LESSON_NAMES_JA} + sx={{ width: 120 }} + renderInput={(params) => <TextField {...params} label={label} />} + /> + ); +}; + +// TODO: この登録画面の他に、一括確認画面を作る +const Page: NextPage = () => { + const { + loading, + sending, + grades, + grade, + setGrade, + date, + setDate, + timetable, + setTimetable, + setUsualTimetable, + submit, + } = useRegisterDayTimetable(); + + // TODO: Temporal をそのまま扱える Adapter を用意する? + // TODO: データフェッチに一定以上の時間がかかった場合のみ進捗表示を出す + return ( + <Container maxWidth='lg' component={Box} py={2}> + <Backdrop open={loading}> + <CircularProgress color='inherit' /> + </Backdrop> + + <Breadcrumbs separator={<NavigateNextIcon fontSize='small' />} aria-label='breadcrumb'> + <Link underline='hover' color='inherit' href='/'> + トップ + </Link> + <Link underline='hover' color='inherit' href='/console'> + 管理 + </Link> + <Typography color='text.primary' variant='h2' fontSize={32} fontWeight={400}> + 学年別曜日時限時間割表 登録・変更 + </Typography> + </Breadcrumbs> + + <Stack direction={{ xs: 'column', sm: 'row' }} spacing={{ xs: 2, sm: 8 }} m={4}> + <Box width='7rem'> + <FormControl fullWidth required disabled={loading} size='small'> + <InputLabel>学年</InputLabel> + <Select value={grade ?? ''} label='学年' onChange={(e) => setGrade(e.target.value as number)}> + {grades.map((g) => ( + <MenuItem value={g} key={g}> + {g} + </MenuItem> + ))} + </Select> + </FormControl> + </Box> + + <Stack direction='row' spacing={1}> + <IconButton onClick={() => setDate((old) => old.subtract({ days: 1 }))}> + <ChevronLeftIcon /> + </IconButton> + <Box width='12rem'> + <DatePicker + value={plainDateToDate(date) ?? null} + onChange={(newValue) => { + isValid(newValue) && setDate(dateToPlainDate(newValue as Date)); + }} + inputFormat='yyyy/MM/dd (EEEEE)' + disableMaskedInput + renderInput={(params) => <TextField {...params} />} + /> + </Box> + <IconButton onClick={() => setDate((old) => old.add({ days: 1 }))}> + <ChevronRightIcon /> + </IconButton> + </Stack> + </Stack> + + <Paper component={Box} p={4}> + <Stack direction={{ xs: 'column', md: 'row' }} spacing={2}> + <UnitInputForm + label='1限' + value={timetable.period1} + onChange={(newValue) => setTimetable('period1', newValue)} + disabled={loading} + /> + <UnitInputForm + label='2限' + value={timetable.period2} + onChange={(newValue) => setTimetable('period2', newValue)} + disabled={loading} + /> + <UnitInputForm + label='3限' + value={timetable.period3} + onChange={(newValue) => setTimetable('period3', newValue)} + disabled={loading} + /> + <UnitInputForm + label='4限' + value={timetable.period4} + onChange={(newValue) => setTimetable('period4', newValue)} + disabled={loading} + /> + <UnitInputForm + label='5限' + value={timetable.period5} + onChange={(newValue) => setTimetable('period5', newValue)} + disabled={loading} + /> + <UnitInputForm + label='6限' + value={timetable.period6} + onChange={(newValue) => setTimetable('period6', newValue)} + disabled={loading} + /> + <UnitInputForm + label='7限' + value={timetable.period7} + onChange={(newValue) => setTimetable('period7', newValue)} + disabled={loading} + /> + </Stack> + + <Box mt={2}> + <Button + variant='outlined' + color='secondary' + disabled={loading} + startIcon={<EditIcon />} + onClick={() => setUsualTimetable()} + > + 時間割通りで自動入力 + </Button> + </Box> + </Paper> + + <Box m={4}> + <LoadingButton + variant='contained' + loading={sending} + loadingPosition='start' + disabled={loading} + startIcon={<SaveIcon />} + onClick={() => submit()} + > + 保存 + </LoadingButton> + </Box> + </Container> + ); +}; + +export default Page; diff --git a/src/types/index.ts b/src/types/index.ts @@ -1,3 +1,4 @@ +import type { ABSTRACT_LESSON_NAMES_EN, ABSTRACT_LESSON_NAMES_JA } from 'constant'; import type { Map } from 'immutable'; import type { z } from 'zod'; @@ -6,13 +7,11 @@ export type Grade = 1 | 2 | 3; // クラス export type Class = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8; -type WeekdayEn = 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri'; -type WeekdayJa = '月' | '火' | '水' | '木' | '金'; type SchoolTime = 1 | 2 | 3 | 4 | 5 | 6 | 7; // 月1 から 金7 (木7 以外) -export type AbstractLessonNameJa = Exclude<`${WeekdayJa}${SchoolTime}`, '木7'>; -export type AbstractLessonNameEn = Exclude<`${WeekdayEn}${SchoolTime}`, 'Thu7'>; +export type AbstractLessonNameJa = typeof ABSTRACT_LESSON_NAMES_JA[number]; +export type AbstractLessonNameEn = typeof ABSTRACT_LESSON_NAMES_EN[number]; // 授業名 export type ActualLessonName = string; diff --git a/yarn.lock b/yarn.lock @@ -442,6 +442,29 @@ __metadata: languageName: node linkType: hard +"@mui/base@npm:5.0.0-alpha.105": + version: 5.0.0-alpha.105 + resolution: "@mui/base@npm:5.0.0-alpha.105" + dependencies: + "@babel/runtime": ^7.19.0 + "@emotion/is-prop-valid": ^1.2.0 + "@mui/types": ^7.2.0 + "@mui/utils": ^5.10.9 + "@popperjs/core": ^2.11.6 + clsx: ^1.2.1 + prop-types: ^15.8.1 + react-is: ^18.2.0 + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 1db3dcd6760c814437c679ddf13d9e3c4e7fc6f51ce933445715a56aa65dcaeb38aff35832c524211d62fb3401b436cbcc3e9e4234068a0c3379da727f44cff2 + languageName: node + linkType: hard + "@mui/core-downloads-tracker@npm:^5.10.12": version: 5.10.12 resolution: "@mui/core-downloads-tracker@npm:5.10.12" @@ -465,6 +488,36 @@ __metadata: languageName: node linkType: hard +"@mui/lab@npm:^5.0.0-alpha.107": + version: 5.0.0-alpha.107 + resolution: "@mui/lab@npm:5.0.0-alpha.107" + dependencies: + "@babel/runtime": ^7.19.0 + "@mui/base": 5.0.0-alpha.105 + "@mui/system": ^5.10.13 + "@mui/types": ^7.2.0 + "@mui/utils": ^5.10.9 + clsx: ^1.2.1 + prop-types: ^15.8.1 + react-is: ^18.2.0 + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@mui/material": ^5.0.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 58ea8d0d7bcf84884d2fdcb6234ec9b6a74e514ba78eb951bcbb6905e65de8b1382d9ce9ef9d29bf1b0d75977987fc0d08241d692341ea08626e885acdce173d + languageName: node + linkType: hard + "@mui/material@npm:^5.10.12": version: 5.10.12 resolution: "@mui/material@npm:5.10.12" @@ -564,6 +617,34 @@ __metadata: languageName: node linkType: hard +"@mui/system@npm:^5.10.13": + version: 5.10.13 + resolution: "@mui/system@npm:5.10.13" + dependencies: + "@babel/runtime": ^7.19.0 + "@mui/private-theming": ^5.10.9 + "@mui/styled-engine": ^5.10.8 + "@mui/types": ^7.2.0 + "@mui/utils": ^5.10.9 + clsx: ^1.2.1 + csstype: ^3.1.1 + prop-types: ^15.8.1 + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: afac3f4859b9e137fa62fa4446642909704964209a44f63a472d452ace6428a0b337b48a934cd805e3728a0098ad4f41fedbc06ce14e245a5fa42fabe349d9e8 + languageName: node + linkType: hard + "@mui/types@npm:^7.2.0": version: 7.2.0 resolution: "@mui/types@npm:7.2.0" @@ -5270,6 +5351,7 @@ __metadata: "@emotion/styled": ^11.10.5 "@js-temporal/polyfill": ^0.4.1 "@mui/icons-material": ^5.10.9 + "@mui/lab": ^5.0.0-alpha.107 "@mui/material": ^5.10.12 "@mui/x-date-pickers": ^5.0.7 "@next/eslint-plugin-next": ^13.0.2