47 lines
3.0 KiB
TypeScript
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>;
|
|
}
|