Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Shift schedule page #69

Merged
merged 25 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b6ec04b
feat: create shift schedule page, layout, and table
seandreassen Oct 20, 2024
9bd2ccf
feat: add recurring checkbox and sections to display skill icons
seandreassen Oct 21, 2024
7b27078
feat: move register section to component and improve styling
seandreassen Oct 21, 2024
588ed0a
feat: make table scale for smaller screens and improve styling
seandreassen Oct 24, 2024
cdae953
refactor: improve scaling on small screens
seandreassen Oct 25, 2024
569f9ba
feat: add skeleton for schedule table
seandreassen Oct 26, 2024
d2979b5
feat: add administrator menu to clear shift schedule
seandreassen Oct 27, 2024
1631083
refactor: improve code quality by adding changes requested in pr
seandreassen Oct 28, 2024
4a62834
Merge branch 'dev' into shift-schedule-page
seandreassen Oct 28, 2024
51d47fc
style: fix formatting for lint and format workflow
seandreassen Oct 28, 2024
b184826
style: add 1 empty line to pass lint and format workflow
seandreassen Oct 28, 2024
dc167fc
feat: make administrator menu collapsible and less distracting
seandreassen Oct 28, 2024
4e9f6df
Merge branch 'dev' into shift-schedule-page
seandreassen Oct 28, 2024
f660846
style: add 1 space to pass lint and format workflow
seandreassen Oct 28, 2024
fb846a8
refactor: change collapsible arrow direction when opening or closing …
seandreassen Oct 28, 2024
8e4944d
feat: display day and time of shift in dialog
seandreassen Oct 29, 2024
22c4241
fix: make dialog title fit within dialog on small screens
seandreassen Oct 29, 2024
7d6bdc5
Merge branch 'dev' into shift-schedule-page
seandreassen Oct 30, 2024
131cd2f
fix: add label to administrator menu button and fix formatting
seandreassen Oct 30, 2024
782dc88
refactor: make changes requested in pr
seandreassen Oct 31, 2024
56147b3
refactor: change display of timeslot based on language
seandreassen Oct 31, 2024
07a57ce
fix: fix lighthouse error
seandreassen Oct 31, 2024
8f70617
refactor: add changes requested in pr
seandreassen Nov 4, 2024
0118dd1
refactor: change table spacing to use classname instead of index
seandreassen Nov 7, 2024
08d0705
refactor: move aria label and remove unnecessary span
seandreassen Nov 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions lighthouserc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const config = {
'http://localhost:3000/en/news/1',
'http://localhost:3000/en/storage',
'http://localhost:3000/en/storage/shopping-cart',
'http://localhost:3000/en/shift-schedule',
],
startServerCommand: 'bun run start',
},
Expand Down
24 changes: 24 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"events": "Events",
"storage": "Storage",
"about": "About",
"shiftSchedule": "Shift Schedule",
"changeLocale": "Change language",
"toggleTheme": "Toggle theme",
"light": "Light",
Expand Down Expand Up @@ -132,5 +133,28 @@
"returnByDescription": "Select how long you would like to borrow the item for.",
"submit": "Submit"
}
},
"shiftSchedule": {
"title": "Shift Schedule",
"administratorMenu": {
"label": "Administrator Menu",
"open": "Open Administrator Menu",
"close": "Close Administrator Menu",
"clearShiftSchedule": "Clear shift schedule"
},
"scheduleTable": {
"time": "Time",
"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"
}
}
}
}
}
}
24 changes: 24 additions & 0 deletions messages/no.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"events": "Hendelser",
"storage": "Lager",
"about": "Om oss",
"shiftSchedule": "Vaktliste",
"changeLocale": "Bytt språk",
"toggleTheme": "Bytt tema",
"light": "Lys",
Expand Down Expand Up @@ -132,5 +133,28 @@
"returnByDescription": "Velg hvor lenge du ønsker å låne gjenstanden(e)",
"submit": "Send"
}
},
"shiftSchedule": {
"title": "Vaktliste",
"administratorMenu": {
"label": "Administrator-meny",
"open": "Åpne Administrator-meny",
"close": "Lukk Administrator-meny",
"clearShiftSchedule": "Tøm vaktliste"
},
"scheduleTable": {
"time": "Tid",
"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",
"registerSection": {
"recurring": "Gjentagende",
"register": "Registrer"
}
}
}
}
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"@aws-sdk/client-s3": "^3.679.0",
"@lucia-auth/adapter-drizzle": "^1.1.0",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
Expand Down
23 changes: 23 additions & 0 deletions src/app/[locale]/(default)/shift-schedule/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getTranslations, setRequestLocale } from 'next-intl/server';

type ShiftScheduleLayoutProps = {
children: React.ReactNode;
params: Promise<{ locale: string }>;
};

export default async function ShiftScheduleLayout({
params,
children,
}: ShiftScheduleLayoutProps) {
const { locale } = await params;

setRequestLocale(locale);
const t = await getTranslations('shiftSchedule');

return (
<>
<h1 className='text-center'>{t('title')}</h1>
{children}
</>
);
}
126 changes: 126 additions & 0 deletions src/app/[locale]/(default)/shift-schedule/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { Skeleton } from '@/components/ui/Skeleton';
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/Table';
import { useFormatter, useTranslations } from 'next-intl';

export default function ShiftScheduleLayout() {
const t = useTranslations('shiftSchedule.scheduleTable');
const format = useFormatter();

const days = [
'monday',
'tuesday',
'wednesday',
'thursday',
'friday',
] 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 (
<>
{/* Table shown on small screens */}
<div className='sm:hidden [&>div]:mt-8'>
{days.map((day) => (
<Table key={day}>
<TableHeader>
<TableRow>
<TableHead className='w-2/5'>{t('time')}</TableHead>
<TableHead className='w-3/5 border-x'>
{t('day', { day: day })}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{timeslots.map((timeslot) => (
<TableRow key={timeslot}>
<TableCell className='border-y'>
{getDateTimeRange(timeslot)}
</TableCell>
<TableCell key={day} className='h-20 min-w-52 border p-1.5'>
<Skeleton className='size-full' />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
))}
<Table>
<TableCaption>[skill icons legend]</TableCaption>
</Table>
</div>

{/* Table shown on all other screens */}
<Table className='mt-8 hidden sm:table'>
<TableHeader>
<TableRow>
<TableHead className='w-1/6'>{t('time')}</TableHead>
{days.map((day) => (
<TableHead key={day} className='w-1/6 border-x'>
{t('day', { day: day })}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{timeslots.map((timeslot) => (
<TableRow key={timeslot}>
<TableCell className='min-w-32 border-y'>
{getDateTimeRange(timeslot)}
</TableCell>
{days.map((day) => (
<TableCell key={day} className='h-20 min-w-52 border p-1.5'>
<Skeleton className='size-full' />
</TableCell>
))}
</TableRow>
))}
</TableBody>
<TableCaption className='h-12'>[skill icons legend]</TableCaption>
</Table>
</>
);
}
42 changes: 42 additions & 0 deletions src/app/[locale]/(default)/shift-schedule/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AdministratorMenu } from '@/components/shift-schedule/AdministratorMenu';
import { ScheduleTable } from '@/components/shift-schedule/ScheduleTable';
import { shiftScheduleMockData } from '@/mock-data/shiftSchedule';
import { getTranslations, setRequestLocale } from 'next-intl/server';

export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'layout' });

return {
title: t('shiftSchedule'),
};
}

export default async function ShiftSchedulePage({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;

setRequestLocale(locale);
const t = await getTranslations('shiftSchedule');

return (
<>
<AdministratorMenu
t={{
label: t('administratorMenu.label'),
open: t('administratorMenu.open'),
close: t('administratorMenu.close'),
clearShiftSchedule: t('administratorMenu.clearShiftSchedule'),
}}
/>
<ScheduleTable week={shiftScheduleMockData} />
</>
);
}
52 changes: 52 additions & 0 deletions src/components/shift-schedule/AdministratorMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use client';

import { Button } from '@/components/ui/Button';
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/Collapsible';
import { ChevronDownIcon, ChevronUpIcon, Trash2Icon } from 'lucide-react';
import { useState } from 'react';

type AdministratorMenuProps = {
t: {
label: string;
open: string;
close: string;
clearShiftSchedule: string;
};
};

function AdministratorMenu({ t }: AdministratorMenuProps) {
const [isOpen, setIsOpen] = useState(false);

return (
<Collapsible
open={isOpen}
onOpenChange={setIsOpen}
className='mx-auto xs:mx-8 mt-8 rounded border p-3'
>
<div className='mx-1 flex justify-between'>
<span className='my-auto font-semibold text-xl'>{t.label}</span>
<CollapsibleTrigger asChild>
<Button variant='ghost' aria-label={isOpen ? t.close : t.open}>
{isOpen ? (
<ChevronUpIcon aria-hidden='true' className='size-4' />
) : (
<ChevronDownIcon aria-hidden='true' className='size-4' />
)}
</Button>
</CollapsibleTrigger>
</div>
<CollapsibleContent className='mt-2'>
<Button variant='link' className='flex gap-3 text-destructive'>
<Trash2Icon />
{t.clearShiftSchedule}
</Button>
</CollapsibleContent>
</Collapsible>
);
}

export { AdministratorMenu };
23 changes: 23 additions & 0 deletions src/components/shift-schedule/RegisterShift.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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 RegisterShift({ className }: { className?: string }) {
const t = useTranslations(
'shiftSchedule.scheduleTable.scheduleCell.scheduleCellDialog.registerSection',
);

return (
<div className={cx(className, 'space-y-3')}>
<section className='flex gap-2'>
<Label htmlFor='recurring'>{t('recurring')}: </Label>
<Checkbox id='recurring' />
</section>
<Button className='float-right'>{t('register')}</Button>
</div>
);
}

export { RegisterShift };
Loading