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

81 lines
4.6 KiB
TypeScript

import { useEffect, useState } from "react";
import { Alert, Box, Button, CircularProgress, Stack, Typography } from "@mui/material";
import CalendarMonthOutlinedIcon from "@mui/icons-material/CalendarMonthOutlined";
import DownloadOutlinedIcon from "@mui/icons-material/DownloadOutlined";
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
import { api, getApiErrorMessage } from "../api";
import { useI18n } from "../i18n/I18nProvider";
type ProviderStatus = { connected: boolean; writable: boolean };
type CalendarStatus = { google: ProviderStatus; microsoft: ProviderStatus };
type EventKind = "follow-up" | "deadline";
type CalendarEventResult = { provider: string; id?: string; webUrl?: string };
export default function ApplicationCalendarActions({ jobId, followUpAt, deadline }: { jobId: number; followUpAt?: string | null; deadline?: string | null }) {
const { t } = useI18n();
const [status, setStatus] = useState<CalendarStatus | null>(null);
const [busy, setBusy] = useState("");
const [error, setError] = useState("");
const [created, setCreated] = useState<CalendarEventResult | null>(null);
useEffect(() => {
let active = true;
api.get<CalendarStatus>("/calendar/status")
.then((response) => { if (active) setStatus(response.data); })
.catch(() => { if (active) setStatus({ google: { connected: false, writable: false }, microsoft: { connected: false, writable: false } }); });
return () => { active = false; };
}, []);
const create = async (provider: "google" | "microsoft", kind: EventKind) => {
setBusy(`${provider}-${kind}`); setError(""); setCreated(null);
try {
const response = await api.post<CalendarEventResult>(`/calendar/jobs/${jobId}/events`, { provider, kind });
setCreated(response.data);
} catch (err) { setError(getApiErrorMessage(err, t("calendarCreateFailed"))); }
finally { setBusy(""); }
};
const download = async (kind: EventKind) => {
setBusy(`ics-${kind}`); setError("");
try {
const response = await api.get(`/calendar/jobs/${jobId}/events.ics`, { params: { kind }, responseType: "blob" });
const url = URL.createObjectURL(response.data);
const anchor = document.createElement("a");
anchor.href = url; anchor.download = `jobjakt-${jobId}-${kind}.ics`; anchor.click();
URL.revokeObjectURL(url);
} catch (err) { setError(getApiErrorMessage(err, t("calendarDownloadFailed"))); }
finally { setBusy(""); }
};
const rows: Array<{ kind: EventKind; title: string }> = [
...(followUpAt ? [{ kind: "follow-up" as const, title: t("calendarFollowUp") }] : []),
...(deadline ? [{ kind: "deadline" as const, title: t("calendarDeadline") }] : []),
];
if (rows.length === 0) return <Alert severity="info">{t("calendarNoDates")}</Alert>;
if (!status) return <Stack direction="row" spacing={1} alignItems="center"><CircularProgress size={18} /><Typography variant="body2">{t("loading")}</Typography></Stack>;
return (
<Stack spacing={1.5}>
<Typography variant="body2" color="text.secondary">{t("calendarHelp")}</Typography>
{(status.google.connected && !status.google.writable) || (status.microsoft.connected && !status.microsoft.writable) ? (
<Alert severity="info" action={<Button color="inherit" size="small" href="/settings">{t("calendarReconnect")}</Button>}>{t("calendarPermissionNeeded")}</Alert>
) : null}
{error ? <Alert severity="error">{error}</Alert> : null}
{created ? (
<Alert severity="success" action={created.webUrl ? <Button color="inherit" size="small" href={created.webUrl} target="_blank" rel="noreferrer" endIcon={<OpenInNewIcon />}>{t("calendarOpen")}</Button> : undefined}>
{t("calendarCreated")}
</Alert>
) : null}
{rows.map((row) => (
<Box key={row.kind} sx={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 1, p: 1.25, border: 1, borderColor: "divider", borderRadius: 2 }}>
<Typography variant="body2" sx={{ fontWeight: 750, flex: "1 1 180px" }}>{row.title}</Typography>
{status.google.writable ? <Button size="small" startIcon={<CalendarMonthOutlinedIcon />} disabled={Boolean(busy)} onClick={() => void create("google", row.kind)}>{t("calendarGoogle")}</Button> : null}
{status.microsoft.writable ? <Button size="small" startIcon={<CalendarMonthOutlinedIcon />} disabled={Boolean(busy)} onClick={() => void create("microsoft", row.kind)}>{t("calendarOutlook")}</Button> : null}
<Button size="small" startIcon={<DownloadOutlinedIcon />} disabled={Boolean(busy)} onClick={() => void download(row.kind)}>{t("calendarDownload")}</Button>
</Box>
))}
</Stack>
);
}