Files
jobtrackingapp/job-tracker-ui/src/components/DashboardWeek.tsx
T

47 lines
3.0 KiB
TypeScript

import React from 'react';
import { Box, Link, Typography } from '@mui/material';
import { Link as RouterLink } from 'react-router-dom';
import { useI18n } from '../i18n/I18nProvider';
import { PageSection } from '../layout/PageLayout';
import type { JobApplication } from '../types';
// Date-only deadlines remain local calendar dates; never infer a time from free text.
function storedDate(value: string | undefined): Date | null {
if (typeof value !== 'string') return null;
const parts = /^(\d{4})-(\d{2})-(\d{2})(?:$|T)/.exec(value);
if (!parts) return null;
const [year, month, day] = parts.slice(1).map(Number);
const calendar = new Date(0); calendar.setFullYear(year, month - 1, day); calendar.setHours(0, 0, 0, 0);
if (calendar.getFullYear() !== year || calendar.getMonth() !== month - 1 || calendar.getDate() !== day) return null;
if (value.length === 10) return calendar;
const time = /T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,7})?)?(?:Z|[+-]\d{2}:\d{2})?$/.exec(value);
if (!time || Number(time[1]) > 23 || Number(time[2]) > 59 || Number(time[3] ?? 0) > 59) return null;
const parsed = new Date(value);
return Number.isNaN(parsed.getTime()) ? null : parsed;
}
export default function DashboardWeek({ jobs }: { jobs: JobApplication[] }) {
const { t, language } = useI18n();
const start = new Date(); start.setHours(0, 0, 0, 0);
const end = new Date(start); end.setDate(end.getDate() + 7);
const events = jobs.flatMap(job => ([
{ kind: 'deadline', raw: job.deadline, label: t('dashboardWeekDeadline') },
{ kind: 'followUp', raw: job.followUpAt, label: t('dashboardWeekFollowUp') },
]).flatMap(event => {
const date = storedDate(event.raw);
return date && date >= start && date < end ? [{ ...event, raw: event.raw!, date, job }] : [];
})).sort((a, b) => a.date.getTime() - b.date.getTime());
const format = new Intl.DateTimeFormat(language === 'nb' ? 'nb-NO' : 'en-US', { weekday: 'short', month: 'short', day: 'numeric' });
return <PageSection title={t('dashboardYourWeek')} description={t('dashboardWeekScope')}
actions={<Link component={RouterLink} to="/reminders">{t('reminders')}</Link>}>
{events.length ? <Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: 'repeat(3, minmax(0, 1fr))' }, gap: 2 }}>
{events.slice(0, 3).map(event => <Box key={`${event.job.id}:${event.kind}`} sx={{ borderInlineStart: '3px solid', borderColor: 'primary.main', pl: 1.5, minWidth: 0 }}>
<Typography variant="caption" component="time" dateTime={event.raw}>{format.format(event.date)}</Typography>
<Typography variant="body2" sx={{ fontWeight: 700 }}>{event.label}</Typography>
<Link component={RouterLink} to={`/jobs/${event.job.id}`} sx={{ display: 'inline-block', py: 1, overflowWrap: 'anywhere' }}>{event.job.jobTitle}</Link>
<Typography variant="body2" color="text.secondary">{event.job.company?.name}</Typography>
</Box>)}
</Box> : <Typography color="text.secondary">{t('dashboardWeekEmpty')}</Typography>}
</PageSection>;
}