81 lines
4.6 KiB
TypeScript
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>
|
|
);
|
|
}
|