From b6ec04b321907363d2662b1da40728e11392e62a Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Sun, 20 Oct 2024 16:44:53 +0200 Subject: [PATCH 01/22] feat: create shift schedule page, layout, and table --- messages/en.json | 22 +++ messages/no.json | 22 +++ .../(default)/shift-schedule/layout.tsx | 24 +++ .../(default)/shift-schedule/page.tsx | 33 +++++ .../shift-schedule/ScheduleCell.tsx | 22 +++ .../shift-schedule/ScheduleCellContent.tsx | 57 ++++++++ .../shift-schedule/ScheduleCellDialog.tsx | 35 +++++ .../shift-schedule/ScheduleTable.tsx | 89 ++++++++++++ src/lib/locale/index.ts | 4 + src/mock-data/shiftSchedule.ts | 137 ++++++++++++++++++ 10 files changed, 445 insertions(+) create mode 100644 src/app/[locale]/(default)/shift-schedule/layout.tsx create mode 100644 src/app/[locale]/(default)/shift-schedule/page.tsx create mode 100644 src/components/shift-schedule/ScheduleCell.tsx create mode 100644 src/components/shift-schedule/ScheduleCellContent.tsx create mode 100644 src/components/shift-schedule/ScheduleCellDialog.tsx create mode 100644 src/components/shift-schedule/ScheduleTable.tsx create mode 100644 src/mock-data/shiftSchedule.ts diff --git a/messages/en.json b/messages/en.json index 5c0c922..14b109b 100644 --- a/messages/en.json +++ b/messages/en.json @@ -29,6 +29,7 @@ "events": "Events", "storage": "Storage", "about": "About", + "shiftSchedule": "Shift Schedule", "changeLocale": "Change language", "toggleTheme": "Toggle theme", "light": "Light", @@ -123,5 +124,26 @@ "returnByDescription": "Select how long you would like to borrow the item for.", "submit": "Submit" } + }, + "shiftSchedule": { + "title": "Shift Schedule", + "scheduleTable": { + "time": "Time", + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "scheduleCellContent": { + "empty": "Empty", + "member": "person", + "members": "people", + "present": "on shift" + }, + "scheduleCellDialog": { + "onShift": "On shift", + "register": "Register" + } + } } } diff --git a/messages/no.json b/messages/no.json index 62a6fd0..c70bd7a 100644 --- a/messages/no.json +++ b/messages/no.json @@ -29,6 +29,7 @@ "events": "Hendelser", "storage": "Lager", "about": "Om oss", + "shiftSchedule": "Vaktliste", "changeLocale": "Bytt språk", "toggleTheme": "Bytt tema", "light": "Lys", @@ -123,5 +124,26 @@ "returnByDescription": "Velg hvor lenge du ønsker å låne gjenstanden(e)", "submit": "Send" } + }, + "shiftSchedule": { + "title": "Vaktliste", + "scheduleTable": { + "time": "Tid", + "monday": "Mandag", + "tuesday": "Tirsdag", + "wednesday": "Onsdag", + "thursday": "Torsdag", + "friday": "Fredag", + "scheduleCellContent": { + "empty": "Tomt", + "member": "person", + "members": "personer", + "present": "på vakt" + }, + "scheduleCellDialog": { + "onShift": "På vakt", + "register": "Registrer" + } + } } } diff --git a/src/app/[locale]/(default)/shift-schedule/layout.tsx b/src/app/[locale]/(default)/shift-schedule/layout.tsx new file mode 100644 index 0000000..7246e7f --- /dev/null +++ b/src/app/[locale]/(default)/shift-schedule/layout.tsx @@ -0,0 +1,24 @@ +import { useTranslations } from "next-intl"; +import { unstable_setRequestLocale } from "next-intl/server"; + +type ShiftScheduleHeaderProps = { + children: React.ReactNode, + params: { locale: string } +}; + +export default function ShiftScheduleHeaderLayout({ + children, + params: { locale }, +}: ShiftScheduleHeaderProps) { + unstable_setRequestLocale(locale); + const t = useTranslations('shiftSchedule'); + + return ( + <> +
+

{t('title')}

+
+ {children} + + ); +} \ No newline at end of file diff --git a/src/app/[locale]/(default)/shift-schedule/page.tsx b/src/app/[locale]/(default)/shift-schedule/page.tsx new file mode 100644 index 0000000..acd7cc2 --- /dev/null +++ b/src/app/[locale]/(default)/shift-schedule/page.tsx @@ -0,0 +1,33 @@ +import { ScheduleTable } from '@/components/shift-schedule/ScheduleTable'; +import { useTranslations } from 'next-intl'; +import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; +import { shiftScheduleMochData } from '@/mock-data/shiftSchedule'; + +export async function generateMetadata({ + params: { locale }, +}: { + params: { locale: string }; +}) { + const t = await getTranslations({ locale, namespace: 'layout' }); + + return { + title: t('shiftSchedule'), + }; +} + +export default function AboutPage({ + params: { locale }, +}: { + params: { locale: string }; +}) { + unstable_setRequestLocale(locale); + const t = useTranslations('shiftSchedule'); + + return ( + <> +
+ +
+ + ); +} diff --git a/src/components/shift-schedule/ScheduleCell.tsx b/src/components/shift-schedule/ScheduleCell.tsx new file mode 100644 index 0000000..18728cd --- /dev/null +++ b/src/components/shift-schedule/ScheduleCell.tsx @@ -0,0 +1,22 @@ +import { ScheduleCellContent } from '@/components/shift-schedule/ScheduleCellContent'; +import { ScheduleCellDialog } from '@/components/shift-schedule/ScheduleCellDialog'; +import type { ScheduleCellProps } from '@/components/shift-schedule/ScheduleTable'; +import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/Dialog'; +import React from 'react'; + +function ScheduleCell({ members }: ScheduleCellProps) { + return ( + <> + + + + + + + + + + ); +} + +export { ScheduleCell }; diff --git a/src/components/shift-schedule/ScheduleCellContent.tsx b/src/components/shift-schedule/ScheduleCellContent.tsx new file mode 100644 index 0000000..ec0ffdd --- /dev/null +++ b/src/components/shift-schedule/ScheduleCellContent.tsx @@ -0,0 +1,57 @@ +import type { ScheduleCellProps } from '@/components/shift-schedule/ScheduleTable'; +import { TableCell } from '@/components/ui/Table'; +import { cx } from '@/lib/utils'; +import { UserIcon, UsersIcon } from 'lucide-react'; +import { useTranslations } from 'next-intl'; +import type React from 'react'; + +function ScheduleCellContent({ members }: ScheduleCellProps) { + const t = useTranslations('shiftSchedule.scheduleTable.scheduleCellContent'); + + const colorStyle = + members.length === 0 + ? 'bg-destructive text-destructive-foreground' + : 'bg-primary text-primary-foreground'; + let membersDisplay: React.ReactNode; + const membersDisplayStyle = 'flex align-bottom space-x-1 space-y-0'; + + if (members.length === 0) { + membersDisplay =

{t('empty')}

; + } else if (members.length === 1) { + membersDisplay = ( +
+ +

+ 1 {t('member')} {t('present')} +

+
+ ); + } else { + membersDisplay = ( +
+ +

+ {members.length} {t('members')} {t('present')} +

+
+ ); + } + + return ( + <> + +
+ {membersDisplay} +
[skill icons]
+
+
+ + ); +} + +export { ScheduleCellContent }; diff --git a/src/components/shift-schedule/ScheduleCellDialog.tsx b/src/components/shift-schedule/ScheduleCellDialog.tsx new file mode 100644 index 0000000..fbb6b45 --- /dev/null +++ b/src/components/shift-schedule/ScheduleCellDialog.tsx @@ -0,0 +1,35 @@ +import type { ScheduleCellProps } from '@/components/shift-schedule/ScheduleTable'; +import { Button } from '@/components/ui/Button'; +import { + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/Dialog'; +import { useTranslations } from 'next-intl'; + +function ScheduleCellDialog({ members }: ScheduleCellProps) { + const t = useTranslations('shiftSchedule.scheduleTable.scheduleCellDialog'); + + return ( + <> + + + {t('onShift')} + + +
+
+ {members.map((member) => ( +

+ {member.name} +

+ ))} +
+ +
+ + ); +} + +export { ScheduleCellDialog }; diff --git a/src/components/shift-schedule/ScheduleTable.tsx b/src/components/shift-schedule/ScheduleTable.tsx new file mode 100644 index 0000000..bef3894 --- /dev/null +++ b/src/components/shift-schedule/ScheduleTable.tsx @@ -0,0 +1,89 @@ +import { ScheduleCell } from '@/components/shift-schedule/ScheduleCell'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/Table'; +import { MessageKeys, useTranslations } from 'next-intl'; + +export type ScheduleCellProps = { + members: { + name: string; + skills: { + _3dPrinting: boolean; + laserCutting: boolean; + microcontrollers: boolean; + raspberryPi: boolean; + soldering: boolean; + terminal: boolean; + workshop: boolean; + }; + }[]; +}; + +type ScheduleDayProps = { + '10:15 - 12:07': ScheduleCellProps; + '12:07 - 14:07': ScheduleCellProps; + '14:07 - 16:07': ScheduleCellProps; + '16:07 - 18:00': ScheduleCellProps; +}; + +type ScheduleTableProps = { + week: { + monday: ScheduleDayProps; + tuesday: ScheduleDayProps; + wednesday: ScheduleDayProps; + thursday: ScheduleDayProps; + friday: ScheduleDayProps; + }; +}; + +function ScheduleTable({ week }: ScheduleTableProps) { + const t = useTranslations('shiftSchedule.scheduleTable'); + // Cannot use translation unless days and times are of these types + const days: ('monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday')[] = [ + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + ]; + const times: ( + | '10:15 - 12:07' + | '12:07 - 14:07' + | '14:07 - 16:07' + | '16:07 - 18:00' + )[] = ['10:15 - 12:07', '12:07 - 14:07', '14:07 - 16:07', '16:07 - 18:00']; + + return ( + <> + + + + {t('time')} + {days.map((day) => ( + + {t(day)} + + ))} + + + + {times.map((time) => ( + + {time} + {days.map((day) => ( + + ))} + + ))} + +
+ + ); +} + +export { ScheduleTable }; diff --git a/src/lib/locale/index.ts b/src/lib/locale/index.ts index 8ffd34e..086a22e 100644 --- a/src/lib/locale/index.ts +++ b/src/lib/locale/index.ts @@ -37,5 +37,9 @@ export const routing = defineRouting({ en: '/storage/shopping-cart', no: '/lager/handlekurv', }, + '/shift-schedule': { + en: '/shift-schedule', + no: '/vaktliste', + }, }, }); diff --git a/src/mock-data/shiftSchedule.ts b/src/mock-data/shiftSchedule.ts new file mode 100644 index 0000000..e14bbd7 --- /dev/null +++ b/src/mock-data/shiftSchedule.ts @@ -0,0 +1,137 @@ +const shiftScheduleMochData = { + monday: { + '10:15 - 12:07': { + members: [ + { + name: 'En person', + skills: { + _3dPrinting: false, + laserCutting: false, + microcontrollers: false, + raspberryPi: true, + soldering: false, + terminal: true, + workshop: true, + }, + }, + { + name: 'En annen person', + skills: { + _3dPrinting: true, + laserCutting: true, + microcontrollers: true, + raspberryPi: false, + soldering: false, + terminal: true, + workshop: true, + }, + }, + { + name: 'Person 3', + skills: { + _3dPrinting: false, + laserCutting: false, + microcontrollers: false, + raspberryPi: false, + soldering: false, + terminal: false, + workshop: false, + }, + }, + ], + }, + '12:07 - 14:07': { + members: [ + { + name: 'Person 3', + skills: { + _3dPrinting: false, + laserCutting: true, + microcontrollers: false, + raspberryPi: true, + soldering: false, + terminal: true, + workshop: false, + }, + }, + ], + }, + '14:07 - 16:07': { + members: [], + }, + '16:07 - 18:00': { + members: [], + }, + }, + tuesday: { + '10:15 - 12:07': { + members: [], + }, + '12:07 - 14:07': { + members: [], + }, + '14:07 - 16:07': { + members: [], + }, + '16:07 - 18:00': { + members: [], + }, + }, + wednesday: { + '10:15 - 12:07': { + members: [], + }, + '12:07 - 14:07': { + members: [], + }, + '14:07 - 16:07': { + members: [], + }, + '16:07 - 18:00': { + members: [], + }, + }, + thursday: { + '10:15 - 12:07': { + members: [], + }, + '12:07 - 14:07': { + members: [], + }, + '14:07 - 16:07': { + members: [], + }, + '16:07 - 18:00': { + members: [], + }, + }, + friday: { + '10:15 - 12:07': { + members: [], + }, + '12:07 - 14:07': { + members: [ + { + name: 'En person', + skills: { + _3dPrinting: false, + laserCutting: false, + microcontrollers: false, + raspberryPi: false, + soldering: false, + terminal: false, + workshop: false, + }, + }, + ], + }, + '14:07 - 16:07': { + members: [], + }, + '16:07 - 18:00': { + members: [], + }, + }, +}; + +export { shiftScheduleMochData }; From 9bd2ccf3cb4aa2af792741c1ebd0b4d227675078 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Mon, 21 Oct 2024 13:12:23 +0200 Subject: [PATCH 02/22] feat: add recurring checkbox and sections to display skill icons --- bun.lockb | Bin 222872 -> 224640 bytes messages/en.json | 1 + messages/no.json | 1 + package.json | 1 + .../(default)/shift-schedule/layout.tsx | 4 +- .../(default)/shift-schedule/page.tsx | 3 +- .../shift-schedule/ScheduleCellContent.tsx | 2 +- .../shift-schedule/ScheduleCellDialog.tsx | 41 +++++++++++++----- .../shift-schedule/ScheduleTable.tsx | 7 ++- src/components/ui/Checkbox.tsx | 30 +++++++++++++ 10 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 src/components/ui/Checkbox.tsx diff --git a/bun.lockb b/bun.lockb index c4e9ff5eb6be3c0c986e277d897b23d3a6297df5..04aaff2b466a156ec4ac72b4c009520aec427214 100755 GIT binary patch delta 37639 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 j@@q8s8NP4NGx}sre0oorvilx -
-

{t('title')}

-
+

{t('title')}

{children} ); diff --git a/src/app/[locale]/(default)/shift-schedule/page.tsx b/src/app/[locale]/(default)/shift-schedule/page.tsx index acd7cc2..48918e5 100644 --- a/src/app/[locale]/(default)/shift-schedule/page.tsx +++ b/src/app/[locale]/(default)/shift-schedule/page.tsx @@ -26,7 +26,8 @@ export default function AboutPage({ return ( <>
- + +
[Skill icons + names]
); diff --git a/src/components/shift-schedule/ScheduleCellContent.tsx b/src/components/shift-schedule/ScheduleCellContent.tsx index ec0ffdd..c70d0b2 100644 --- a/src/components/shift-schedule/ScheduleCellContent.tsx +++ b/src/components/shift-schedule/ScheduleCellContent.tsx @@ -47,7 +47,7 @@ function ScheduleCellContent({ members }: ScheduleCellProps) { )} > {membersDisplay} -
[skill icons]
+
[skill icons total]
diff --git a/src/components/shift-schedule/ScheduleCellDialog.tsx b/src/components/shift-schedule/ScheduleCellDialog.tsx index fbb6b45..f6f9826 100644 --- a/src/components/shift-schedule/ScheduleCellDialog.tsx +++ b/src/components/shift-schedule/ScheduleCellDialog.tsx @@ -1,16 +1,35 @@ import type { ScheduleCellProps } from '@/components/shift-schedule/ScheduleTable'; import { Button } from '@/components/ui/Button'; +import { Checkbox } from '@/components/ui/Checkbox'; import { DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/Dialog'; +import { Label } from '@/components/ui/Label'; import { useTranslations } from 'next-intl'; function ScheduleCellDialog({ members }: ScheduleCellProps) { const t = useTranslations('shiftSchedule.scheduleTable.scheduleCellDialog'); + let membersDisplay: React.ReactNode; + + if (members.length === 0) { + membersDisplay =

{t('empty')}

; + } else { + membersDisplay = ( + <> + {members.map((member) => ( +
+

{member.name}

+
[skill icons]
+
+ ))} + + ); + } + return ( <> @@ -18,16 +37,18 @@ function ScheduleCellDialog({ members }: ScheduleCellProps) { {t('onShift')} -
-
- {members.map((member) => ( -

- {member.name} -

- ))} -
- -
+
+
+ {membersDisplay} +
+
+
+ + +
+ +
+
); } diff --git a/src/components/shift-schedule/ScheduleTable.tsx b/src/components/shift-schedule/ScheduleTable.tsx index bef3894..c3d37ce 100644 --- a/src/components/shift-schedule/ScheduleTable.tsx +++ b/src/components/shift-schedule/ScheduleTable.tsx @@ -2,6 +2,7 @@ import { ScheduleCell } from '@/components/shift-schedule/ScheduleCell'; import { Table, TableBody, + TableCaption, TableCell, TableHead, TableHeader, @@ -39,9 +40,10 @@ type ScheduleTableProps = { thursday: ScheduleDayProps; friday: ScheduleDayProps; }; + className?: string; }; -function ScheduleTable({ week }: ScheduleTableProps) { +function ScheduleTable({ week, className }: ScheduleTableProps) { const t = useTranslations('shiftSchedule.scheduleTable'); // Cannot use translation unless days and times are of these types const days: ('monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday')[] = [ @@ -60,7 +62,7 @@ function ScheduleTable({ week }: ScheduleTableProps) { return ( <> - +
{t('time')} @@ -81,6 +83,7 @@ function ScheduleTable({ week }: ScheduleTableProps) { ))} + [skill icons legend]
); diff --git a/src/components/ui/Checkbox.tsx b/src/components/ui/Checkbox.tsx new file mode 100644 index 0000000..8159c24 --- /dev/null +++ b/src/components/ui/Checkbox.tsx @@ -0,0 +1,30 @@ +'use client'; + +import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; +import { Check } from 'lucide-react'; +import * as React from 'react'; + +import { cx } from '@/lib/utils'; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; From 7b27078f1805a7ed8495472d6bab66bcb1e5944b Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Mon, 21 Oct 2024 13:43:25 +0200 Subject: [PATCH 03/22] feat: move register section to component and improve styling --- messages/en.json | 5 +++- messages/no.json | 5 +++- .../(default)/shift-schedule/page.tsx | 5 +--- .../shift-schedule/RegisterSection.tsx | 24 +++++++++++++++++++ .../shift-schedule/ScheduleCellDialog.tsx | 18 ++++---------- 5 files changed, 37 insertions(+), 20 deletions(-) create mode 100644 src/components/shift-schedule/RegisterSection.tsx diff --git a/messages/en.json b/messages/en.json index 5dd7532..a51cab2 100644 --- a/messages/en.json +++ b/messages/en.json @@ -143,7 +143,10 @@ "scheduleCellDialog": { "onShift": "On shift", "empty": "No-one on shift", - "register": "Register" + "registerSection": { + "recurring": "Recurring", + "register": "Registrer" + } } } } diff --git a/messages/no.json b/messages/no.json index c050575..da57dbd 100644 --- a/messages/no.json +++ b/messages/no.json @@ -143,7 +143,10 @@ "scheduleCellDialog": { "onShift": "På vakt", "empty": "Ingen på vakt", - "register": "Registrer" + "registerSection": { + "recurring": "Gjentagende", + "register": "Registrer" + } } } } diff --git a/src/app/[locale]/(default)/shift-schedule/page.tsx b/src/app/[locale]/(default)/shift-schedule/page.tsx index 48918e5..aaf4f98 100644 --- a/src/app/[locale]/(default)/shift-schedule/page.tsx +++ b/src/app/[locale]/(default)/shift-schedule/page.tsx @@ -25,10 +25,7 @@ export default function AboutPage({ return ( <> -
- -
[Skill icons + names]
-
+ ); } diff --git a/src/components/shift-schedule/RegisterSection.tsx b/src/components/shift-schedule/RegisterSection.tsx new file mode 100644 index 0000000..eb98101 --- /dev/null +++ b/src/components/shift-schedule/RegisterSection.tsx @@ -0,0 +1,24 @@ +import { useTranslations } from 'next-intl'; +import { Button } from '../ui/Button'; +import { Checkbox } from '../ui/Checkbox'; +import { Label } from '../ui/Label'; + +function RegisterSection({ className }: { className?: string }) { + const t = useTranslations( + 'shiftSchedule.scheduleTable.scheduleCellDialog.registerSection', + ); + + return ( +
+
+ + +
+ +
+ ); +} + +export { RegisterSection }; diff --git a/src/components/shift-schedule/ScheduleCellDialog.tsx b/src/components/shift-schedule/ScheduleCellDialog.tsx index f6f9826..a037234 100644 --- a/src/components/shift-schedule/ScheduleCellDialog.tsx +++ b/src/components/shift-schedule/ScheduleCellDialog.tsx @@ -1,14 +1,10 @@ import type { ScheduleCellProps } from '@/components/shift-schedule/ScheduleTable'; import { Button } from '@/components/ui/Button'; import { Checkbox } from '@/components/ui/Checkbox'; -import { - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/Dialog'; +import { DialogHeader, DialogTitle } from '@/components/ui/Dialog'; import { Label } from '@/components/ui/Label'; import { useTranslations } from 'next-intl'; +import { RegisterSection } from './RegisterSection'; function ScheduleCellDialog({ members }: ScheduleCellProps) { const t = useTranslations('shiftSchedule.scheduleTable.scheduleCellDialog'); @@ -37,17 +33,11 @@ function ScheduleCellDialog({ members }: ScheduleCellProps) { {t('onShift')} -
+
{membersDisplay}
-
-
- - -
- -
+
); From 588ed0a5993ee378ebeed74f4535fb489c4119d2 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Thu, 24 Oct 2024 21:34:09 +0200 Subject: [PATCH 04/22] feat: make table scale for smaller screens and improve styling --- messages/en.json | 2 +- messages/no.json | 2 +- .../shift-schedule/RegisterSection.tsx | 4 +- .../shift-schedule/ScheduleCellContent.tsx | 27 +++++++------ .../shift-schedule/ScheduleCellDialog.tsx | 3 -- .../shift-schedule/ScheduleTable.tsx | 38 ++++++++++++++++--- 6 files changed, 51 insertions(+), 25 deletions(-) diff --git a/messages/en.json b/messages/en.json index a51cab2..63a6a1a 100644 --- a/messages/en.json +++ b/messages/en.json @@ -135,7 +135,7 @@ "thursday": "Thursday", "friday": "Friday", "scheduleCellContent": { - "empty": "Empty", + "empty": "Closed", "member": "person", "members": "people", "present": "on shift" diff --git a/messages/no.json b/messages/no.json index da57dbd..e1b44fe 100644 --- a/messages/no.json +++ b/messages/no.json @@ -135,7 +135,7 @@ "thursday": "Torsdag", "friday": "Fredag", "scheduleCellContent": { - "empty": "Tomt", + "empty": "Lukket", "member": "person", "members": "personer", "present": "på vakt" diff --git a/src/components/shift-schedule/RegisterSection.tsx b/src/components/shift-schedule/RegisterSection.tsx index eb98101..06d301b 100644 --- a/src/components/shift-schedule/RegisterSection.tsx +++ b/src/components/shift-schedule/RegisterSection.tsx @@ -14,9 +14,7 @@ function RegisterSection({ className }: { className?: string }) {
- + ); } diff --git a/src/components/shift-schedule/ScheduleCellContent.tsx b/src/components/shift-schedule/ScheduleCellContent.tsx index c70d0b2..8f78390 100644 --- a/src/components/shift-schedule/ScheduleCellContent.tsx +++ b/src/components/shift-schedule/ScheduleCellContent.tsx @@ -10,8 +10,9 @@ function ScheduleCellContent({ members }: ScheduleCellProps) { const colorStyle = members.length === 0 - ? 'bg-destructive text-destructive-foreground' - : 'bg-primary text-primary-foreground'; + ? 'bg-accent/50 hover:bg-accent dark:bg-accent/40 dark:hover:bg-accent/60 text-accent-foreground' + : 'bg-foreground/20 hover:bg-foreground/25'; + let membersDisplay: React.ReactNode; const membersDisplayStyle = 'flex align-bottom space-x-1 space-y-0'; @@ -21,7 +22,7 @@ function ScheduleCellContent({ members }: ScheduleCellProps) { membersDisplay = (
-

+

1 {t('member')} {t('present')}

@@ -37,17 +38,21 @@ function ScheduleCellContent({ members }: ScheduleCellProps) { ); } + let iconsDisplay: React.ReactNode; + + if (members.length === 0) { + } else { + iconsDisplay = ( +
[skill icons total]
+ ); + } + return ( <> - -
+ +
{membersDisplay} -
[skill icons total]
+ {iconsDisplay}
diff --git a/src/components/shift-schedule/ScheduleCellDialog.tsx b/src/components/shift-schedule/ScheduleCellDialog.tsx index a037234..fb47eb7 100644 --- a/src/components/shift-schedule/ScheduleCellDialog.tsx +++ b/src/components/shift-schedule/ScheduleCellDialog.tsx @@ -1,8 +1,5 @@ import type { ScheduleCellProps } from '@/components/shift-schedule/ScheduleTable'; -import { Button } from '@/components/ui/Button'; -import { Checkbox } from '@/components/ui/Checkbox'; import { DialogHeader, DialogTitle } from '@/components/ui/Dialog'; -import { Label } from '@/components/ui/Label'; import { useTranslations } from 'next-intl'; import { RegisterSection } from './RegisterSection'; diff --git a/src/components/shift-schedule/ScheduleTable.tsx b/src/components/shift-schedule/ScheduleTable.tsx index c3d37ce..7f98953 100644 --- a/src/components/shift-schedule/ScheduleTable.tsx +++ b/src/components/shift-schedule/ScheduleTable.tsx @@ -8,7 +8,8 @@ import { TableHeader, TableRow, } from '@/components/ui/Table'; -import { MessageKeys, useTranslations } from 'next-intl'; +import { cx } from '@/lib/utils'; +import { useTranslations } from 'next-intl'; export type ScheduleCellProps = { members: { @@ -40,10 +41,9 @@ type ScheduleTableProps = { thursday: ScheduleDayProps; friday: ScheduleDayProps; }; - className?: string; }; -function ScheduleTable({ week, className }: ScheduleTableProps) { +function ScheduleTable({ week }: ScheduleTableProps) { const t = useTranslations('shiftSchedule.scheduleTable'); // Cannot use translation unless days and times are of these types const days: ('monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday')[] = [ @@ -62,7 +62,33 @@ function ScheduleTable({ week, className }: ScheduleTableProps) { return ( <> - + {/* Table shown on small screens */} +
+ {days.map((day, index) => ( +
+ + + {t('time')} + {t(day)} + + + + {times.map((time) => ( + + {time} + + + ))} + +
+ ))} + + [skill icons legend] +
+
+ + {/* Table shown on all other screens */} + {t('time')} @@ -76,14 +102,14 @@ function ScheduleTable({ week, className }: ScheduleTableProps) { {times.map((time) => ( - {time} + {time} {days.map((day) => ( ))} ))} - [skill icons legend] + [skill icons legend]
); From cdae953b70be51154a1ca281c9f8ca97ff70ebd7 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Fri, 25 Oct 2024 13:23:17 +0200 Subject: [PATCH 05/22] refactor: improve scaling on small screens --- messages/en.json | 2 +- .../shift-schedule/ScheduleCell.tsx | 2 +- .../shift-schedule/ScheduleCellContent.tsx | 72 +++++++----- .../shift-schedule/ScheduleCellDialog.tsx | 12 +- .../shift-schedule/ScheduleTable.tsx | 9 -- src/mock-data/shiftSchedule.ts | 107 +++++------------- 6 files changed, 76 insertions(+), 128 deletions(-) diff --git a/messages/en.json b/messages/en.json index 63a6a1a..f2831ce 100644 --- a/messages/en.json +++ b/messages/en.json @@ -145,7 +145,7 @@ "empty": "No-one on shift", "registerSection": { "recurring": "Recurring", - "register": "Registrer" + "register": "Register" } } } diff --git a/src/components/shift-schedule/ScheduleCell.tsx b/src/components/shift-schedule/ScheduleCell.tsx index 18728cd..c91567e 100644 --- a/src/components/shift-schedule/ScheduleCell.tsx +++ b/src/components/shift-schedule/ScheduleCell.tsx @@ -11,7 +11,7 @@ function ScheduleCell({ members }: ScheduleCellProps) { - + diff --git a/src/components/shift-schedule/ScheduleCellContent.tsx b/src/components/shift-schedule/ScheduleCellContent.tsx index 8f78390..4fc3250 100644 --- a/src/components/shift-schedule/ScheduleCellContent.tsx +++ b/src/components/shift-schedule/ScheduleCellContent.tsx @@ -13,46 +13,56 @@ function ScheduleCellContent({ members }: ScheduleCellProps) { ? 'bg-accent/50 hover:bg-accent dark:bg-accent/40 dark:hover:bg-accent/60 text-accent-foreground' : 'bg-foreground/20 hover:bg-foreground/25'; - let membersDisplay: React.ReactNode; - const membersDisplayStyle = 'flex align-bottom space-x-1 space-y-0'; - - if (members.length === 0) { - membersDisplay =

{t('empty')}

; - } else if (members.length === 1) { - membersDisplay = ( -
- -

- 1 {t('member')} {t('present')} -

-
- ); - } else { - membersDisplay = ( -
- -

- {members.length} {t('members')} {t('present')} -

-
- ); - } - - let iconsDisplay: React.ReactNode; + let memberCountIcon: React.ReactNode; + const memberCountIconStyle = 'w-6 h-6'; + let memberCount: React.ReactNode; + const memberCountStyle = 'flex align-bottom space-x-1 space-y-0'; + let skillIcons: React.ReactNode; + // Set member count icon, member count, and skill icons based on amount of members present if (members.length === 0) { + // Empty shift + memberCount =

{t('empty')}

; } else { - iconsDisplay = ( + // At least 1 person on shift + skillIcons = (
[skill icons total]
); + + if (members.length === 1) { + // 1 person on shit + memberCountIcon = ; + memberCount = ( +
+

+ 1 {t('member')} {t('present')} +

+
+ ); + } else { + // 2 or more people on shift + memberCountIcon = ; + memberCount = ( +
+

+ {members.length} {t('members')} {t('present')} +

+
+ ); + } } return ( <> - -
); } return ( <> - - {messages.day} - {messages.time} + + {tDialog.day} + {tDialog.time} -
+
{membersDisplay} -
+ ); } diff --git a/src/components/shift-schedule/ScheduleCellSkeleton.tsx b/src/components/shift-schedule/ScheduleCellSkeleton.tsx deleted file mode 100644 index f589c93..0000000 --- a/src/components/shift-schedule/ScheduleCellSkeleton.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Skeleton } from '@/components/ui/Skeleton'; -import { TableCell } from '@/components/ui/Table'; -import type React from 'react'; - -function ScheduleCellSkeleton() { - return ( - - - - ); -} - -export { ScheduleCellSkeleton }; diff --git a/src/components/shift-schedule/ScheduleTable.tsx b/src/components/shift-schedule/ScheduleTable.tsx index 48e5abd..f19cacd 100644 --- a/src/components/shift-schedule/ScheduleTable.tsx +++ b/src/components/shift-schedule/ScheduleTable.tsx @@ -8,7 +8,9 @@ import { TableHeader, TableRow, } from '@/components/ui/Table'; -import { useTranslations } from 'next-intl'; +import { getTime, toDate } from 'date-fns'; +import { TimeSpan } from 'lucia'; +import { useFormatter, useTranslations } from 'next-intl'; export type ScheduleEntryProps = { members: { @@ -35,7 +37,8 @@ type ScheduleTableProps = { function ScheduleTable({ week }: ScheduleTableProps) { const t = useTranslations('shiftSchedule.scheduleTable'); - // Cannot use translation unless days and times are of these types + const format = useFormatter(); + const days = [ 'monday', 'tuesday', @@ -59,7 +62,9 @@ function ScheduleTable({ week }: ScheduleTableProps) { {t('time')} - {t(day)} + + {t('day', { day: day })} + @@ -67,8 +72,8 @@ function ScheduleTable({ week }: ScheduleTableProps) { {time} {t('time')} {days.map((day) => ( - {t(day)} + {t('day', { day: day })} ))} @@ -102,8 +107,8 @@ function ScheduleTable({ week }: ScheduleTableProps) { {days.map((day) => ( Date: Thu, 31 Oct 2024 19:30:23 +0100 Subject: [PATCH 18/22] refactor: change display of timeslot based on language --- messages/no.json | 8 +- .../(default)/shift-schedule/loading.tsx | 76 ++++++++++++++---- .../shift-schedule/ScheduleTable.tsx | 80 +++++++++++++------ src/mock-data/shiftSchedule.ts | 40 +++++----- 4 files changed, 140 insertions(+), 64 deletions(-) diff --git a/messages/no.json b/messages/no.json index dba8487..8062933 100644 --- a/messages/no.json +++ b/messages/no.json @@ -144,12 +144,8 @@ }, "scheduleTable": { "time": "Tid", - "monday": "Mandag", - "tuesday": "Tirsdag", - "wednesday": "Onsdag", - "thursday": "Torsdag", - "friday": "Fredag", - "scheduleCellContent": { + "day": "{day, select, monday {Mandag} tuesday {Tirsdag} wednesday {Onsdag} thursday {Torsdag} other {Fredag}}", + "scheduleCell": { "onShift": "{count, plural, =0 {Stengt} =1 {1 person på vakt} other {# personer på vakt}}", "scheduleCellDialog": { "empty": "Ingen på vakt", diff --git a/src/app/[locale]/(default)/shift-schedule/loading.tsx b/src/app/[locale]/(default)/shift-schedule/loading.tsx index e977e5e..46f0d57 100644 --- a/src/app/[locale]/(default)/shift-schedule/loading.tsx +++ b/src/app/[locale]/(default)/shift-schedule/loading.tsx @@ -8,10 +8,11 @@ import { TableHeader, TableRow, } from '@/components/ui/Table'; -import { useTranslations } from 'next-intl'; +import { useFormatter, useTranslations } from 'next-intl'; export default function ShiftScheduleLayout() { const t = useTranslations('shiftSchedule.scheduleTable'); + const format = useFormatter(); const days = [ 'monday', @@ -20,13 +21,56 @@ export default function ShiftScheduleLayout() { 'thursday', 'friday', ] as const; - const times = [ - '10:15 - 12:07', - '12:07 - 14:07', - '14:07 - 16:07', - '16:07 - 18:00', + const timeslots = [ + 'first', + 'second', + 'third', + 'fourth', ] as const; + function getDateTimeRange(timeslot: string) { + let firstDate: Date; + let secondDate: Date; + + switch (timeslot) { + case timeslots[0]: + firstDate = new Date(0, 0, 0, 10, 15, 0, 0); + secondDate = new Date(0, 0, 0, 12, 7, 0, 0); + break; + + case timeslots[1]: + firstDate = new Date(0, 0, 0, 12, 7, 0, 0); + secondDate = new Date(0, 0, 0, 14, 7, 0, 0); + break; + + case timeslots[2]: + firstDate = new Date(0, 0, 0, 14, 7, 0, 0); + secondDate = new Date(0, 0, 0, 16, 7, 0, 0); + break; + + case timeslots[3]: + firstDate = new Date(0, 0, 0, 16, 7, 0, 0); + secondDate = new Date(0, 0, 0, 18, 0, 0, 0); + break; + + default: + firstDate = new Date(); + secondDate = new Date(); + } + + return ( + format.dateTimeRange( + firstDate, + secondDate, + { + hour: '2-digit', + minute: '2-digit', + hour12: false + } + ) + ) + } + return ( <> {/* Table shown on small screens */} @@ -36,14 +80,16 @@ export default function ShiftScheduleLayout() { {t('time')} - {t('day', { day: day })} + + {t('day', { day: day })} + - {times.map((time) => ( - - {time} - + {timeslots.map((timeslot) => ( + + {getDateTimeRange(timeslot)} + @@ -69,9 +115,9 @@ export default function ShiftScheduleLayout() { - {times.map((time) => ( - - {time} + {timeslots.map((timeslot) => ( + + {getDateTimeRange(timeslot)} {days.map((day) => ( @@ -84,4 +130,4 @@ export default function ShiftScheduleLayout() { ); -} \ No newline at end of file +} diff --git a/src/components/shift-schedule/ScheduleTable.tsx b/src/components/shift-schedule/ScheduleTable.tsx index f19cacd..e26996c 100644 --- a/src/components/shift-schedule/ScheduleTable.tsx +++ b/src/components/shift-schedule/ScheduleTable.tsx @@ -8,21 +8,19 @@ import { TableHeader, TableRow, } from '@/components/ui/Table'; -import { getTime, toDate } from 'date-fns'; -import { TimeSpan } from 'lucia'; import { useFormatter, useTranslations } from 'next-intl'; -export type ScheduleEntryProps = { +type ScheduleEntryProps = { members: { name: string; }[]; }; type ScheduleDayProps = { - '10:15 - 12:07': ScheduleEntryProps; - '12:07 - 14:07': ScheduleEntryProps; - '14:07 - 16:07': ScheduleEntryProps; - '16:07 - 18:00': ScheduleEntryProps; + first: ScheduleEntryProps; + second: ScheduleEntryProps; + third: ScheduleEntryProps; + fourth: ScheduleEntryProps; }; type ScheduleTableProps = { @@ -46,12 +44,44 @@ function ScheduleTable({ week }: ScheduleTableProps) { 'thursday', 'friday', ] as const; - const times = [ - '10:15 - 12:07', - '12:07 - 14:07', - '14:07 - 16:07', - '16:07 - 18:00', - ] as const; + const timeslots = ['first', 'second', 'third', 'fourth'] as const; + + function getDateTimeRange(timeslot: string) { + let firstDate: Date; + let secondDate: Date; + + switch (timeslot) { + case timeslots[0]: + firstDate = new Date(0, 0, 0, 10, 15, 0, 0); + secondDate = new Date(0, 0, 0, 12, 7, 0, 0); + break; + + case timeslots[1]: + firstDate = new Date(0, 0, 0, 12, 7, 0, 0); + secondDate = new Date(0, 0, 0, 14, 7, 0, 0); + break; + + case timeslots[2]: + firstDate = new Date(0, 0, 0, 14, 7, 0, 0); + secondDate = new Date(0, 0, 0, 16, 7, 0, 0); + break; + + case timeslots[3]: + firstDate = new Date(0, 0, 0, 16, 7, 0, 0); + secondDate = new Date(0, 0, 0, 18, 0, 0, 0); + break; + + default: + firstDate = new Date(); + secondDate = new Date(); + } + + return format.dateTimeRange(firstDate, secondDate, { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }); + } return ( <> @@ -68,15 +98,17 @@ function ScheduleTable({ week }: ScheduleTableProps) { - {times.map((time) => ( - - {time} + {timeslots.map((timeslot) => ( + + + {getDateTimeRange(timeslot)} + ))} @@ -101,17 +133,19 @@ function ScheduleTable({ week }: ScheduleTableProps) { - {times.map((time) => ( - - {time} + {timeslots.map((timeslot) => ( + + + {getDateTimeRange(timeslot)} + {days.map((day) => ( ))} diff --git a/src/mock-data/shiftSchedule.ts b/src/mock-data/shiftSchedule.ts index 818b83d..d6090d6 100644 --- a/src/mock-data/shiftSchedule.ts +++ b/src/mock-data/shiftSchedule.ts @@ -1,77 +1,77 @@ const shiftScheduleMockData = { monday: { - '10:15 - 12:07': { + first: { members: [{ name: 'En person' }], }, - '12:07 - 14:07': { + second: { members: [{ name: 'En person' }], }, - '14:07 - 16:07': { + third: { members: [ { name: 'En person' }, { name: 'En annen person' }, { name: 'Person 3' }, ], }, - '16:07 - 18:00': { + fourth: { members: [{ name: 'En person' }], }, }, tuesday: { - '10:15 - 12:07': { + first: { members: [{ name: 'En person' }], }, - '12:07 - 14:07': { + second: { members: [{ name: 'En person' }], }, - '14:07 - 16:07': { + third: { members: [ { name: 'En person' }, { name: 'En annen person' }, { name: 'Person 3' }, ], }, - '16:07 - 18:00': { + fourth: { members: [{ name: 'En person' }], }, }, wednesday: { - '10:15 - 12:07': { + first: { members: [{ name: 'En person' }, { name: 'En annen person' }], }, - '12:07 - 14:07': { + second: { members: [], }, - '14:07 - 16:07': { + third: { members: [{ name: 'En person' }, { name: 'En annen person' }], }, - '16:07 - 18:00': { + fourth: { members: [{ name: 'En person' }, { name: 'En annen person' }], }, }, thursday: { - '10:15 - 12:07': { + first: { members: [{ name: 'En person' }], }, - '12:07 - 14:07': { + second: { members: [ { name: 'En person' }, { name: 'En annen person' }, { name: 'Person 3' }, ], }, - '14:07 - 16:07': { + third: { members: [], }, - '16:07 - 18:00': { + fourth: { members: [], }, }, friday: { - '10:15 - 12:07': { + first: { members: [], }, - '12:07 - 14:07': { + second: { members: [ { name: 'En person med veldig langt navn så jeg kan teste navn som bruker to linjer', @@ -79,10 +79,10 @@ const shiftScheduleMockData = { { name: 'En annen person' }, ], }, - '14:07 - 16:07': { + third: { members: [{ name: 'En person' }], }, - '16:07 - 18:00': { + fourth: { members: [{ name: 'En person' }], }, }, From 07a57ce9bbfb57561c88bfce90506467df843bdc Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Thu, 31 Oct 2024 19:54:49 +0100 Subject: [PATCH 19/22] fix: fix lighthouse error --- .../(default)/shift-schedule/loading.tsx | 33 ++++++++----------- .../shift-schedule/ScheduleCell.tsx | 13 ++++---- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/app/[locale]/(default)/shift-schedule/loading.tsx b/src/app/[locale]/(default)/shift-schedule/loading.tsx index 46f0d57..2dce3c0 100644 --- a/src/app/[locale]/(default)/shift-schedule/loading.tsx +++ b/src/app/[locale]/(default)/shift-schedule/loading.tsx @@ -21,17 +21,12 @@ export default function ShiftScheduleLayout() { 'thursday', 'friday', ] as const; - const timeslots = [ - 'first', - 'second', - 'third', - 'fourth', - ] as const; + const timeslots = ['first', 'second', 'third', 'fourth'] as const; function getDateTimeRange(timeslot: string) { let firstDate: Date; let secondDate: Date; - + switch (timeslot) { case timeslots[0]: firstDate = new Date(0, 0, 0, 10, 15, 0, 0); @@ -58,17 +53,11 @@ export default function ShiftScheduleLayout() { secondDate = new Date(); } - return ( - format.dateTimeRange( - firstDate, - secondDate, - { - hour: '2-digit', - minute: '2-digit', - hour12: false - } - ) - ) + return format.dateTimeRange(firstDate, secondDate, { + hour: '2-digit', + minute: '2-digit', + hour12: false, + }); } return ( @@ -88,7 +77,9 @@ export default function ShiftScheduleLayout() { {timeslots.map((timeslot) => ( - {getDateTimeRange(timeslot)} + + {getDateTimeRange(timeslot)} + @@ -117,7 +108,9 @@ export default function ShiftScheduleLayout() { {timeslots.map((timeslot) => ( - {getDateTimeRange(timeslot)} + + {getDateTimeRange(timeslot)} + {days.map((day) => ( diff --git a/src/components/shift-schedule/ScheduleCell.tsx b/src/components/shift-schedule/ScheduleCell.tsx index 15ba537..e00401f 100644 --- a/src/components/shift-schedule/ScheduleCell.tsx +++ b/src/components/shift-schedule/ScheduleCell.tsx @@ -3,7 +3,7 @@ import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/Dialog'; import { TableCell } from '@/components/ui/Table'; import { cx } from '@/lib/utils'; import { UserIcon, UsersIcon } from 'lucide-react'; -import { useFormatter, useTranslations } from 'next-intl'; +import { useTranslations } from 'next-intl'; type ScheduleCellProps = { tDialog: { @@ -22,9 +22,10 @@ function ScheduleCell({ tDialog, members }: ScheduleCellProps) { -
)} -
+
{/* Amount of people on shift */} {t('onShift', { count: members.length })} {/* Skill icons */} {members.length === 0 ? ( <> ) : ( -
[skill icons total]
+ [skill icons total] )}
-
+ From 8f70617f9684b5491f509ca78c80f232afb1f329 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Mon, 4 Nov 2024 12:07:50 +0100 Subject: [PATCH 20/22] refactor: add changes requested in pr --- messages/en.json | 2 +- ...{RegisterSection.tsx => RegisterShift.tsx} | 4 +-- .../shift-schedule/ScheduleCell.tsx | 8 ++--- .../shift-schedule/ScheduleCellDialog.tsx | 34 ++++++++----------- 4 files changed, 19 insertions(+), 29 deletions(-) rename src/components/shift-schedule/{RegisterSection.tsx => RegisterShift.tsx} (87%) diff --git a/messages/en.json b/messages/en.json index 5591d57..f9b2767 100644 --- a/messages/en.json +++ b/messages/en.json @@ -148,7 +148,7 @@ "scheduleCell": { "onShift": "{count, plural, =0 {Closed} =1 {1 person on shift} other {# people on shift}}", "scheduleCellDialog": { - "empty": "No-one on shift", + "empty": "No one on shift", "registerSection": { "recurring": "Recurring", "register": "Register" diff --git a/src/components/shift-schedule/RegisterSection.tsx b/src/components/shift-schedule/RegisterShift.tsx similarity index 87% rename from src/components/shift-schedule/RegisterSection.tsx rename to src/components/shift-schedule/RegisterShift.tsx index 5b6fd0a..801dc2f 100644 --- a/src/components/shift-schedule/RegisterSection.tsx +++ b/src/components/shift-schedule/RegisterShift.tsx @@ -4,7 +4,7 @@ import { Label } from '@/components/ui/Label'; import { cx } from '@/lib/utils'; import { useTranslations } from 'next-intl'; -function RegisterSection({ className }: { className?: string }) { +function RegisterShift({ className }: { className?: string }) { const t = useTranslations( 'shiftSchedule.scheduleTable.scheduleCell.scheduleCellDialog.registerSection', ); @@ -20,4 +20,4 @@ function RegisterSection({ className }: { className?: string }) { ); } -export { RegisterSection }; +export { RegisterShift }; diff --git a/src/components/shift-schedule/ScheduleCell.tsx b/src/components/shift-schedule/ScheduleCell.tsx index e00401f..8cd360e 100644 --- a/src/components/shift-schedule/ScheduleCell.tsx +++ b/src/components/shift-schedule/ScheduleCell.tsx @@ -34,18 +34,14 @@ function ScheduleCell({ tDialog, members }: ScheduleCellProps) { {/* Icon displaying amount of people on shift */} {members.length === 1 ? ( - ) : members.length > 1 ? ( - ) : ( - <> + members.length > 1 && )}
{/* Amount of people on shift */} {t('onShift', { count: members.length })} {/* Skill icons */} - {members.length === 0 ? ( - <> - ) : ( + {members.length !== 0 && ( [skill icons total] )}
diff --git a/src/components/shift-schedule/ScheduleCellDialog.tsx b/src/components/shift-schedule/ScheduleCellDialog.tsx index 053ce90..248d3bf 100644 --- a/src/components/shift-schedule/ScheduleCellDialog.tsx +++ b/src/components/shift-schedule/ScheduleCellDialog.tsx @@ -1,4 +1,4 @@ -import { RegisterSection } from '@/components/shift-schedule/RegisterSection'; +import { RegisterShift } from '@/components/shift-schedule/RegisterShift'; import { DialogHeader, DialogTitle } from '@/components/ui/Dialog'; import { useTranslations } from 'next-intl'; @@ -17,23 +17,6 @@ function ScheduleCellDialog({ tDialog, members }: ScheduleCellDialogProps) { 'shiftSchedule.scheduleTable.scheduleCell.scheduleCellDialog', ); - let membersDisplay: React.ReactNode; - - if (members.length === 0) { - membersDisplay =

{t('empty')}

; - } else { - membersDisplay = ( -
- {members.map((member) => ( -
-

{member.name}

-
[skill icons]
-
- ))} -
- ); - } - return ( <> @@ -43,8 +26,19 @@ function ScheduleCellDialog({ tDialog, members }: ScheduleCellDialogProps) {
- {membersDisplay} - + {members.length === 0 ? ( +

{t('empty')}

+ ) : ( +
+ {members.map((member) => ( +
+

{member.name}

+
[skill icons]
+
+ ))} +
+ )} +
); From 0118dd1738c690524e1f041dfb82b1999ccc8438 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Thu, 7 Nov 2024 11:15:44 +0100 Subject: [PATCH 21/22] refactor: change table spacing to use classname instead of index --- src/app/[locale]/(default)/shift-schedule/layout.tsx | 2 +- src/app/[locale]/(default)/shift-schedule/loading.tsx | 8 ++++---- src/components/shift-schedule/AdministratorMenu.tsx | 2 +- src/components/shift-schedule/ScheduleTable.tsx | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/app/[locale]/(default)/shift-schedule/layout.tsx b/src/app/[locale]/(default)/shift-schedule/layout.tsx index 7425732..8aa4078 100644 --- a/src/app/[locale]/(default)/shift-schedule/layout.tsx +++ b/src/app/[locale]/(default)/shift-schedule/layout.tsx @@ -16,7 +16,7 @@ export default async function ShiftScheduleLayout({ return ( <> -

{t('title')}

+

{t('title')}

{children} ); diff --git a/src/app/[locale]/(default)/shift-schedule/loading.tsx b/src/app/[locale]/(default)/shift-schedule/loading.tsx index 2dce3c0..7086b1d 100644 --- a/src/app/[locale]/(default)/shift-schedule/loading.tsx +++ b/src/app/[locale]/(default)/shift-schedule/loading.tsx @@ -63,9 +63,9 @@ export default function ShiftScheduleLayout() { return ( <> {/* Table shown on small screens */} -
- {days.map((day, index) => ( - +
+ {days.map((day) => ( +
{t('time')} @@ -94,7 +94,7 @@ export default function ShiftScheduleLayout() { {/* Table shown on all other screens */} -
+
{t('time')} diff --git a/src/components/shift-schedule/AdministratorMenu.tsx b/src/components/shift-schedule/AdministratorMenu.tsx index 6037a5f..6b05a89 100644 --- a/src/components/shift-schedule/AdministratorMenu.tsx +++ b/src/components/shift-schedule/AdministratorMenu.tsx @@ -25,7 +25,7 @@ function AdministratorMenu({ t }: AdministratorMenuProps) {
{t.label} diff --git a/src/components/shift-schedule/ScheduleTable.tsx b/src/components/shift-schedule/ScheduleTable.tsx index e26996c..a438008 100644 --- a/src/components/shift-schedule/ScheduleTable.tsx +++ b/src/components/shift-schedule/ScheduleTable.tsx @@ -86,9 +86,9 @@ function ScheduleTable({ week }: ScheduleTableProps) { return ( <> {/* Table shown on small screens */} -
- {days.map((day, index) => ( -
+
+ {days.map((day) => ( +
{t('time')} @@ -121,7 +121,7 @@ function ScheduleTable({ week }: ScheduleTableProps) { {/* Table shown on all other screens */} -
+
{t('time')} From 08d07052c4df071e2efdefdb3fa23fcad3ee34e7 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Thu, 7 Nov 2024 17:42:02 +0100 Subject: [PATCH 22/22] refactor: move aria label and remove unnecessary span --- src/components/shift-schedule/AdministratorMenu.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/shift-schedule/AdministratorMenu.tsx b/src/components/shift-schedule/AdministratorMenu.tsx index 6b05a89..3548912 100644 --- a/src/components/shift-schedule/AdministratorMenu.tsx +++ b/src/components/shift-schedule/AdministratorMenu.tsx @@ -30,11 +30,11 @@ function AdministratorMenu({ t }: AdministratorMenuProps) {
{t.label} - @@ -42,7 +42,7 @@ function AdministratorMenu({ t }: AdministratorMenuProps) {
- {membersDisplay} - {iconsDisplay} + +
+ {memberCountIcon} +
+ {memberCount} + {skillIcons} +
diff --git a/src/components/shift-schedule/ScheduleCellDialog.tsx b/src/components/shift-schedule/ScheduleCellDialog.tsx index fb47eb7..adac36c 100644 --- a/src/components/shift-schedule/ScheduleCellDialog.tsx +++ b/src/components/shift-schedule/ScheduleCellDialog.tsx @@ -14,9 +14,9 @@ function ScheduleCellDialog({ members }: ScheduleCellProps) { membersDisplay = ( <> {members.map((member) => ( -
+

{member.name}

-
[skill icons]
+
[skill icons]
))} @@ -30,11 +30,9 @@ function ScheduleCellDialog({ members }: ScheduleCellProps) { {t('onShift')} -
-
- {membersDisplay} -
- +
+
{membersDisplay}
+
); diff --git a/src/components/shift-schedule/ScheduleTable.tsx b/src/components/shift-schedule/ScheduleTable.tsx index 7f98953..12df1d6 100644 --- a/src/components/shift-schedule/ScheduleTable.tsx +++ b/src/components/shift-schedule/ScheduleTable.tsx @@ -14,15 +14,6 @@ import { useTranslations } from 'next-intl'; export type ScheduleCellProps = { members: { name: string; - skills: { - _3dPrinting: boolean; - laserCutting: boolean; - microcontrollers: boolean; - raspberryPi: boolean; - soldering: boolean; - terminal: boolean; - workshop: boolean; - }; }[]; }; diff --git a/src/mock-data/shiftSchedule.ts b/src/mock-data/shiftSchedule.ts index e14bbd7..d79c25d 100644 --- a/src/mock-data/shiftSchedule.ts +++ b/src/mock-data/shiftSchedule.ts @@ -1,102 +1,64 @@ const shiftScheduleMochData = { monday: { '10:15 - 12:07': { - members: [ - { - name: 'En person', - skills: { - _3dPrinting: false, - laserCutting: false, - microcontrollers: false, - raspberryPi: true, - soldering: false, - terminal: true, - workshop: true, - }, - }, - { - name: 'En annen person', - skills: { - _3dPrinting: true, - laserCutting: true, - microcontrollers: true, - raspberryPi: false, - soldering: false, - terminal: true, - workshop: true, - }, - }, - { - name: 'Person 3', - skills: { - _3dPrinting: false, - laserCutting: false, - microcontrollers: false, - raspberryPi: false, - soldering: false, - terminal: false, - workshop: false, - }, - }, - ], + members: [{ name: 'En person' }], }, '12:07 - 14:07': { - members: [ - { - name: 'Person 3', - skills: { - _3dPrinting: false, - laserCutting: true, - microcontrollers: false, - raspberryPi: true, - soldering: false, - terminal: true, - workshop: false, - }, - }, - ], + members: [{ name: 'En person' }], }, '14:07 - 16:07': { - members: [], + members: [ + { name: 'En person' }, + { name: 'En annen person' }, + { name: 'Person 3' }, + ], }, '16:07 - 18:00': { - members: [], + members: [{ name: 'En person' }], }, }, tuesday: { '10:15 - 12:07': { - members: [], + members: [{ name: 'En person' }], }, '12:07 - 14:07': { - members: [], + members: [{ name: 'En person' }], }, '14:07 - 16:07': { - members: [], + members: [ + { name: 'En person' }, + { name: 'En annen person' }, + { name: 'Person 3' }, + ], }, '16:07 - 18:00': { - members: [], + members: [{ name: 'En person' }], }, }, wednesday: { '10:15 - 12:07': { - members: [], + members: [{ name: 'En person' }, { name: 'En annen person' }], }, '12:07 - 14:07': { members: [], }, '14:07 - 16:07': { - members: [], + members: [{ name: 'En person' }, { name: 'En annen person' }], }, '16:07 - 18:00': { - members: [], + members: [{ name: 'En person' }, { name: 'En annen person' }], }, }, thursday: { '10:15 - 12:07': { - members: [], + members: [{ name: 'En person' }], }, '12:07 - 14:07': { - members: [], + members: [ + { name: 'En person' }, + { name: 'En annen person' }, + { name: 'Person 3' }, + ], }, '14:07 - 16:07': { members: [], @@ -110,26 +72,13 @@ const shiftScheduleMochData = { members: [], }, '12:07 - 14:07': { - members: [ - { - name: 'En person', - skills: { - _3dPrinting: false, - laserCutting: false, - microcontrollers: false, - raspberryPi: false, - soldering: false, - terminal: false, - workshop: false, - }, - }, - ], + members: [{ name: 'En person' }, { name: 'En annen person' }], }, '14:07 - 16:07': { - members: [], + members: [{ name: 'En person' }], }, '16:07 - 18:00': { - members: [], + members: [{ name: 'En person' }], }, }, }; From 569f9ba45cc295eb5013fbce837bd8ecd6bfd0b0 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Sun, 27 Oct 2024 01:05:28 +0200 Subject: [PATCH 06/22] feat: add skeleton for schedule table --- .../(default)/shift-schedule/loading.tsx | 9 ++ .../(default)/shift-schedule/page.tsx | 3 +- .../shift-schedule/ScheduleCellSkeleton.tsx | 15 ++++ .../shift-schedule/ScheduleTable.tsx | 1 - .../shift-schedule/ScheduleTableSkeleton.tsx | 86 +++++++++++++++++++ 5 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 src/app/[locale]/(default)/shift-schedule/loading.tsx create mode 100644 src/components/shift-schedule/ScheduleCellSkeleton.tsx create mode 100644 src/components/shift-schedule/ScheduleTableSkeleton.tsx diff --git a/src/app/[locale]/(default)/shift-schedule/loading.tsx b/src/app/[locale]/(default)/shift-schedule/loading.tsx new file mode 100644 index 0000000..5178537 --- /dev/null +++ b/src/app/[locale]/(default)/shift-schedule/loading.tsx @@ -0,0 +1,9 @@ +import { ScheduleTableSkeleton } from "@/components/shift-schedule/ScheduleTableSkeleton"; + +export default function ShiftScheduleSkeleton() { + return ( + <> + + + ); +} \ No newline at end of file diff --git a/src/app/[locale]/(default)/shift-schedule/page.tsx b/src/app/[locale]/(default)/shift-schedule/page.tsx index aaf4f98..62c61e5 100644 --- a/src/app/[locale]/(default)/shift-schedule/page.tsx +++ b/src/app/[locale]/(default)/shift-schedule/page.tsx @@ -15,13 +15,12 @@ export async function generateMetadata({ }; } -export default function AboutPage({ +export default function ShiftSchedulePage({ params: { locale }, }: { params: { locale: string }; }) { unstable_setRequestLocale(locale); - const t = useTranslations('shiftSchedule'); return ( <> diff --git a/src/components/shift-schedule/ScheduleCellSkeleton.tsx b/src/components/shift-schedule/ScheduleCellSkeleton.tsx new file mode 100644 index 0000000..243a4bd --- /dev/null +++ b/src/components/shift-schedule/ScheduleCellSkeleton.tsx @@ -0,0 +1,15 @@ +import { Skeleton } from '@/components/ui/Skeleton'; +import { TableCell } from '@/components/ui/Table'; +import type React from 'react'; + +function ScheduleCellSkeleton() { + return ( + <> + + + + + ); +} + +export { ScheduleCellSkeleton }; diff --git a/src/components/shift-schedule/ScheduleTable.tsx b/src/components/shift-schedule/ScheduleTable.tsx index 12df1d6..9e45b16 100644 --- a/src/components/shift-schedule/ScheduleTable.tsx +++ b/src/components/shift-schedule/ScheduleTable.tsx @@ -8,7 +8,6 @@ import { TableHeader, TableRow, } from '@/components/ui/Table'; -import { cx } from '@/lib/utils'; import { useTranslations } from 'next-intl'; export type ScheduleCellProps = { diff --git a/src/components/shift-schedule/ScheduleTableSkeleton.tsx b/src/components/shift-schedule/ScheduleTableSkeleton.tsx new file mode 100644 index 0000000..0bfbe08 --- /dev/null +++ b/src/components/shift-schedule/ScheduleTableSkeleton.tsx @@ -0,0 +1,86 @@ +import { ScheduleCellSkeleton } from '@/components/shift-schedule/ScheduleCellSkeleton'; +import { Skeleton } from '@/components/ui/Skeleton'; +import { + Table, + TableBody, + TableCaption, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/Table'; +import { useTranslations } from 'next-intl'; + +function ScheduleTableSkeleton() { + const t = useTranslations('shiftSchedule.scheduleTable'); + // Cannot use translation unless days and times are of these types + const days: ('monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday')[] = [ + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + ]; + const times: ( + | '10:15 - 12:07' + | '12:07 - 14:07' + | '14:07 - 16:07' + | '16:07 - 18:00' + )[] = ['10:15 - 12:07', '12:07 - 14:07', '14:07 - 16:07', '16:07 - 18:00']; + + return ( + <> + {/* Table shown on small screens */} +
+ {days.map((day, index) => ( + + + + {t('time')} + {t(day)} + + + + {times.map((time) => ( + + {time} + + + ))} + +
+ ))} + + [skill icons legend] +
+
+ + {/* Table shown on all other screens */} + + + + {t('time')} + {days.map((day) => ( + + {t(day)} + + ))} + + + + {times.map((time) => ( + + {time} + {days.map((day) => ( + + ))} + + ))} + + [skill icons legend] +
+ + ); +} + +export { ScheduleTableSkeleton }; From d2979b592dc1f1869e782660341fd2c202088f47 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Sun, 27 Oct 2024 14:05:48 +0100 Subject: [PATCH 07/22] feat: add administrator menu to clear shift schedule --- messages/en.json | 4 +++ messages/no.json | 4 +++ .../(default)/shift-schedule/page.tsx | 3 ++- .../shift-schedule/AdministratorMenu.tsx | 24 +++++++++++++++++ .../shift-schedule/RegisterSection.tsx | 6 ++--- .../shift-schedule/ScheduleCell.tsx | 18 ++++++------- .../shift-schedule/ScheduleCellContent.tsx | 26 +++++++++---------- .../shift-schedule/ScheduleCellDialog.tsx | 2 +- .../shift-schedule/ScheduleCellSkeleton.tsx | 8 +++--- .../shift-schedule/ScheduleTableSkeleton.tsx | 1 - 10 files changed, 61 insertions(+), 35 deletions(-) create mode 100644 src/components/shift-schedule/AdministratorMenu.tsx diff --git a/messages/en.json b/messages/en.json index f2831ce..7dce67e 100644 --- a/messages/en.json +++ b/messages/en.json @@ -127,6 +127,10 @@ }, "shiftSchedule": { "title": "Shift Schedule", + "administratorMenu": { + "administratorMenu": "Administrator Menu", + "clearShiftSchedule": "Clear shift schedule" + }, "scheduleTable": { "time": "Time", "monday": "Monday", diff --git a/messages/no.json b/messages/no.json index e1b44fe..ab62a6f 100644 --- a/messages/no.json +++ b/messages/no.json @@ -127,6 +127,10 @@ }, "shiftSchedule": { "title": "Vaktliste", + "administratorMenu": { + "administratorMenu": "Administrator-meny", + "clearShiftSchedule": "Tøm vaktliste" + }, "scheduleTable": { "time": "Tid", "monday": "Mandag", diff --git a/src/app/[locale]/(default)/shift-schedule/page.tsx b/src/app/[locale]/(default)/shift-schedule/page.tsx index 62c61e5..6db6f36 100644 --- a/src/app/[locale]/(default)/shift-schedule/page.tsx +++ b/src/app/[locale]/(default)/shift-schedule/page.tsx @@ -1,7 +1,7 @@ import { ScheduleTable } from '@/components/shift-schedule/ScheduleTable'; -import { useTranslations } from 'next-intl'; import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; import { shiftScheduleMochData } from '@/mock-data/shiftSchedule'; +import { AdministratorMenu } from '@/components/shift-schedule/AdministratorMenu'; export async function generateMetadata({ params: { locale }, @@ -24,6 +24,7 @@ export default function ShiftSchedulePage({ return ( <> + ); diff --git a/src/components/shift-schedule/AdministratorMenu.tsx b/src/components/shift-schedule/AdministratorMenu.tsx new file mode 100644 index 0000000..3032124 --- /dev/null +++ b/src/components/shift-schedule/AdministratorMenu.tsx @@ -0,0 +1,24 @@ +import { Button } from '@/components/ui/Button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'; +import { Trash2Icon } from 'lucide-react'; +import { useTranslations } from 'next-intl'; + +function AdministratorMenu() { + const t = useTranslations('shiftSchedule.administratorMenu'); + + return ( + + + {t('administratorMenu')} + + + + + + ); +} + +export { AdministratorMenu }; diff --git a/src/components/shift-schedule/RegisterSection.tsx b/src/components/shift-schedule/RegisterSection.tsx index 06d301b..79f3e95 100644 --- a/src/components/shift-schedule/RegisterSection.tsx +++ b/src/components/shift-schedule/RegisterSection.tsx @@ -1,7 +1,7 @@ +import { Button } from '@/components/ui/Button'; +import { Checkbox } from '@/components/ui/Checkbox'; +import { Label } from '@/components/ui/Label'; import { useTranslations } from 'next-intl'; -import { Button } from '../ui/Button'; -import { Checkbox } from '../ui/Checkbox'; -import { Label } from '../ui/Label'; function RegisterSection({ className }: { className?: string }) { const t = useTranslations( diff --git a/src/components/shift-schedule/ScheduleCell.tsx b/src/components/shift-schedule/ScheduleCell.tsx index c91567e..04d5348 100644 --- a/src/components/shift-schedule/ScheduleCell.tsx +++ b/src/components/shift-schedule/ScheduleCell.tsx @@ -6,16 +6,14 @@ import React from 'react'; function ScheduleCell({ members }: ScheduleCellProps) { return ( - <> - - - - - - - - - + + + + + + + + ); } diff --git a/src/components/shift-schedule/ScheduleCellContent.tsx b/src/components/shift-schedule/ScheduleCellContent.tsx index 4fc3250..49e338e 100644 --- a/src/components/shift-schedule/ScheduleCellContent.tsx +++ b/src/components/shift-schedule/ScheduleCellContent.tsx @@ -14,7 +14,7 @@ function ScheduleCellContent({ members }: ScheduleCellProps) { : 'bg-foreground/20 hover:bg-foreground/25'; let memberCountIcon: React.ReactNode; - const memberCountIconStyle = 'w-6 h-6'; + const memberCountIconStyle = 'w-7 h-7'; let memberCount: React.ReactNode; const memberCountStyle = 'flex align-bottom space-x-1 space-y-0'; let skillIcons: React.ReactNode; @@ -53,19 +53,17 @@ function ScheduleCellContent({ members }: ScheduleCellProps) { } return ( - <> - -
- {memberCountIcon} -
- {memberCount} - {skillIcons} -
-
-
- + +
+ {memberCountIcon} +
+ {memberCount} + {skillIcons} +
+
+
); } diff --git a/src/components/shift-schedule/ScheduleCellDialog.tsx b/src/components/shift-schedule/ScheduleCellDialog.tsx index adac36c..86ae2b9 100644 --- a/src/components/shift-schedule/ScheduleCellDialog.tsx +++ b/src/components/shift-schedule/ScheduleCellDialog.tsx @@ -1,7 +1,7 @@ +import { RegisterSection } from '@/components/shift-schedule/RegisterSection'; import type { ScheduleCellProps } from '@/components/shift-schedule/ScheduleTable'; import { DialogHeader, DialogTitle } from '@/components/ui/Dialog'; import { useTranslations } from 'next-intl'; -import { RegisterSection } from './RegisterSection'; function ScheduleCellDialog({ members }: ScheduleCellProps) { const t = useTranslations('shiftSchedule.scheduleTable.scheduleCellDialog'); diff --git a/src/components/shift-schedule/ScheduleCellSkeleton.tsx b/src/components/shift-schedule/ScheduleCellSkeleton.tsx index 243a4bd..76c663d 100644 --- a/src/components/shift-schedule/ScheduleCellSkeleton.tsx +++ b/src/components/shift-schedule/ScheduleCellSkeleton.tsx @@ -4,11 +4,9 @@ import type React from 'react'; function ScheduleCellSkeleton() { return ( - <> - - - - + + + ); } diff --git a/src/components/shift-schedule/ScheduleTableSkeleton.tsx b/src/components/shift-schedule/ScheduleTableSkeleton.tsx index 0bfbe08..b362194 100644 --- a/src/components/shift-schedule/ScheduleTableSkeleton.tsx +++ b/src/components/shift-schedule/ScheduleTableSkeleton.tsx @@ -1,5 +1,4 @@ import { ScheduleCellSkeleton } from '@/components/shift-schedule/ScheduleCellSkeleton'; -import { Skeleton } from '@/components/ui/Skeleton'; import { Table, TableBody, From 16310839970c902433cd7d6c76f1384ecffcb690 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Mon, 28 Oct 2024 01:21:26 +0100 Subject: [PATCH 08/22] refactor: improve code quality by adding changes requested in pr --- messages/en.json | 5 +- messages/no.json | 5 +- .../(default)/shift-schedule/layout.tsx | 2 +- .../(default)/shift-schedule/loading.tsx | 69 ++++++++++++++- .../(default)/shift-schedule/page.tsx | 4 +- .../shift-schedule/AdministratorMenu.tsx | 4 +- .../shift-schedule/RegisterSection.tsx | 5 +- .../shift-schedule/ScheduleCellContent.tsx | 56 ++++-------- .../shift-schedule/ScheduleCellDialog.tsx | 18 ++-- .../shift-schedule/ScheduleCellSkeleton.tsx | 2 +- .../shift-schedule/ScheduleTable.tsx | 16 ++-- .../shift-schedule/ScheduleTableSkeleton.tsx | 85 ------------------- src/mock-data/shiftSchedule.ts | 11 ++- 13 files changed, 118 insertions(+), 164 deletions(-) delete mode 100644 src/components/shift-schedule/ScheduleTableSkeleton.tsx diff --git a/messages/en.json b/messages/en.json index 7dce67e..65181a6 100644 --- a/messages/en.json +++ b/messages/en.json @@ -139,10 +139,7 @@ "thursday": "Thursday", "friday": "Friday", "scheduleCellContent": { - "empty": "Closed", - "member": "person", - "members": "people", - "present": "on shift" + "onShift": "{count, plural, =0 {Closed} =1 {1 member on shift} other {# members on shift}}" }, "scheduleCellDialog": { "onShift": "On shift", diff --git a/messages/no.json b/messages/no.json index ab62a6f..9f10ca5 100644 --- a/messages/no.json +++ b/messages/no.json @@ -139,10 +139,7 @@ "thursday": "Torsdag", "friday": "Fredag", "scheduleCellContent": { - "empty": "Lukket", - "member": "person", - "members": "personer", - "present": "på vakt" + "onShift": "{count, plural, =0 {Lukket} =1 {1 person på vakt} other {# personer på vakt}}" }, "scheduleCellDialog": { "onShift": "På vakt", diff --git a/src/app/[locale]/(default)/shift-schedule/layout.tsx b/src/app/[locale]/(default)/shift-schedule/layout.tsx index 33c170c..5694313 100644 --- a/src/app/[locale]/(default)/shift-schedule/layout.tsx +++ b/src/app/[locale]/(default)/shift-schedule/layout.tsx @@ -15,7 +15,7 @@ export default function ShiftScheduleHeaderLayout({ return ( <> -

{t('title')}

+

{t('title')}

{children} ); diff --git a/src/app/[locale]/(default)/shift-schedule/loading.tsx b/src/app/[locale]/(default)/shift-schedule/loading.tsx index 5178537..3c9a0cd 100644 --- a/src/app/[locale]/(default)/shift-schedule/loading.tsx +++ b/src/app/[locale]/(default)/shift-schedule/loading.tsx @@ -1,9 +1,72 @@ -import { ScheduleTableSkeleton } from "@/components/shift-schedule/ScheduleTableSkeleton"; +import { ScheduleCellSkeleton } from '@/components/shift-schedule/ScheduleCellSkeleton'; +import { + Table, + TableBody, + TableCaption, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/Table'; +import { useTranslations } from 'next-intl'; + +export default function ShiftScheduleLoading() { + const t = useTranslations('shiftSchedule.scheduleTable'); + // Cannot use translation unless days and times are of these types + const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] as const; + const times = ['10:15 - 12:07', '12:07 - 14:07', '14:07 - 16:07', '16:07 - 18:00'] as const; -export default function ShiftScheduleSkeleton() { return ( <> - + {/* Table shown on small screens */} +
+ {days.map((day, index) => ( + + + + {t('time')} + {t(day)} + + + + {times.map((time) => ( + + {time} + + + ))} + +
+ ))} + + [skill icons legend] +
+
+ + {/* Table shown on all other screens */} + + + + {t('time')} + {days.map((day) => ( + + {t(day)} + + ))} + + + + {times.map((time) => ( + + {time} + {days.map((day) => ( + + ))} + + ))} + + [skill icons legend] +
); } \ No newline at end of file diff --git a/src/app/[locale]/(default)/shift-schedule/page.tsx b/src/app/[locale]/(default)/shift-schedule/page.tsx index 6db6f36..b18f360 100644 --- a/src/app/[locale]/(default)/shift-schedule/page.tsx +++ b/src/app/[locale]/(default)/shift-schedule/page.tsx @@ -1,6 +1,6 @@ import { ScheduleTable } from '@/components/shift-schedule/ScheduleTable'; import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; -import { shiftScheduleMochData } from '@/mock-data/shiftSchedule'; +import { shiftScheduleMockData } from '@/mock-data/shiftSchedule'; import { AdministratorMenu } from '@/components/shift-schedule/AdministratorMenu'; export async function generateMetadata({ @@ -25,7 +25,7 @@ export default function ShiftSchedulePage({ return ( <> - + ); } diff --git a/src/components/shift-schedule/AdministratorMenu.tsx b/src/components/shift-schedule/AdministratorMenu.tsx index 3032124..3e4600a 100644 --- a/src/components/shift-schedule/AdministratorMenu.tsx +++ b/src/components/shift-schedule/AdministratorMenu.tsx @@ -12,9 +12,9 @@ function AdministratorMenu() { {t('administratorMenu')} - diff --git a/src/components/shift-schedule/RegisterSection.tsx b/src/components/shift-schedule/RegisterSection.tsx index 79f3e95..0342ced 100644 --- a/src/components/shift-schedule/RegisterSection.tsx +++ b/src/components/shift-schedule/RegisterSection.tsx @@ -1,6 +1,7 @@ import { Button } from '@/components/ui/Button'; import { Checkbox } from '@/components/ui/Checkbox'; import { Label } from '@/components/ui/Label'; +import { cx } from '@/lib/utils'; import { useTranslations } from 'next-intl'; function RegisterSection({ className }: { className?: string }) { @@ -9,8 +10,8 @@ function RegisterSection({ className }: { className?: string }) { ); return ( -
-
+
+
diff --git a/src/components/shift-schedule/ScheduleCellContent.tsx b/src/components/shift-schedule/ScheduleCellContent.tsx index 49e338e..3d705a5 100644 --- a/src/components/shift-schedule/ScheduleCellContent.tsx +++ b/src/components/shift-schedule/ScheduleCellContent.tsx @@ -13,52 +13,28 @@ function ScheduleCellContent({ members }: ScheduleCellProps) { ? 'bg-accent/50 hover:bg-accent dark:bg-accent/40 dark:hover:bg-accent/60 text-accent-foreground' : 'bg-foreground/20 hover:bg-foreground/25'; - let memberCountIcon: React.ReactNode; - const memberCountIconStyle = 'w-7 h-7'; - let memberCount: React.ReactNode; - const memberCountStyle = 'flex align-bottom space-x-1 space-y-0'; - let skillIcons: React.ReactNode; - - // Set member count icon, member count, and skill icons based on amount of members present - if (members.length === 0) { - // Empty shift - memberCount =

{t('empty')}

; - } else { - // At least 1 person on shift - skillIcons = ( -
[skill icons total]
+ const skillIcons = + members.length === 0 ? ( + <> + ) : ( +
[skill icons total]
); - if (members.length === 1) { - // 1 person on shit - memberCountIcon = ; - memberCount = ( -
-

- 1 {t('member')} {t('present')} -

-
- ); - } else { - // 2 or more people on shift - memberCountIcon = ; - memberCount = ( -
-

- {members.length} {t('members')} {t('present')} -

-
- ); - } + const memberCount = {t('onShift', { count: members.length })}; + + const memberCountIconStyle = 'w-7 h-7'; + let memberCountIcon: React.ReactNode; + if (members.length === 1) { + memberCountIcon = ; + } else if (members.length >= 1) { + memberCountIcon = ; } return ( - -
+ +
{memberCountIcon} -
+
{memberCount} {skillIcons}
diff --git a/src/components/shift-schedule/ScheduleCellDialog.tsx b/src/components/shift-schedule/ScheduleCellDialog.tsx index 86ae2b9..eac641f 100644 --- a/src/components/shift-schedule/ScheduleCellDialog.tsx +++ b/src/components/shift-schedule/ScheduleCellDialog.tsx @@ -9,17 +9,17 @@ function ScheduleCellDialog({ members }: ScheduleCellProps) { let membersDisplay: React.ReactNode; if (members.length === 0) { - membersDisplay =

{t('empty')}

; + membersDisplay =

{t('empty')}

; } else { membersDisplay = ( - <> +
{members.map((member) => ( -
-

{member.name}

-
[skill icons]
+
+

{member.name}

+
[skill icons]
))} - +
); } @@ -30,9 +30,9 @@ function ScheduleCellDialog({ members }: ScheduleCellProps) { {t('onShift')} -
-
{membersDisplay}
- +
+ {membersDisplay} +
); diff --git a/src/components/shift-schedule/ScheduleCellSkeleton.tsx b/src/components/shift-schedule/ScheduleCellSkeleton.tsx index 76c663d..f589c93 100644 --- a/src/components/shift-schedule/ScheduleCellSkeleton.tsx +++ b/src/components/shift-schedule/ScheduleCellSkeleton.tsx @@ -4,7 +4,7 @@ import type React from 'react'; function ScheduleCellSkeleton() { return ( - + ); diff --git a/src/components/shift-schedule/ScheduleTable.tsx b/src/components/shift-schedule/ScheduleTable.tsx index 9e45b16..6d89f50 100644 --- a/src/components/shift-schedule/ScheduleTable.tsx +++ b/src/components/shift-schedule/ScheduleTable.tsx @@ -36,19 +36,19 @@ type ScheduleTableProps = { function ScheduleTable({ week }: ScheduleTableProps) { const t = useTranslations('shiftSchedule.scheduleTable'); // Cannot use translation unless days and times are of these types - const days: ('monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday')[] = [ + const days = [ 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', - ]; - const times: ( - | '10:15 - 12:07' - | '12:07 - 14:07' - | '14:07 - 16:07' - | '16:07 - 18:00' - )[] = ['10:15 - 12:07', '12:07 - 14:07', '14:07 - 16:07', '16:07 - 18:00']; + ] as const; + const times = [ + '10:15 - 12:07', + '12:07 - 14:07', + '14:07 - 16:07', + '16:07 - 18:00', + ] as const; return ( <> diff --git a/src/components/shift-schedule/ScheduleTableSkeleton.tsx b/src/components/shift-schedule/ScheduleTableSkeleton.tsx deleted file mode 100644 index b362194..0000000 --- a/src/components/shift-schedule/ScheduleTableSkeleton.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { ScheduleCellSkeleton } from '@/components/shift-schedule/ScheduleCellSkeleton'; -import { - Table, - TableBody, - TableCaption, - TableCell, - TableHead, - TableHeader, - TableRow, -} from '@/components/ui/Table'; -import { useTranslations } from 'next-intl'; - -function ScheduleTableSkeleton() { - const t = useTranslations('shiftSchedule.scheduleTable'); - // Cannot use translation unless days and times are of these types - const days: ('monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday')[] = [ - 'monday', - 'tuesday', - 'wednesday', - 'thursday', - 'friday', - ]; - const times: ( - | '10:15 - 12:07' - | '12:07 - 14:07' - | '14:07 - 16:07' - | '16:07 - 18:00' - )[] = ['10:15 - 12:07', '12:07 - 14:07', '14:07 - 16:07', '16:07 - 18:00']; - - return ( - <> - {/* Table shown on small screens */} -
- {days.map((day, index) => ( - - - - {t('time')} - {t(day)} - - - - {times.map((time) => ( - - {time} - - - ))} - -
- ))} - - [skill icons legend] -
-
- - {/* Table shown on all other screens */} - - - - {t('time')} - {days.map((day) => ( - - {t(day)} - - ))} - - - - {times.map((time) => ( - - {time} - {days.map((day) => ( - - ))} - - ))} - - [skill icons legend] -
- - ); -} - -export { ScheduleTableSkeleton }; diff --git a/src/mock-data/shiftSchedule.ts b/src/mock-data/shiftSchedule.ts index d79c25d..818b83d 100644 --- a/src/mock-data/shiftSchedule.ts +++ b/src/mock-data/shiftSchedule.ts @@ -1,4 +1,4 @@ -const shiftScheduleMochData = { +const shiftScheduleMockData = { monday: { '10:15 - 12:07': { members: [{ name: 'En person' }], @@ -72,7 +72,12 @@ const shiftScheduleMochData = { members: [], }, '12:07 - 14:07': { - members: [{ name: 'En person' }, { name: 'En annen person' }], + members: [ + { + name: 'En person med veldig langt navn så jeg kan teste navn som bruker to linjer', + }, + { name: 'En annen person' }, + ], }, '14:07 - 16:07': { members: [{ name: 'En person' }], @@ -83,4 +88,4 @@ const shiftScheduleMochData = { }, }; -export { shiftScheduleMochData }; +export { shiftScheduleMockData }; From 51d47fc28b352e0e4e3d42db06ad04d346850713 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Mon, 28 Oct 2024 02:13:04 +0100 Subject: [PATCH 09/22] style: fix formatting for lint and format workflow --- .../[locale]/(default)/shift-schedule/layout.tsx | 10 +++++----- .../[locale]/(default)/shift-schedule/loading.tsx | 15 +++++++++++++-- .../[locale]/(default)/shift-schedule/page.tsx | 4 ++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/app/[locale]/(default)/shift-schedule/layout.tsx b/src/app/[locale]/(default)/shift-schedule/layout.tsx index 5694313..74df4d9 100644 --- a/src/app/[locale]/(default)/shift-schedule/layout.tsx +++ b/src/app/[locale]/(default)/shift-schedule/layout.tsx @@ -1,9 +1,9 @@ -import { useTranslations } from "next-intl"; -import { unstable_setRequestLocale } from "next-intl/server"; +import { useTranslations } from 'next-intl'; +import { unstable_setRequestLocale } from 'next-intl/server'; type ShiftScheduleHeaderProps = { - children: React.ReactNode, - params: { locale: string } + children: React.ReactNode; + params: { locale: string }; }; export default function ShiftScheduleHeaderLayout({ @@ -19,4 +19,4 @@ export default function ShiftScheduleHeaderLayout({ {children} ); -} \ No newline at end of file +} diff --git a/src/app/[locale]/(default)/shift-schedule/loading.tsx b/src/app/[locale]/(default)/shift-schedule/loading.tsx index 3c9a0cd..d0532b5 100644 --- a/src/app/[locale]/(default)/shift-schedule/loading.tsx +++ b/src/app/[locale]/(default)/shift-schedule/loading.tsx @@ -13,8 +13,19 @@ import { useTranslations } from 'next-intl'; export default function ShiftScheduleLoading() { const t = useTranslations('shiftSchedule.scheduleTable'); // Cannot use translation unless days and times are of these types - const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] as const; - const times = ['10:15 - 12:07', '12:07 - 14:07', '14:07 - 16:07', '16:07 - 18:00'] as const; + const days = [ + 'monday', + 'tuesday', + 'wednesday', + 'thursday', + 'friday', + ] as const; + const times = [ + '10:15 - 12:07', + '12:07 - 14:07', + '14:07 - 16:07', + '16:07 - 18:00', + ] as const; return ( <> diff --git a/src/app/[locale]/(default)/shift-schedule/page.tsx b/src/app/[locale]/(default)/shift-schedule/page.tsx index b18f360..bf4593f 100644 --- a/src/app/[locale]/(default)/shift-schedule/page.tsx +++ b/src/app/[locale]/(default)/shift-schedule/page.tsx @@ -1,7 +1,7 @@ +import { AdministratorMenu } from '@/components/shift-schedule/AdministratorMenu'; import { ScheduleTable } from '@/components/shift-schedule/ScheduleTable'; -import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; import { shiftScheduleMockData } from '@/mock-data/shiftSchedule'; -import { AdministratorMenu } from '@/components/shift-schedule/AdministratorMenu'; +import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; export async function generateMetadata({ params: { locale }, From b184826740950f2cd3e77b4e030bbf26cd154c04 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Mon, 28 Oct 2024 02:18:18 +0100 Subject: [PATCH 10/22] style: add 1 empty line to pass lint and format workflow --- src/app/[locale]/(default)/shift-schedule/loading.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/[locale]/(default)/shift-schedule/loading.tsx b/src/app/[locale]/(default)/shift-schedule/loading.tsx index d0532b5..0e98b95 100644 --- a/src/app/[locale]/(default)/shift-schedule/loading.tsx +++ b/src/app/[locale]/(default)/shift-schedule/loading.tsx @@ -80,4 +80,4 @@ export default function ShiftScheduleLoading() { ); -} \ No newline at end of file +} From dc167fc522ec2fd7a215c45d563a3526cfb3320a Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Mon, 28 Oct 2024 17:26:46 +0100 Subject: [PATCH 11/22] feat: make administrator menu collapsible and less distracting --- bun.lockb | Bin 225027 -> 225810 bytes messages/en.json | 2 +- messages/no.json | 2 +- package.json | 1 + .../shift-schedule/AdministratorMenu.tsx | 29 ++++++++++++------ .../shift-schedule/ScheduleCellContent.tsx | 2 +- src/components/ui/Collapsible.tsx | 11 +++++++ 7 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 src/components/ui/Collapsible.tsx diff --git a/bun.lockb b/bun.lockb index 7859ab8fa306b79d22316a281689f1911d7560d8..089a234098a9a065188958d527330b54ac2ac66e 100755 GIT binary patch delta 38931 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 diff --git a/messages/en.json b/messages/en.json index 65181a6..216eb74 100644 --- a/messages/en.json +++ b/messages/en.json @@ -139,7 +139,7 @@ "thursday": "Thursday", "friday": "Friday", "scheduleCellContent": { - "onShift": "{count, plural, =0 {Closed} =1 {1 member on shift} other {# members on shift}}" + "onShift": "{count, plural, =0 {Closed} =1 {1 person on shift} other {# people on shift}}" }, "scheduleCellDialog": { "onShift": "On shift", diff --git a/messages/no.json b/messages/no.json index 9f10ca5..6052de2 100644 --- a/messages/no.json +++ b/messages/no.json @@ -139,7 +139,7 @@ "thursday": "Torsdag", "friday": "Fredag", "scheduleCellContent": { - "onShift": "{count, plural, =0 {Lukket} =1 {1 person på vakt} other {# personer på vakt}}" + "onShift": "{count, plural, =0 {Stengt} =1 {1 person på vakt} other {# personer på vakt}}" }, "scheduleCellDialog": { "onShift": "På vakt", diff --git a/package.json b/package.json index 0cd9632..6e4679d 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@lucia-auth/adapter-drizzle": "^1.1.0", "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-collapsible": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", diff --git a/src/components/shift-schedule/AdministratorMenu.tsx b/src/components/shift-schedule/AdministratorMenu.tsx index 3e4600a..5c685e8 100644 --- a/src/components/shift-schedule/AdministratorMenu.tsx +++ b/src/components/shift-schedule/AdministratorMenu.tsx @@ -1,23 +1,34 @@ import { Button } from '@/components/ui/Button'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card'; -import { Trash2Icon } from 'lucide-react'; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from '@/components/ui/Collapsible'; +import { ChevronDownIcon, Trash2Icon } from 'lucide-react'; import { useTranslations } from 'next-intl'; function AdministratorMenu() { const t = useTranslations('shiftSchedule.administratorMenu'); return ( - - - {t('administratorMenu')} - - + +
+ + {t('administratorMenu')} + + + + +
+ -
-
+ + ); } diff --git a/src/components/shift-schedule/ScheduleCellContent.tsx b/src/components/shift-schedule/ScheduleCellContent.tsx index 3d705a5..14b6484 100644 --- a/src/components/shift-schedule/ScheduleCellContent.tsx +++ b/src/components/shift-schedule/ScheduleCellContent.tsx @@ -22,7 +22,7 @@ function ScheduleCellContent({ members }: ScheduleCellProps) { const memberCount = {t('onShift', { count: members.length })}; - const memberCountIconStyle = 'w-7 h-7'; + const memberCountIconStyle = 'size-7'; let memberCountIcon: React.ReactNode; if (members.length === 1) { memberCountIcon = ; diff --git a/src/components/ui/Collapsible.tsx b/src/components/ui/Collapsible.tsx new file mode 100644 index 0000000..86ab87d --- /dev/null +++ b/src/components/ui/Collapsible.tsx @@ -0,0 +1,11 @@ +'use client'; + +import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; + +const Collapsible = CollapsiblePrimitive.Root; + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger; + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; + +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; From f6608460233fa6c02027219ec1db5fef80662911 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Mon, 28 Oct 2024 18:16:30 +0100 Subject: [PATCH 12/22] style: add 1 space to pass lint and format workflow --- src/app/[locale]/(default)/shift-schedule/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/[locale]/(default)/shift-schedule/layout.tsx b/src/app/[locale]/(default)/shift-schedule/layout.tsx index 306a6b9..7a65670 100644 --- a/src/app/[locale]/(default)/shift-schedule/layout.tsx +++ b/src/app/[locale]/(default)/shift-schedule/layout.tsx @@ -2,7 +2,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server'; type ShiftScheduleLayoutProps = { children: React.ReactNode; - params: Promise<{ locale: string}>; + params: Promise<{ locale: string }>; }; export default async function ShiftScheduleLayout({ From fb846a82ab9621670f00cdb7a4ea95f450d98e1c Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Mon, 28 Oct 2024 21:04:18 +0100 Subject: [PATCH 13/22] refactor: change collapsible arrow direction when opening or closing menu --- .../(default)/shift-schedule/page.tsx | 8 ++++- .../shift-schedule/AdministratorMenu.tsx | 36 ++++++++++++++----- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/app/[locale]/(default)/shift-schedule/page.tsx b/src/app/[locale]/(default)/shift-schedule/page.tsx index 35b3a2e..24d8de4 100644 --- a/src/app/[locale]/(default)/shift-schedule/page.tsx +++ b/src/app/[locale]/(default)/shift-schedule/page.tsx @@ -24,10 +24,16 @@ export default async function ShiftSchedulePage({ const { locale } = await params; setRequestLocale(locale); + const t = await getTranslations('shiftSchedule'); return ( <> - + ); diff --git a/src/components/shift-schedule/AdministratorMenu.tsx b/src/components/shift-schedule/AdministratorMenu.tsx index 5c685e8..83fb98e 100644 --- a/src/components/shift-schedule/AdministratorMenu.tsx +++ b/src/components/shift-schedule/AdministratorMenu.tsx @@ -1,31 +1,49 @@ +'use client'; + import { Button } from '@/components/ui/Button'; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/Collapsible'; -import { ChevronDownIcon, Trash2Icon } from 'lucide-react'; -import { useTranslations } from 'next-intl'; +import { Separator } from '@/components/ui/Separator'; +import { ChevronDownIcon, ChevronUpIcon, Trash2Icon } from 'lucide-react'; +import { useState } from 'react'; + +type AdministratorMenuProps = { + messages: { + administratorMenu: string; + clearShiftSchedule: string; + }; +}; -function AdministratorMenu() { - const t = useTranslations('shiftSchedule.administratorMenu'); +function AdministratorMenu({ messages }: AdministratorMenuProps) { + const [isOpen, setIsOpen] = useState(false); return ( - +
- {t('administratorMenu')} + {messages.administratorMenu}
-
From 8e4944de341eecdb30b53725e07e8bcb10b2d431 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Tue, 29 Oct 2024 12:46:02 +0100 Subject: [PATCH 14/22] feat: display day and time of shift in dialog --- messages/en.json | 1 - messages/no.json | 1 - .../shift-schedule/ScheduleCell.tsx | 15 ++++++++--- .../shift-schedule/ScheduleCellContent.tsx | 11 +++++--- .../shift-schedule/ScheduleCellDialog.tsx | 18 ++++++++++--- .../shift-schedule/ScheduleTable.tsx | 27 ++++++++++++++----- 6 files changed, 54 insertions(+), 19 deletions(-) diff --git a/messages/en.json b/messages/en.json index 87b93f9..88d112d 100644 --- a/messages/en.json +++ b/messages/en.json @@ -151,7 +151,6 @@ "onShift": "{count, plural, =0 {Closed} =1 {1 person on shift} other {# people on shift}}" }, "scheduleCellDialog": { - "onShift": "On shift", "empty": "No-one on shift", "registerSection": { "recurring": "Recurring", diff --git a/messages/no.json b/messages/no.json index baf9784..ab6205d 100644 --- a/messages/no.json +++ b/messages/no.json @@ -151,7 +151,6 @@ "onShift": "{count, plural, =0 {Stengt} =1 {1 person på vakt} other {# personer på vakt}}" }, "scheduleCellDialog": { - "onShift": "På vakt", "empty": "Ingen på vakt", "registerSection": { "recurring": "Gjentagende", diff --git a/src/components/shift-schedule/ScheduleCell.tsx b/src/components/shift-schedule/ScheduleCell.tsx index 04d5348..36c434b 100644 --- a/src/components/shift-schedule/ScheduleCell.tsx +++ b/src/components/shift-schedule/ScheduleCell.tsx @@ -1,17 +1,26 @@ import { ScheduleCellContent } from '@/components/shift-schedule/ScheduleCellContent'; import { ScheduleCellDialog } from '@/components/shift-schedule/ScheduleCellDialog'; -import type { ScheduleCellProps } from '@/components/shift-schedule/ScheduleTable'; import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/Dialog'; import React from 'react'; -function ScheduleCell({ members }: ScheduleCellProps) { +type ScheduleCellProps = { + messages: { + day: string; + time: string; + }; + members: { + name: string; + }[]; +}; + +function ScheduleCell({ messages, members }: ScheduleCellProps) { return ( - + ); diff --git a/src/components/shift-schedule/ScheduleCellContent.tsx b/src/components/shift-schedule/ScheduleCellContent.tsx index 14b6484..c65a63f 100644 --- a/src/components/shift-schedule/ScheduleCellContent.tsx +++ b/src/components/shift-schedule/ScheduleCellContent.tsx @@ -1,11 +1,16 @@ -import type { ScheduleCellProps } from '@/components/shift-schedule/ScheduleTable'; import { TableCell } from '@/components/ui/Table'; import { cx } from '@/lib/utils'; import { UserIcon, UsersIcon } from 'lucide-react'; import { useTranslations } from 'next-intl'; import type React from 'react'; -function ScheduleCellContent({ members }: ScheduleCellProps) { +type ScheduleCellContentProps = { + members: { + name: string; + }[]; +}; + +function ScheduleCellContent({ members }: ScheduleCellContentProps) { const t = useTranslations('shiftSchedule.scheduleTable.scheduleCellContent'); const colorStyle = @@ -26,7 +31,7 @@ function ScheduleCellContent({ members }: ScheduleCellProps) { let memberCountIcon: React.ReactNode; if (members.length === 1) { memberCountIcon = ; - } else if (members.length >= 1) { + } else if (members.length > 1) { memberCountIcon = ; } diff --git a/src/components/shift-schedule/ScheduleCellDialog.tsx b/src/components/shift-schedule/ScheduleCellDialog.tsx index eac641f..bb89d02 100644 --- a/src/components/shift-schedule/ScheduleCellDialog.tsx +++ b/src/components/shift-schedule/ScheduleCellDialog.tsx @@ -1,9 +1,18 @@ import { RegisterSection } from '@/components/shift-schedule/RegisterSection'; -import type { ScheduleCellProps } from '@/components/shift-schedule/ScheduleTable'; import { DialogHeader, DialogTitle } from '@/components/ui/Dialog'; import { useTranslations } from 'next-intl'; -function ScheduleCellDialog({ members }: ScheduleCellProps) { +type ScheduleCellDialogProps = { + messages: { + day: string; + time: string; + }; + members: { + name: string; + }[]; +}; + +function ScheduleCellDialog({ messages, members }: ScheduleCellDialogProps) { const t = useTranslations('shiftSchedule.scheduleTable.scheduleCellDialog'); let membersDisplay: React.ReactNode; @@ -26,8 +35,9 @@ function ScheduleCellDialog({ members }: ScheduleCellProps) { return ( <> - - {t('onShift')} + + {messages.day} + {messages.time}
diff --git a/src/components/shift-schedule/ScheduleTable.tsx b/src/components/shift-schedule/ScheduleTable.tsx index 6d89f50..48e5abd 100644 --- a/src/components/shift-schedule/ScheduleTable.tsx +++ b/src/components/shift-schedule/ScheduleTable.tsx @@ -10,17 +10,17 @@ import { } from '@/components/ui/Table'; import { useTranslations } from 'next-intl'; -export type ScheduleCellProps = { +export type ScheduleEntryProps = { members: { name: string; }[]; }; type ScheduleDayProps = { - '10:15 - 12:07': ScheduleCellProps; - '12:07 - 14:07': ScheduleCellProps; - '14:07 - 16:07': ScheduleCellProps; - '16:07 - 18:00': ScheduleCellProps; + '10:15 - 12:07': ScheduleEntryProps; + '12:07 - 14:07': ScheduleEntryProps; + '14:07 - 16:07': ScheduleEntryProps; + '16:07 - 18:00': ScheduleEntryProps; }; type ScheduleTableProps = { @@ -66,7 +66,13 @@ function ScheduleTable({ week }: ScheduleTableProps) { {times.map((time) => ( {time} - + ))} @@ -94,7 +100,14 @@ function ScheduleTable({ week }: ScheduleTableProps) { {time} {days.map((day) => ( - + ))} ))} From 22c42410beb6baf77bf3a65b0c536c509af58715 Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Tue, 29 Oct 2024 13:34:39 +0100 Subject: [PATCH 15/22] fix: make dialog title fit within dialog on small screens --- src/components/shift-schedule/ScheduleCell.tsx | 2 +- src/components/shift-schedule/ScheduleCellDialog.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/shift-schedule/ScheduleCell.tsx b/src/components/shift-schedule/ScheduleCell.tsx index 36c434b..304d077 100644 --- a/src/components/shift-schedule/ScheduleCell.tsx +++ b/src/components/shift-schedule/ScheduleCell.tsx @@ -19,7 +19,7 @@ function ScheduleCell({ messages, members }: ScheduleCellProps) { - + diff --git a/src/components/shift-schedule/ScheduleCellDialog.tsx b/src/components/shift-schedule/ScheduleCellDialog.tsx index bb89d02..8f56d63 100644 --- a/src/components/shift-schedule/ScheduleCellDialog.tsx +++ b/src/components/shift-schedule/ScheduleCellDialog.tsx @@ -35,9 +35,9 @@ function ScheduleCellDialog({ messages, members }: ScheduleCellDialogProps) { return ( <> - + {messages.day} - {messages.time} + {messages.time}
From 131cd2f4a27b6e6bde3524f960b1547d1ab565ff Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Wed, 30 Oct 2024 16:04:38 +0100 Subject: [PATCH 16/22] fix: add label to administrator menu button and fix formatting --- src/app/[locale]/(default)/shift-schedule/page.tsx | 4 ++-- src/components/shift-schedule/AdministratorMenu.tsx | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/app/[locale]/(default)/shift-schedule/page.tsx b/src/app/[locale]/(default)/shift-schedule/page.tsx index 24d8de4..06c79f6 100644 --- a/src/app/[locale]/(default)/shift-schedule/page.tsx +++ b/src/app/[locale]/(default)/shift-schedule/page.tsx @@ -29,9 +29,9 @@ export default async function ShiftSchedulePage({ return ( <> diff --git a/src/components/shift-schedule/AdministratorMenu.tsx b/src/components/shift-schedule/AdministratorMenu.tsx index 83fb98e..1445717 100644 --- a/src/components/shift-schedule/AdministratorMenu.tsx +++ b/src/components/shift-schedule/AdministratorMenu.tsx @@ -6,7 +6,6 @@ import { CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/Collapsible'; -import { Separator } from '@/components/ui/Separator'; import { ChevronDownIcon, ChevronUpIcon, Trash2Icon } from 'lucide-react'; import { useState } from 'react'; @@ -24,7 +23,7 @@ function AdministratorMenu({ messages }: AdministratorMenuProps) {
@@ -33,9 +32,15 @@ function AdministratorMenu({ messages }: AdministratorMenuProps) { From 782dc88e5ed0ecccfe0817e4b1077ea33d0277eb Mon Sep 17 00:00:00 2001 From: Sander Eikemo Andreassen Date: Thu, 31 Oct 2024 18:39:27 +0100 Subject: [PATCH 17/22] refactor: make changes requested in pr --- messages/en.json | 26 ++++----- messages/no.json | 18 +++--- .../(default)/shift-schedule/layout.tsx | 2 +- .../(default)/shift-schedule/loading.tsx | 20 ++++--- .../(default)/shift-schedule/page.tsx | 6 +- .../shift-schedule/AdministratorMenu.tsx | 28 ++++------ .../shift-schedule/RegisterSection.tsx | 6 +- .../shift-schedule/ScheduleCell.tsx | 56 +++++++++++++++---- .../shift-schedule/ScheduleCellContent.tsx | 51 ----------------- .../shift-schedule/ScheduleCellDialog.tsx | 22 ++++---- .../shift-schedule/ScheduleCellSkeleton.tsx | 13 ----- .../shift-schedule/ScheduleTable.tsx | 21 ++++--- 12 files changed, 122 insertions(+), 147 deletions(-) delete mode 100644 src/components/shift-schedule/ScheduleCellContent.tsx delete mode 100644 src/components/shift-schedule/ScheduleCellSkeleton.tsx diff --git a/messages/en.json b/messages/en.json index 88d112d..5591d57 100644 --- a/messages/en.json +++ b/messages/en.json @@ -137,24 +137,22 @@ "shiftSchedule": { "title": "Shift Schedule", "administratorMenu": { - "administratorMenu": "Administrator Menu", + "label": "Administrator Menu", + "open": "Open Administrator Menu", + "close": "Close Administrator Menu", "clearShiftSchedule": "Clear shift schedule" }, "scheduleTable": { "time": "Time", - "monday": "Monday", - "tuesday": "Tuesday", - "wednesday": "Wednesday", - "thursday": "Thursday", - "friday": "Friday", - "scheduleCellContent": { - "onShift": "{count, plural, =0 {Closed} =1 {1 person on shift} other {# people on shift}}" - }, - "scheduleCellDialog": { - "empty": "No-one on shift", - "registerSection": { - "recurring": "Recurring", - "register": "Register" + "day": "{day, select, monday {Monday} tuesday {Tuesday} wednesday {Wednesday} thursday {Thursday} other {Friday}}", + "scheduleCell": { + "onShift": "{count, plural, =0 {Closed} =1 {1 person on shift} other {# people on shift}}", + "scheduleCellDialog": { + "empty": "No-one on shift", + "registerSection": { + "recurring": "Recurring", + "register": "Register" + } } } } diff --git a/messages/no.json b/messages/no.json index ab6205d..dba8487 100644 --- a/messages/no.json +++ b/messages/no.json @@ -137,7 +137,9 @@ "shiftSchedule": { "title": "Vaktliste", "administratorMenu": { - "administratorMenu": "Administrator-meny", + "label": "Administrator-meny", + "open": "Åpne Administrator-meny", + "close": "Lukk Administrator-meny", "clearShiftSchedule": "Tøm vaktliste" }, "scheduleTable": { @@ -148,13 +150,13 @@ "thursday": "Torsdag", "friday": "Fredag", "scheduleCellContent": { - "onShift": "{count, plural, =0 {Stengt} =1 {1 person på vakt} other {# personer på vakt}}" - }, - "scheduleCellDialog": { - "empty": "Ingen på vakt", - "registerSection": { - "recurring": "Gjentagende", - "register": "Registrer" + "onShift": "{count, plural, =0 {Stengt} =1 {1 person på vakt} other {# personer på vakt}}", + "scheduleCellDialog": { + "empty": "Ingen på vakt", + "registerSection": { + "recurring": "Gjentagende", + "register": "Registrer" + } } } } diff --git a/src/app/[locale]/(default)/shift-schedule/layout.tsx b/src/app/[locale]/(default)/shift-schedule/layout.tsx index 7a65670..7425732 100644 --- a/src/app/[locale]/(default)/shift-schedule/layout.tsx +++ b/src/app/[locale]/(default)/shift-schedule/layout.tsx @@ -16,7 +16,7 @@ export default async function ShiftScheduleLayout({ return ( <> -

{t('title')}

+

{t('title')}

{children} ); diff --git a/src/app/[locale]/(default)/shift-schedule/loading.tsx b/src/app/[locale]/(default)/shift-schedule/loading.tsx index 0e98b95..e977e5e 100644 --- a/src/app/[locale]/(default)/shift-schedule/loading.tsx +++ b/src/app/[locale]/(default)/shift-schedule/loading.tsx @@ -1,4 +1,4 @@ -import { ScheduleCellSkeleton } from '@/components/shift-schedule/ScheduleCellSkeleton'; +import { Skeleton } from '@/components/ui/Skeleton'; import { Table, TableBody, @@ -10,9 +10,9 @@ import { } from '@/components/ui/Table'; import { useTranslations } from 'next-intl'; -export default function ShiftScheduleLoading() { +export default function ShiftScheduleLayout() { const t = useTranslations('shiftSchedule.scheduleTable'); - // Cannot use translation unless days and times are of these types + const days = [ 'monday', 'tuesday', @@ -36,14 +36,16 @@ export default function ShiftScheduleLoading() { {t('time')} - {t(day)} + {t('day', { day: day })} {times.map((time) => ( {time} - + + + ))} @@ -61,7 +63,7 @@ export default function ShiftScheduleLoading() { {t('time')} {days.map((day) => ( - {t(day)} + {t('day', { day: day })} ))} @@ -71,7 +73,9 @@ export default function ShiftScheduleLoading() { {time} {days.map((day) => ( - + + + ))} ))} @@ -80,4 +84,4 @@ export default function ShiftScheduleLoading() { ); -} +} \ No newline at end of file diff --git a/src/app/[locale]/(default)/shift-schedule/page.tsx b/src/app/[locale]/(default)/shift-schedule/page.tsx index 06c79f6..decde74 100644 --- a/src/app/[locale]/(default)/shift-schedule/page.tsx +++ b/src/app/[locale]/(default)/shift-schedule/page.tsx @@ -29,8 +29,10 @@ export default async function ShiftSchedulePage({ return ( <> diff --git a/src/components/shift-schedule/AdministratorMenu.tsx b/src/components/shift-schedule/AdministratorMenu.tsx index 1445717..6037a5f 100644 --- a/src/components/shift-schedule/AdministratorMenu.tsx +++ b/src/components/shift-schedule/AdministratorMenu.tsx @@ -10,13 +10,15 @@ import { ChevronDownIcon, ChevronUpIcon, Trash2Icon } from 'lucide-react'; import { useState } from 'react'; type AdministratorMenuProps = { - messages: { - administratorMenu: string; + t: { + label: string; + open: string; + close: string; clearShiftSchedule: string; }; }; -function AdministratorMenu({ messages }: AdministratorMenuProps) { +function AdministratorMenu({ t }: AdministratorMenuProps) { const [isOpen, setIsOpen] = useState(false); return ( @@ -25,30 +27,22 @@ function AdministratorMenu({ messages }: AdministratorMenuProps) { onOpenChange={setIsOpen} className='mx-auto xs:mx-8 my-8 rounded border p-3' > -
- - {messages.administratorMenu} - +
+ {t.label} -
+
diff --git a/src/components/shift-schedule/RegisterSection.tsx b/src/components/shift-schedule/RegisterSection.tsx index 46a68cf..5b6fd0a 100644 --- a/src/components/shift-schedule/RegisterSection.tsx +++ b/src/components/shift-schedule/RegisterSection.tsx @@ -6,17 +6,17 @@ import { useTranslations } from 'next-intl'; function RegisterSection({ className }: { className?: string }) { const t = useTranslations( - 'shiftSchedule.scheduleTable.scheduleCellDialog.registerSection', + 'shiftSchedule.scheduleTable.scheduleCell.scheduleCellDialog.registerSection', ); return ( -
+
-
+
); } diff --git a/src/components/shift-schedule/ScheduleCell.tsx b/src/components/shift-schedule/ScheduleCell.tsx index 304d077..15ba537 100644 --- a/src/components/shift-schedule/ScheduleCell.tsx +++ b/src/components/shift-schedule/ScheduleCell.tsx @@ -1,10 +1,12 @@ -import { ScheduleCellContent } from '@/components/shift-schedule/ScheduleCellContent'; import { ScheduleCellDialog } from '@/components/shift-schedule/ScheduleCellDialog'; import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/Dialog'; -import React from 'react'; +import { TableCell } from '@/components/ui/Table'; +import { cx } from '@/lib/utils'; +import { UserIcon, UsersIcon } from 'lucide-react'; +import { useFormatter, useTranslations } from 'next-intl'; type ScheduleCellProps = { - messages: { + tDialog: { day: string; time: string; }; @@ -13,16 +15,46 @@ type ScheduleCellProps = { }[]; }; -function ScheduleCell({ messages, members }: ScheduleCellProps) { +function ScheduleCell({ tDialog, members }: ScheduleCellProps) { + const t = useTranslations('shiftSchedule.scheduleTable.scheduleCell'); + return ( - - - - - - - - + + + +
+ {/* Icon displaying amount of people on shift */} + {members.length === 1 ? ( + + ) : members.length > 1 ? ( + + ) : ( + <> + )} +
+ {/* Amount of people on shift */} + {t('onShift', { count: members.length })} + {/* Skill icons */} + {members.length === 0 ? ( + <> + ) : ( +
[skill icons total]
+ )} +
+
+
+ + + +
+
); } diff --git a/src/components/shift-schedule/ScheduleCellContent.tsx b/src/components/shift-schedule/ScheduleCellContent.tsx deleted file mode 100644 index c65a63f..0000000 --- a/src/components/shift-schedule/ScheduleCellContent.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { TableCell } from '@/components/ui/Table'; -import { cx } from '@/lib/utils'; -import { UserIcon, UsersIcon } from 'lucide-react'; -import { useTranslations } from 'next-intl'; -import type React from 'react'; - -type ScheduleCellContentProps = { - members: { - name: string; - }[]; -}; - -function ScheduleCellContent({ members }: ScheduleCellContentProps) { - const t = useTranslations('shiftSchedule.scheduleTable.scheduleCellContent'); - - const colorStyle = - members.length === 0 - ? 'bg-accent/50 hover:bg-accent dark:bg-accent/40 dark:hover:bg-accent/60 text-accent-foreground' - : 'bg-foreground/20 hover:bg-foreground/25'; - - const skillIcons = - members.length === 0 ? ( - <> - ) : ( -
[skill icons total]
- ); - - const memberCount = {t('onShift', { count: members.length })}; - - const memberCountIconStyle = 'size-7'; - let memberCountIcon: React.ReactNode; - if (members.length === 1) { - memberCountIcon = ; - } else if (members.length > 1) { - memberCountIcon = ; - } - - return ( - -
- {memberCountIcon} -
- {memberCount} - {skillIcons} -
-
-
- ); -} - -export { ScheduleCellContent }; diff --git a/src/components/shift-schedule/ScheduleCellDialog.tsx b/src/components/shift-schedule/ScheduleCellDialog.tsx index 8f56d63..053ce90 100644 --- a/src/components/shift-schedule/ScheduleCellDialog.tsx +++ b/src/components/shift-schedule/ScheduleCellDialog.tsx @@ -3,7 +3,7 @@ import { DialogHeader, DialogTitle } from '@/components/ui/Dialog'; import { useTranslations } from 'next-intl'; type ScheduleCellDialogProps = { - messages: { + tDialog: { day: string; time: string; }; @@ -12,8 +12,10 @@ type ScheduleCellDialogProps = { }[]; }; -function ScheduleCellDialog({ messages, members }: ScheduleCellDialogProps) { - const t = useTranslations('shiftSchedule.scheduleTable.scheduleCellDialog'); +function ScheduleCellDialog({ tDialog, members }: ScheduleCellDialogProps) { + const t = useTranslations( + 'shiftSchedule.scheduleTable.scheduleCell.scheduleCellDialog', + ); let membersDisplay: React.ReactNode; @@ -21,29 +23,29 @@ function ScheduleCellDialog({ messages, members }: ScheduleCellDialogProps) { membersDisplay =

{t('empty')}

; } else { membersDisplay = ( -
+
{members.map((member) => (

{member.name}

[skill icons]
))} -
+