Files
app/src/admin/components/PlanCellModal.tsx

391 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import FormModal from "./FormModal";
import FormField from "./FormField";
import AdminDatePicker from "./AdminDatePicker";
import ConfirmModal from "./ConfirmModal";
import { ResolvedCell } from "../lib/queries/plan";
interface Project {
id: number;
name: string;
}
export type PlanCellModalMode =
| { kind: "closed" }
| { kind: "create"; userId: number; date: string }
| {
kind: "edit-range";
entryId: number;
userId: number;
date: string;
range: {
date_from: string;
date_to: string;
project_id: number | null;
category: string;
note: string;
};
}
| {
kind: "edit-override";
overrideId: number;
userId: number;
date: string;
cell: ResolvedCell;
}
| {
kind: "day-in-range";
entryId: number;
userId: number;
date: string;
range: {
date_from: string;
date_to: string;
project_id: number | null;
category: string;
note: string;
};
}
| { kind: "view"; userId: number; date: string; cell: ResolvedCell };
interface Props {
open: boolean;
mode: PlanCellModalMode;
projects: Project[];
onClose: () => void;
onSaveEntry: (body: any) => Promise<void>;
onUpdateEntry: (id: number, body: any) => Promise<void>;
onDeleteEntry: (id: number) => Promise<void>;
onSaveOverride: (body: any) => Promise<void>;
onUpdateOverride: (id: number, body: any) => Promise<void>;
onDeleteOverride: (id: number) => Promise<void>;
onCreateOverrideFromRange: (
entryId: number,
userId: number,
date: string,
) => Promise<void>;
}
const CATEGORIES = [
{ value: "work", label: "Práce" },
{ value: "preparation", label: "Příprava" },
{ value: "travel", label: "Cesta / Montáž" },
{ value: "leave", label: "Dovolená" },
{ value: "sick", label: "Nemoc" },
{ value: "training", label: "Školení" },
{ value: "other", label: "Jiné" },
];
export default function PlanCellModal(props: Props) {
const { open, mode, onClose } = props;
if (!open || mode.kind === "closed") return null;
if (mode.kind === "view") return <ViewModal {...props} />;
if (mode.kind === "day-in-range")
return <DayInRangeModal {...props} mode={mode} />;
return <EditForm {...props} />;
}
function EditForm(props: Props) {
const {
mode,
onClose,
onSaveEntry,
onUpdateEntry,
onDeleteEntry,
onSaveOverride,
onUpdateOverride,
onDeleteOverride,
projects,
} = props;
const [submitting, setSubmitting] = useState(false);
const [confirmDelete, setConfirmDelete] = useState(false);
const isOverride = mode.kind === "edit-override";
const initial = (() => {
if (mode.kind === "create") {
return {
date_from: mode.date,
date_to: mode.date,
is_range: false,
project_id: null as number | null,
category: "work",
note: "",
};
}
if (mode.kind === "edit-range") {
return {
date_from: mode.range.date_from,
date_to: mode.range.date_to,
is_range: mode.range.date_from !== mode.range.date_to,
project_id: mode.range.project_id,
category: mode.range.category,
note: mode.range.note,
};
}
return {
date_from: mode.date,
date_to: mode.date,
is_range: false,
project_id: mode.cell.project_id,
category: mode.cell.category,
note: mode.cell.note,
};
})();
const [dateFrom, setDateFrom] = useState(initial.date_from);
const [dateTo, setDateTo] = useState(initial.date_to);
const [isRange, setIsRange] = useState(initial.is_range);
const [projectId, setProjectId] = useState<number | null>(initial.project_id);
const [category, setCategory] = useState(initial.category);
const [note, setNote] = useState(initial.note);
const title =
mode.kind === "create"
? "Nový záznam plánu"
: mode.kind === "edit-range"
? "Upravit rozsah"
: "Upravit přepsání dne";
const handleSubmit = async () => {
setSubmitting(true);
try {
if (mode.kind === "create") {
const userId = mode.userId;
await onSaveEntry({
user_id: userId,
date_from: dateFrom,
date_to: isRange ? dateTo : dateFrom,
project_id: projectId,
category,
note,
});
} else if (mode.kind === "edit-range") {
await onUpdateEntry(mode.entryId, {
date_from: dateFrom,
date_to: isRange ? dateTo : dateFrom,
project_id: projectId,
category,
note,
});
} else {
await onUpdateOverride(mode.overrideId, {
project_id: projectId,
category,
note,
});
}
onClose();
} finally {
setSubmitting(false);
}
};
const handleDelete = async () => {
setSubmitting(true);
try {
if (mode.kind === "edit-range") {
await onDeleteEntry(mode.entryId);
} else if (mode.kind === "edit-override") {
await onDeleteOverride(mode.overrideId);
}
onClose();
} finally {
setSubmitting(false);
}
};
return (
<>
<FormModal
isOpen
title={title}
onClose={onClose}
onSubmit={handleSubmit}
submitLabel={mode.kind === "create" ? "Vytvořit" : "Uložit"}
loading={submitting}
>
{mode.kind !== "create" && (
<div
style={{
marginBottom: 12,
display: "flex",
justifyContent: "flex-end",
}}
>
<button
type="button"
className="admin-btn admin-btn-danger"
onClick={() => setConfirmDelete(true)}
disabled={submitting}
>
Smazat
</button>
</div>
)}
<FormField label="Datum od">
<AdminDatePicker
value={dateFrom}
onChange={setDateFrom}
disabled={isOverride}
/>
</FormField>
{!isOverride && (
<FormField label="Rozsah dnů">
<label>
<input
type="checkbox"
checked={isRange}
onChange={(e) => setIsRange(e.target.checked)}
/>{" "}
Více dní
</label>
</FormField>
)}
{!isOverride && isRange && (
<FormField label="Datum do">
<AdminDatePicker value={dateTo} onChange={setDateTo} />
</FormField>
)}
<FormField label="Projekt">
<select
value={projectId ?? ""}
onChange={(e) =>
setProjectId(e.target.value ? Number(e.target.value) : null)
}
>
<option value=""> bez projektu </option>
{projects.map((p) => (
<option key={p.id} value={p.id}>
{p.name}
</option>
))}
</select>
</FormField>
<FormField label="Kategorie">
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
>
{CATEGORIES.map((c) => (
<option key={c.value} value={c.value}>
{c.label}
</option>
))}
</select>
</FormField>
<FormField label="Text poznámky" required>
<textarea
value={note}
onChange={(e) => setNote(e.target.value)}
maxLength={500}
rows={3}
required
/>
</FormField>
</FormModal>
<ConfirmModal
isOpen={confirmDelete}
title="Smazat záznam plánu?"
message="Tato akce je nevratná (záznam bude soft-delete)."
confirmText="Smazat"
type="danger"
confirmVariant="danger"
loading={submitting}
onClose={() => setConfirmDelete(false)}
onConfirm={handleDelete}
/>
</>
);
}
function DayInRangeModal(
props: Props & { mode: Extract<PlanCellModalMode, { kind: "day-in-range" }> },
) {
const { mode, onClose, onCreateOverrideFromRange } = props;
return (
<FormModal
isOpen
title={`Den ${mode.date} je součástí rozsahu`}
onClose={onClose}
hideFooter
>
<p>
Tento den je součástí rozsahu:{" "}
<strong>
{mode.range.date_from} {mode.range.date_to}
</strong>
</p>
<p>Co chcete udělat?</p>
<ul>
<li>
Upravit celý rozsah klikněte na tlačítko níže nebo zavřete a
klikněte znovu na buňku v režimu editace rozsahu.
</li>
<li>
Upravit pouze tento den vytvoří přepsání (override) pro toto datum.
</li>
<li>Zavřít ponechá rozsah beze změny.</li>
</ul>
<div
style={{
marginTop: 12,
display: "flex",
gap: 8,
justifyContent: "flex-end",
}}
>
<button
type="button"
className="admin-btn admin-btn-secondary"
onClick={onClose}
>
Zavřít
</button>
<button
type="button"
className="admin-btn admin-btn-primary"
onClick={async () => {
await onCreateOverrideFromRange(
mode.entryId,
mode.userId,
mode.date,
);
onClose();
}}
>
Upravit pouze tento den
</button>
</div>
</FormModal>
);
}
function ViewModal(props: Props) {
const { mode, onClose } = props;
if (mode.kind !== "view") return null;
const c = mode.cell;
return (
<FormModal isOpen title="Detail plánu" onClose={onClose} hideFooter>
<p>
<strong>Datum:</strong> {c.shift_date}
</p>
<p>
<strong>Kategorie:</strong> {c.category}
</p>
<p>
<strong>Projekt:</strong> {c.project_id ?? "—"}
</p>
<p>
<strong>Text:</strong> {c.note}
</p>
{c.rangeFrom && c.rangeTo && (
<p>
<strong>Patří do rozsahu:</strong> {c.rangeFrom} {c.rangeTo}
</p>
)}
</FormModal>
);
}