From 18d392fb1f8bfa19db040d92ebf6ddc8a7e42afd Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 15 Nov 2023 20:49:26 -0600 Subject: [PATCH] Add rendered config viewer at index for improved debug operations later --- src/components/ConfigurationList.tsx | 146 ++++++++++++++++++++++ src/pages/config.tsx | 159 ++++++++++++++++++++++++ src/pages/index.tsx | 176 ++++++--------------------- 3 files changed, 339 insertions(+), 142 deletions(-) create mode 100644 src/components/ConfigurationList.tsx create mode 100644 src/pages/config.tsx diff --git a/src/components/ConfigurationList.tsx b/src/components/ConfigurationList.tsx new file mode 100644 index 0000000..8c8961b --- /dev/null +++ b/src/components/ConfigurationList.tsx @@ -0,0 +1,146 @@ +import { FunctionComponent } from 'react'; +import { BellIcon, CalendarIcon, ClockIcon } from '@heroicons/react/20/solid'; +import { + type Configuration, + type DayEnum, + numberAsDay, + ParsedTime +} from '@/timing'; +import clsx from 'clsx'; + +export type ConfigurationListProps = { + configs: Configuration; +}; + +const shortDays: Record = { + monday: 'Mon', + tuesday: 'Tue', + wednesday: 'Wed', + thursday: 'Thurs', + friday: 'Fri', + saturday: 'Sat', + sunday: 'Sun' +}; + +const stringifyParsedTime = (time: ParsedTime): string => { + let hour = time.hours; + let postMeridiem = hour >= 12; + if (postMeridiem) hour -= 12; + + return `${hour.toString().padStart(2, ' ')}:${time.minutes + .toString() + .padStart(2, '0')} ${postMeridiem ? 'P' : 'A'}M`; +}; + +// Stringification function for producing dense but precise weekday information. +// Can produce things like "Mon, Wed, Sat", "Mon", "Sat", "Mon - Sat" +const stringifyDaySet = (days: Set): string => { + if (days.size == 0) return 'No days'; + if (days.size == 1) return shortDays[days.values().next().value as DayEnum]; + + // Build a sorted array of the day set so we can display it properly + const array = Array(...days); + array.sort((a, b) => numberAsDay[a] - numberAsDay[b]); + + // Check if continuous from start to end + let isContinuous = true; + let previousDayIndex = numberAsDay[array[0]]; + let current = 0; + while (current < array.length - 1) { + current += 1; + let currentDayIndex = numberAsDay[array[current]]; + // If current day index is previous + 1, then it's still continuous + if (currentDayIndex !== previousDayIndex + 1) { + isContinuous = false; + break; + } + previousDayIndex = currentDayIndex; + } + + if (isContinuous) + return `${shortDays[array[0]]} - ${shortDays[array[current]]}`; + + return array.map((day) => shortDays[day]).join(', '); +}; + +const ConfigurationItem: FunctionComponent<{ + title: string; + isCurrent: boolean; + days: Set; + message: string; + timeString: string; +}> = ({ title, isCurrent, days, message, timeString }) => { + return ( +
  • + +
    +
    +

    + {title} +

    +
    +

    + Not Current +

    +
    +
    +
    +
    +

    +

    +

    +

    +
    +
    +
    +
    +
    +
    +
  • + ); +}; + +const ConfigurationList: FunctionComponent = ({ + configs +}) => { + return ( +
    +
      + {configs.times.map((config, index) => ( + + ))} +
    +
    + ); +}; + +export default ConfigurationList; diff --git a/src/pages/config.tsx b/src/pages/config.tsx new file mode 100644 index 0000000..c70d911 --- /dev/null +++ b/src/pages/config.tsx @@ -0,0 +1,159 @@ +import { + GetServerSidePropsContext, + GetServerSidePropsResult, + NextPage +} from 'next'; +import { z } from 'zod'; +import { env } from '@/env/server.mjs'; +import Editor from 'react-simple-code-editor'; +import { ReactNode, useState } from 'react'; +import { highlight, languages } from 'prismjs'; +import 'prismjs/components/prism-json'; +import { fetchConfiguration } from '@/db'; +import { useForm } from 'react-hook-form'; +import { ConfigurationSchema } from '@/timing'; +import { useRouter } from 'next/router'; +import Layout from '@/components/Layout'; + +type Props = { + config: string; +}; + +export async function getServerSideProps({ + query +}: GetServerSidePropsContext): Promise> { + const parsedKey = z.string().safeParse(query?.key); + + if (parsedKey.success && env.API_KEY === parsedKey.data) { + return { + props: { + config: JSON.stringify( + await fetchConfiguration({ times: [] }, false), + null, + 4 + ) + } + }; + } + + return { + redirect: { + destination: '/login', + permanent: false + } + }; +} + +const exampleConfiguration = { + times: [ + { + time: '03:13', + maxLate: '00:10', + message: 'The bus is leaving soon.', + days: [ + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + 'saturday', + 'sunday' + ], + name: 'B' + }, + { + name: 'A', + message: 'The bus is leaving soon.', + time: '23:26', + maxLate: '00:10', + days: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] + } + ] +}; + +const ConfigurationPage: NextPage = ({ config }) => { + const [code, setCode] = useState(config); + const router = useRouter(); + const [validationElement, setValidationElement] = useState( + null + ); + const [parseError, setParseError] = useState(null); + const { register, handleSubmit } = useForm(); + + async function onSubmit() { + const parsedConfig = await ConfigurationSchema.safeParseAsync( + JSON.parse(code) + ); + if (!parsedConfig.success) { + console.log(parsedConfig.error); + setParseError(parsedConfig.error); + } + setValidationElement( + parsedConfig.success ? 'Valid Configuration' : 'Invalid Configuration' + ); + + if (parsedConfig.success) { + const response = await fetch(`/api/config?key=${router.query?.key}`, { + method: 'POST', + body: code + }); + console.log(response); + } + } + + return ( + +
    +
    +
    + +
    + setCode(code)} + highlight={(code) => highlight(code, languages.json, 'json')} + padding={20} + preClassName="language-json overflow-y-scroll" + textareaClassName="border-zinc-700/70 overflow-y-scroll" + className="text-white w-full rounded-md bg-zinc-800/50 border-zinc-700/70 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + style={{ + fontFamily: '"Fira code", "Fira Mono", monospace' + }} + /> +
    +
    +
    {validationElement}
    +
    + + +
    +
    +
    +
    +
    +
    + ); +}; + +export default ConfigurationPage; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index ea31b31..41a595a 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,159 +1,51 @@ import { - GetServerSidePropsContext, - GetServerSidePropsResult, - NextPage + GetServerSidePropsContext, + GetServerSidePropsResult, + NextPage } from 'next'; import { z } from 'zod'; import { env } from '@/env/server.mjs'; -import Editor from 'react-simple-code-editor'; -import { ReactNode, useState } from 'react'; -import { highlight, languages } from 'prismjs'; -import 'prismjs/components/prism-json'; import { fetchConfiguration } from '@/db'; -import { useForm } from 'react-hook-form'; -import { ConfigurationSchema } from '@/timing'; -import { useRouter } from 'next/router'; import Layout from '@/components/Layout'; +import ConfigurationList from '@/components/ConfigurationList'; +import superjson from 'superjson'; +import type { Configuration } from '@/timing'; -type Props = { - config: string; +type IndexPageProps = { + json: string; }; export async function getServerSideProps({ - query -}: GetServerSidePropsContext): Promise> { - const parsedKey = z.string().safeParse(query?.key); + query +}: GetServerSidePropsContext): Promise< + GetServerSidePropsResult +> { + const parsedKey = z.string().safeParse(query?.key); - if (parsedKey.success && env.API_KEY === parsedKey.data) { - return { - props: { - config: JSON.stringify( - await fetchConfiguration({ times: [] }, false), - null, - 4 - ) - } - }; - } + if (parsedKey.success && env.API_KEY === parsedKey.data) { + const config = await fetchConfiguration({ times: [] }, true); + return { + props: { + json: superjson.stringify(config) + } + }; + } - return { - redirect: { - destination: '/login', - permanent: false - } - }; + return { + redirect: { + destination: '/login', + permanent: false + } + }; } -const exampleConfiguration = { - times: [ - { - time: '03:13', - maxLate: '00:10', - message: 'The bus is leaving soon.', - days: [ - 'monday', - 'tuesday', - 'wednesday', - 'thursday', - 'friday', - 'saturday', - 'sunday' - ], - name: 'B' - }, - { - name: 'A', - message: 'The bus is leaving soon.', - time: '23:26', - maxLate: '00:10', - days: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] - } - ] -}; - -const IndexPage: NextPage = ({ config }) => { - const [code, setCode] = useState(config); - const router = useRouter(); - const [validationElement, setValidationElement] = useState( - null - ); - const [parseError, setParseError] = useState(null); - const { register, handleSubmit } = useForm(); - - async function onSubmit() { - const parsedConfig = await ConfigurationSchema.safeParseAsync( - JSON.parse(code) - ); - if (!parsedConfig.success) { - console.log(parsedConfig.error); - setParseError(parsedConfig.error); - } - setValidationElement( - parsedConfig.success ? 'Valid Configuration' : 'Invalid Configuration' - ); - - if (parsedConfig.success) { - const response = await fetch(`/api/config?key=${router.query?.key}`, { - method: 'POST', - body: code - }); - console.log(response); - } - } - - return ( - -
    -
    -
    - -
    - setCode(code)} - highlight={(code) => highlight(code, languages.json, 'json')} - padding={20} - preClassName="language-json overflow-y-scroll" - textareaClassName="border-zinc-700/70 overflow-y-scroll" - className="text-white w-full rounded-md bg-zinc-800/50 border-zinc-700/70 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" - style={{ - fontFamily: '"Fira code", "Fira Mono", monospace' - }} - /> -
    -
    -
    {validationElement}
    -
    - - -
    -
    -
    -
    -
    -
    - ); +const IndexPage: NextPage = ({ json }) => { + const config = superjson.parse(json); + return ( + + + + ); }; export default IndexPage;