commit cd39c5974529f711c654733291a02a28a6fb5285
parent b3c11af2a4ca04ee8b62c07ffcc1fa1d06e1696e
Author: Yuukin256 <52195426+Yuukin256@users.noreply.github.com>
Date: Sun, 17 Mar 2024 21:47:23 +0900
Merge pull request #17 from Yuukin256/rollback-to-worked
2023年度サービス終了処理を巻き戻し
Diffstat:
11 files changed, 682 insertions(+), 83 deletions(-)
diff --git a/.vscode/settings.json b/.vscode/settings.json
@@ -1,10 +1,11 @@
{
"npm.packageManager": "pnpm",
+ "eslint.packageManager": "pnpm",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnPaste": true,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
- "source.fixAll.eslint": "explicit"
+ "source.fixAll.eslint": true
},
"[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"
diff --git a/next.config.js b/next.config.js
@@ -13,13 +13,31 @@ const nextConfig = withPWA({
swcPlugins: [['next-superjson-plugin', {}]],
},
redirects: async () => {
- return [
- {
- source: '/:slug(.+)',
- destination: '/',
- permanent: false,
- },
- ];
+ const startClassRedirects = [1, 2, 3].flatMap((g) =>
+ [1, 2, 3, 4, 5, 6, 7, 8].map((c) => {
+ const className = `${g}-${c}`;
+ return {
+ source: '/start',
+ has: [
+ {
+ type: 'cookie',
+ key: 'default_class',
+ value: className,
+ },
+ ],
+ destination: '/' + className,
+ permanent: false,
+ };
+ })
+ );
+
+ const startDefaultRedirect = {
+ source: '/start',
+ destination: '/',
+ permanent: false,
+ };
+
+ return [...startClassRedirects, startDefaultRedirect];
},
});
diff --git a/src/client/components/SlideFooter/index.tsx b/src/client/components/SlideFooter/index.tsx
@@ -50,15 +50,6 @@ type SlideData = {
const prioritySlideData: SlideData[] = [
{
- id: 1,
- content: (
- <>
- このアプリは2月16日をもってサービス終了となります。ご利用いただきありがとうございました!
- </>
- ),
- severity: 'success',
- },
- {
id: 11,
content: (
<>
diff --git a/src/client/components/WarningOfUse/index.tsx b/src/client/components/WarningOfUse/index.tsx
@@ -36,6 +36,7 @@ export default function WarningOfUse(props: Props) {
<li>
このアプリの利用により何らかの不利益が生じた場合、IT部や学校側は一切の責任を負いません。
</li>
+ <li>予告なくサービスを停止する可能性があります。</li>
</List>
</Alert>
);
diff --git a/src/pages/[class]/index.tsx b/src/pages/[class]/index.tsx
@@ -0,0 +1,215 @@
+import { Box } from '@mui/material';
+import { addDays, subDays } from 'date-fns';
+
+import Head from 'client/components/Head';
+import Layout from 'client/components/Layout';
+import SlideFooter from 'client/components/SlideFooter';
+import DailyView from 'client/features/daily';
+import { CLASSES, DATE_FORMAT, GRADES } from 'common/constant';
+import formatDate from 'common/utils/formatDate';
+import { zodClass, zodGrade, zodPeriod } from 'common/utils/zod';
+import { caller } from 'server/routers/_app';
+import { prisma } from 'server/utils/prisma';
+
+import type { DayTimetable, LessonData } from 'client/features/daily/types';
+import type { Class, Grade } from 'common/types';
+import type { NextPage, GetStaticPaths, GetStaticProps } from 'next';
+
+type UrlQuery = {
+ class: string;
+};
+
+type PageProps = {
+ grade: Grade;
+ class: Class;
+ calendar: [string, DayTimetable][];
+};
+
+export const getStaticPaths: GetStaticPaths<UrlQuery> = async () => {
+ const paths = GRADES.flatMap((g) =>
+ CLASSES.map((c) => ({ params: { class: `${g}-${c}` } }))
+ );
+
+ return {
+ paths,
+ fallback: false,
+ };
+};
+
+export const getStaticProps: GetStaticProps<PageProps, UrlQuery> = async ({
+ params,
+}) => {
+ if (typeof params === 'undefined') {
+ return {
+ notFound: true,
+ };
+ }
+
+ // 学年、クラスの数字が問題ないか確認
+ const [gradeNumRaw, classNumRaw] = params.class
+ .split('-')
+ .map((v) => parseInt(v));
+ const gradeParsed = zodGrade.safeParse(gradeNumRaw);
+ const classParsed = zodClass.safeParse(classNumRaw);
+ if (!gradeParsed.success || !classParsed.success) {
+ return {
+ notFound: true,
+ };
+ }
+
+ const now = new Date();
+ const startDate = subDays(now, 5);
+ const endDate = addDays(now, 30);
+
+ // クラスの標準時間割表を取得
+ const { standard, id: classId } = await caller.classStandard.byGradeAndClass({
+ grade: gradeParsed.data,
+ class: classParsed.data,
+ });
+
+ // 学年の曜日時限時間割表を取得
+ const schedules = await prisma.gradeSchedule.findMany({
+ where: {
+ grade: gradeParsed.data,
+ date: {
+ gte: startDate,
+ lte: endDate,
+ },
+ },
+ });
+
+ // クラスの臨時変更を取得
+ const temporarySchedules = await prisma.classTemporarySchedule.findMany({
+ where: {
+ classId,
+ date: {
+ gte: startDate,
+ lte: endDate,
+ },
+ },
+ });
+
+ // 学年のイベントを取得
+ const events = await prisma.schoolEvent.findMany({
+ where: {
+ grades: { has: gradeParsed.data },
+ date: {
+ gte: startDate,
+ lte: endDate,
+ },
+ },
+ });
+
+ // 時間割を構築していくMap
+ const calendar: Map<string, DayTimetable> = new Map();
+
+ // イベントでループしてMapに当てはめていく
+ events.forEach((event) => {
+ const dateString = formatDate(event.date, DATE_FORMAT);
+ const old = calendar.get(dateString);
+ if (old) {
+ calendar.set(dateString, {
+ ...old,
+ events: [...old.events, event],
+ });
+ } else {
+ calendar.set(dateString, {
+ date: event.date,
+ events: [event],
+ });
+ }
+ });
+
+ // 曜日時限時間割表の1コマずつでループしてMapに当てはめていく
+ schedules.forEach(({ date, period, dayPeriod, updatedAt }) => {
+ const dateString = formatDate(date, DATE_FORMAT);
+ const parsedPeriod = zodPeriod.safeParse(period);
+
+ if (!parsedPeriod.success) return;
+
+ const old = calendar.get(dateString);
+ const dataToAdd: LessonData = {
+ subjects: standard.get(dayPeriod) ?? [],
+ basis: { type: 'dayPeriod', dayPeriod },
+ updated: updatedAt,
+ };
+
+ if (old) {
+ calendar.set(dateString, {
+ ...old,
+ [parsedPeriod.data]: dataToAdd,
+ });
+ } else {
+ calendar.set(dateString, {
+ date: date,
+ events: [],
+ [parsedPeriod.data]: dataToAdd,
+ });
+ }
+ });
+
+ // 臨時変更を1コマずつループしてMapに当てはめていく
+ temporarySchedules.forEach(({ id, date, period, subjects, updatedAt }) => {
+ const dateString = formatDate(date, DATE_FORMAT);
+ const parsedPeriod = zodPeriod.safeParse(period);
+
+ if (!parsedPeriod.success) return;
+
+ const old = calendar.get(dateString);
+ const dataToAdd: LessonData = {
+ subjects,
+ basis: { type: 'temporary', id },
+ updated: updatedAt,
+ };
+
+ if (old) {
+ calendar.set(dateString, {
+ ...old,
+ [parsedPeriod.data]: dataToAdd,
+ });
+ } else {
+ calendar.set(dateString, {
+ date: date,
+ events: [],
+ [parsedPeriod.data]: dataToAdd,
+ });
+ }
+ });
+
+ // 日付順に並び替えた配列にする
+ const sorted = [...calendar.entries()].sort((a, b) => {
+ return a[1].date.getTime() - b[1].date.getTime();
+ });
+
+ return {
+ props: {
+ grade: gradeParsed.data,
+ class: classParsed.data,
+ calendar: sorted,
+ },
+ revalidate: 60 * 60, // 1時間毎の訪問で再生成
+ };
+};
+
+// 5日前~30日後の時間割を表示するページ
+const Page: NextPage<PageProps> = (props) => {
+ const className = `${props.grade}年${props.class}組`;
+ return (
+ <>
+ <Head title={className} />
+ <Layout title={className} footer={<SlideFooter />}>
+ <Box width={1}>
+ {props.calendar.length ? (
+ <DailyView {...props} />
+ ) : (
+ <Box px={2}>
+ <p>データがありません</p>
+ </Box>
+ )}
+ </Box>
+ </Layout>
+ </>
+ );
+};
+
+export default Page;
diff --git a/src/pages/about.tsx b/src/pages/about.tsx
@@ -0,0 +1,119 @@
+import InstagramIcon from '@mui/icons-material/Instagram';
+import SchoolIcon from '@mui/icons-material/School';
+import TwitterIcon from '@mui/icons-material/Twitter';
+import { Box, Divider, List, ListItem, Stack } from '@mui/material';
+
+import Head from 'client/components/Head';
+import Layout from 'client/components/Layout';
+import Link from 'client/components/Link';
+import WarningOfUse from 'client/components/WarningOfUse';
+import { BUG_REPORT_FORM_URL, FEEDBACK_FORM_URL } from 'common/constant';
+
+import type { NextPage } from 'next';
+
+const AboutPage: NextPage = () => {
+ return (
+ <>
+ <Head title='このアプリについて' />
+ <Layout title='このアプリについて'>
+ <Box px={2} pb={2} height='fit-content'>
+ <p>
+ 大阪府立岸和田高等学校
+ IT部制作「岸高時間割アプリ」です。元は76期情報ゼミの研究活動で制作していたものを、IT部に引き継ぎました。
+ アプリの公開期間は2024年3月頃までの予定です。早期に修正することが不可能な問題が発生した場合は公開を中断する可能性があります。
+ </p>
+
+ <p>
+ このアプリでは、
+ 時間割変更(曜日変更)が反映されたクラスの時間割を簡単に確認することができます。詳しくは
+ <Link href={{ pathname: '/how-to-use' }}>使い方</Link>
+ をご覧ください。
+ </p>
+
+ <WarningOfUse />
+
+ {BUG_REPORT_FORM_URL && (
+ <>
+ <p>
+ アプリの動作に不具合があった場合や、時間割の情報が間違っていた場合などは、以下のGoogleフォームに報告をお願いします。
+ </p>
+ <ul>
+ <li>
+ <Link
+ href={BUG_REPORT_FORM_URL}
+ target='_blank'
+ rel='noopener'
+ >
+ 不具合報告フォーム
+ </Link>
+ </li>
+ </ul>
+ </>
+ )}
+
+ {FEEDBACK_FORM_URL && (
+ <>
+ <p>
+ 以下のGoogleフォームから、是非アプリを評価してください!ご意見・ご要望などもお待ちしております。「こんな機能があったらいいな」「こうしたほうが使いやすいな」など、率直に教えていただけると非常に参考になります。
+ </p>
+ <ul>
+ <li>
+ <Link href={FEEDBACK_FORM_URL} target='_blank' rel='noopener'>
+ フィードバックフォーム
+ </Link>
+ </li>
+ </ul>
+ </>
+ )}
+
+ <p>今後の開発に活かすため、皆さんのご協力をお願いします。</p>
+
+ <Divider />
+
+ <List>
+ <ListItem>
+ <Stack direction='row' spacing={1} alignItems='center'>
+ <SchoolIcon fontSize='inherit' />
+ <Link
+ href='https://www.osaka-c.ed.jp/kishiwada/'
+ target='_blank'
+ rel='noopener'
+ >
+ 大阪府立岸和田高等学校
+ </Link>
+ </Stack>
+ </ListItem>
+
+ <ListItem>
+ <Stack direction='row' spacing={1} alignItems='center'>
+ <TwitterIcon fontSize='inherit' />
+ <Link
+ href='https://twitter.com/Kishiwada_it'
+ target='_blank'
+ rel='noopener'
+ >
+ IT部 Twitter
+ </Link>
+ </Stack>
+ </ListItem>
+
+ <ListItem>
+ <Stack direction='row' spacing={1} alignItems='center'>
+ <InstagramIcon fontSize='inherit' />
+ <Link
+ href='https://www.instagram.com/kishiwada_it/'
+ target='_blank'
+ rel='noopener'
+ >
+ IT部 Instagram
+ </Link>
+ </Stack>
+ </ListItem>
+ </List>
+ </Box>
+ </Layout>
+ </>
+ );
+};
+
+export default AboutPage;
diff --git a/src/pages/api/auth.ts b/src/pages/api/auth.ts
@@ -0,0 +1,7 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+
+export default function handler(_: NextApiRequest, res: NextApiResponse) {
+ res.setHeader('WWW-authenticate', 'Basic realm="Secure Area"');
+ res.statusCode = 401;
+ res.end(`Auth Required.`);
+}
diff --git a/src/pages/api/trpc/[trpc].ts b/src/pages/api/trpc/[trpc].ts
@@ -0,0 +1,11 @@
+import * as trpcNext from '@trpc/server/adapters/next';
+
+import { createContext } from 'server/context';
+import { appRouter } from 'server/routers/_app';
+
+// export API handler
+// @see https://trpc.io/docs/api-handler
+export default trpcNext.createNextApiHandler({
+ router: appRouter,
+ createContext,
+});
diff --git a/src/pages/how-to-use.tsx b/src/pages/how-to-use.tsx
@@ -0,0 +1,234 @@
+import EventIcon from '@mui/icons-material/Event';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import HomeIcon from '@mui/icons-material/Home';
+import InstallMobileIcon from '@mui/icons-material/InstallMobile';
+import IosShareIcon from '@mui/icons-material/IosShare';
+import MenuIcon from '@mui/icons-material/Menu';
+import MoreVertIcon from '@mui/icons-material/MoreVert';
+import NoteAddIcon from '@mui/icons-material/NoteAdd';
+import {
+ Accordion,
+ AccordionDetails,
+ AccordionSummary,
+ Box,
+ Button,
+ Chip,
+ Divider,
+ Typography,
+} from '@mui/material';
+import Image from 'next/image';
+
+import Head from 'client/components/Head';
+import Layout from 'client/components/Layout';
+import { NextLinkComposed } from 'client/components/Link';
+import WarningOfUse from 'client/components/WarningOfUse';
+
+import type { NextPage } from 'next';
+
+const HouToUsePage: NextPage = () => {
+ return (
+ <>
+ <Head title='使い方' />
+ <Layout title='使い方'>
+ <Box px={2} mb='50vh' height='fit-content'>
+ <p>
+ IT部制作「岸高時間割アプリ」は、時間割変更(曜日変更)が反映されたクラスごとの時間割表を表示するアプリです。初めてお使いの方は、まずは下記の「利用上の注意・免責事項」をお読みください。
+ </p>
+ <WarningOfUse />
+ <p>
+ 上記の内容を理解し、了承された方のみこのアプリをお使いください。以下にアプリの使い方が続きます。
+ </p>
+ <Box mb={4}>
+ <Typography variant='h3' fontSize={20}>
+ 時間割ページの使い方
+ </Typography>
+ <Box
+ sx={{ display: 'flex', justifyContent: 'space-evenly', my: 2 }}
+ >
+ <Image
+ src='/app-example.png'
+ width={432}
+ height={768}
+ style={{
+ width: 'auto',
+ height: '50vh',
+ border: '1px solid gray',
+ }}
+ alt='時間割ページの表示例'
+ />
+ </Box>
+ <p>
+ 時間割ページでは上の画像のように、クラス別に1日ごとに時間割を表示しています。過去5日間と未来1ヶ月間の時間割を表示します。
+ </p>
+ <p>
+ 左右にフリックすることで前後の日付に移動できます。
+ <EventIcon
+ fontSize='inherit'
+ style={{ verticalAlign: 'text-bottom' }}
+ />
+ を押すとカレンダーが開き、日付を選択できます。
+ </p>
+ <p>
+ <NoteAddIcon
+ fontSize='inherit'
+ style={{ verticalAlign: 'text-bottom' }}
+ />
+ を押すと授業ごとにメモを入力できます。小テストや提出物、臨時変更の予定など、ご自由にお使いください。メモの内容はブラウザアプリに保存されるため、別のブラウザで開いてもデータは引き継がれません。
+ </p>
+ <p>
+ その科目名の元となっている曜日時限を{' '}
+ <Chip component='span' label='金1' size='small' />{' '}
+ のように表示しています。時間割変更がある場合は色が変わって{' '}
+ <Chip component='span' label='金1' size='small' color='warning' />{' '}
+ のように強調表示されます。
+ </p>
+ <p>
+ 左下にデータ更新日を表示しています。タップすると、さらに詳細に1コマずつのデータ更新日時が表示されます。
+ </p>
+ </Box>
+
+ <Box mb={4}>
+ <Typography variant='h3' fontSize={20}>
+ 表示科目名変更機能(3年生にオススメ!)
+ </Typography>
+ <p>
+ 主に3年生は授業展開が多いため、上の画像のようにいくつもの科目名がスラッシュで区切られて表示され、一部がはみ出て見えないこともあります。
+ </p>
+ <p>
+ そこで、曜日時限ごとに自分の選択科目を設定し、時間割ページで自分専用の時間割を表示する機能があります。
+ あとで右上{' '}
+ <MenuIcon
+ fontSize='inherit'
+ style={{ verticalAlign: 'text-bottom' }}
+ />{' '}
+ 内の設定ページから自分の時間割を設定してみては?(まずはこのページを最後までお読みください)
+ </p>
+ <p>
+ ※3年生以外でもお使いいただけます。
+ <br />
+ ※設定した科目名はブラウザアプリに保存されるため、別のブラウザで開いてもデータは引き継がれません。
+ </p>
+ </Box>
+
+ <Box mb={4}>
+ <Typography variant='h3' fontSize={20} id='install'>
+ インストール機能(PWA)と所属クラス機能
+ </Typography>
+ <p>
+ このアプリではPWAという技術を使用しているので、ブラウザで動作するWebアプリでありながら通常のアプリのようにインストールして使うことができます。インストールすると、ホーム画面のアイコンからダイレクトに自分のクラスの時間割を開けるようになります。
+ </p>
+
+ <Accordion>
+ <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+ インストール方法 (iPhone)
+ </AccordionSummary>
+ <AccordionDetails>
+ <ol>
+ <li>
+ このアプリをSafariで開く
+ <Box ml='2em'>
+ QRコードリーダーで開いている場合は、右下のアイコンをタップしてSafariで開く。Safari以外のブラウザで開いている場合はURLをコピー&ペーストするなどしてSafariで開き直す。
+ </Box>
+ </li>
+ <li>
+ 共有メニュー{' '}
+ <IosShareIcon
+ fontSize='inherit'
+ style={{ verticalAlign: 'text-bottom' }}
+ />{' '}
+ を開く
+ </li>
+ <li>「ホーム画面に追加」をタップ</li>
+ <li>ポップアップ内の「追加」をタップ</li>
+ </ol>
+ </AccordionDetails>
+ </Accordion>
+
+ <Accordion>
+ <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+ インストール方法 (Android)
+ </AccordionSummary>
+ <AccordionDetails>
+ <ol>
+ <li>
+ このアプリをChromeで開く
+ <Box ml='2em'>
+ 上部のアドレスバーをタップして編集できる状態になっていればOKです。Googleレンズのアプリ内ブラウザで開いているなどの場合は、メニュー
+ <MoreVertIcon
+ fontSize='inherit'
+ style={{ verticalAlign: 'text-bottom' }}
+ />
+ から「Chromeで開く」をタップ。その他の場合でも頑張ってChromeで開いてください。
+ </Box>
+ </li>
+ <li>
+ メニュー{' '}
+ <MoreVertIcon
+ fontSize='inherit'
+ style={{ verticalAlign: 'text-bottom' }}
+ />{' '}
+ 内の「
+ <InstallMobileIcon
+ fontSize='inherit'
+ style={{ verticalAlign: 'text-bottom' }}
+ />
+ アプリをインストール」をタップ
+ </li>
+ <li>ダイアログ内の「インストール」をタップ</li>
+ </ol>
+ </AccordionDetails>
+ </Accordion>
+
+ <p>
+ アプリをインストールした状態で時間割ページを開いて数秒すると「所属クラスに設定」ボタンが表示されます。設定後、ホーム画面のアイコンから起動すると、設定したクラスの時間割ページが開きます。
+ </p>
+
+ <p>
+ ちなみに、表示のライトモードとダークモードは右上{' '}
+ <MenuIcon
+ fontSize='inherit'
+ style={{ verticalAlign: 'text-bottom' }}
+ />{' '}
+ 内の設定ページで切り替え可能です。
+ </p>
+ </Box>
+
+ <Divider />
+
+ <p>説明は以上で終わりです。</p>
+ <p>
+ アプリの不具合や時間割の間違いを見つけた場合は、アプリ内の「不具合・誤情報報告」のGoogleフォームに報告をお願いします。
+ </p>
+ <p>
+ アプリへの評価やご意見、ご要望も募集しています。アプリ内の「フィードバック」からぜひご協力ください。
+ </p>
+ <p>
+ トップページから各クラスの時間割ページを開けます。「岸高時間割アプリ」を使って快適な高校生活を送ろう!
+ </p>
+
+ <Button
+ component={NextLinkComposed}
+ to='/'
+ variant='contained'
+ startIcon={<HomeIcon />}
+ sx={{ mx: 2, mb: 2 }}
+ >
+ トップページへ
+ </Button>
+
+ <Divider />
+
+ <p>これからどうすれば良いのか迷った人向け↓</p>
+ <ol>
+ <li>アプリをインストールする</li>
+ <li>所属クラスを設定する</li>
+ <li>いろいろ触ってみる</li>
+ <li>自分の科目名を登録する</li>
+ </ol>
+ </Box>
+ </Layout>
+ </>
+ );
+};
+
+export default HouToUsePage;
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
@@ -1,12 +1,10 @@
-import InstagramIcon from '@mui/icons-material/Instagram';
-import SchoolIcon from '@mui/icons-material/School';
-import XIcon from '@mui/icons-material/X';
-import { Box, Divider, List, ListItem, Stack } from '@mui/material';
+import { Box, Typography, Stack, Button } from '@mui/material';
import Head from 'client/components/Head';
import Layout from 'client/components/Layout';
import Link from 'client/components/Link';
-import { FEEDBACK_FORM_URL } from 'common/constant';
+import WarningOfUse from 'client/components/WarningOfUse';
+import { CLASSES, GRADES } from 'common/constant';
import type { NextPage } from 'next';
@@ -17,70 +15,41 @@ const Page: NextPage = () => {
<Layout title='トップ'>
<Box px={2} pb={2} height='fit-content'>
<p>
- 岸和田高校IT部制作「岸高時間割アプリ」です。2023年度版は2024年2月16日をもってサービスを終了しました。今までご利用いただきありがとうございました!
+ 岸和田高校IT部制作「岸高時間割アプリ」です。詳細は「
+ <Link href='/about'>このアプリについて</Link>
+ 」をご覧ください。
</p>
- <p>
- 2024年度もアプリを公開できるように準備しております。なお、アプリの運営に興味がある方は遠慮なくIT部までご連絡ください。
- </p>
-
- <p>
- 以下のGoogleフォームから、是非アプリを評価してください!ご意見・ご要望などもお待ちしております。機能改善や新機能も来年度には対応できるかもしれません。
- </p>
- <ul>
- <li>
- <Link
- href={FEEDBACK_FORM_URL ?? ''}
- target='_blank'
- rel='noopener'
- >
- フィードバックフォーム
- </Link>
- </li>
- </ul>
-
- <Divider />
-
- <List>
- <ListItem>
- <Stack direction='row' spacing={1} alignItems='center'>
- <SchoolIcon fontSize='inherit' />
- <Link
- href='https://www.osaka-c.ed.jp/kishiwada/'
- target='_blank'
- rel='noopener'
- >
- 大阪府立岸和田高等学校
- </Link>
- </Stack>
- </ListItem>
-
- <ListItem>
- <Stack direction='row' spacing={1} alignItems='center'>
- <XIcon fontSize='inherit' />
- <Link
- href='https://twitter.com/Kishiwada_it'
- target='_blank'
- rel='noopener'
- >
- IT部 X (旧Twitter)
- </Link>
- </Stack>
- </ListItem>
-
- <ListItem>
- <Stack direction='row' spacing={1} alignItems='center'>
- <InstagramIcon fontSize='inherit' />
- <Link
- href='https://www.instagram.com/kishiwada_it/'
- target='_blank'
- rel='noopener'
- >
- IT部 Instagram
- </Link>
+ <WarningOfUse />
+
+ <Typography variant='h2' fontSize={24} my={3}>
+ 時間割ページ一覧
+ </Typography>
+
+ <Stack
+ direction='row'
+ spacing={{ xs: 2, md: 4 }}
+ alignItems='center'
+ width='100%'
+ px={2}
+ >
+ {GRADES.map((g) => (
+ <Stack spacing={2} width='100%' key={g}>
+ {CLASSES.map((c) => (
+ <Button
+ fullWidth
+ variant='outlined'
+ href={`/${g}-${c}`}
+ sx={{ fontSize: 15, fontWeight: 400 }}
+ LinkComponent={Link}
+ key={c}
+ >
+ {g}年{c}組
+ </Button>
+ ))}
</Stack>
- </ListItem>
- </List>
+ ))}
+ </Stack>
</Box>
</Layout>
</>
diff --git a/src/pages/setting.tsx b/src/pages/setting.tsx
@@ -0,0 +1,33 @@
+import { Backdrop, Box, CircularProgress } from '@mui/material';
+import dynamic from 'next/dynamic';
+
+import Head from 'client/components/Head';
+import Layout from 'client/components/Layout';
+
+import type { NextPage } from 'next';
+
+// 設定内容はクライアントで反映させたいので動的読み込みする
+const SettingView = dynamic(() => import('client/features/setting'), {
+ ssr: false,
+ loading: () => (
+ <Backdrop open transitionDuration={0}>
+ <CircularProgress />
+ </Backdrop>
+ ),
+});
+
+// テーマ、所属クラスを設定するページ
+const SettingPage: NextPage = () => {
+ return (
+ <>
+ <Head title='設定' />
+ <Layout title='設定'>
+ <Box p={2} pb={2} width={1} height='fit-content'>
+ <SettingView />
+ </Box>
+ </Layout>
+ </>
+ );
+};
+
+export default SettingPage;