Deletion verification modal
This commit is contained in:
Binary file not shown.
83
ui/src/components/ConfirmDialog.tsx
Normal file
83
ui/src/components/ConfirmDialog.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
type ConfirmDialogProps = {
|
||||||
|
open: boolean;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
contextText?: string;
|
||||||
|
confirmLabel: string;
|
||||||
|
cancelLabel: string;
|
||||||
|
busy?: boolean;
|
||||||
|
busyLabel?: string;
|
||||||
|
onConfirm: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ConfirmDialog({
|
||||||
|
open,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
contextText,
|
||||||
|
confirmLabel,
|
||||||
|
cancelLabel,
|
||||||
|
busy = false,
|
||||||
|
busyLabel,
|
||||||
|
onConfirm,
|
||||||
|
onCancel,
|
||||||
|
}: ConfirmDialogProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return;
|
||||||
|
|
||||||
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === "Escape" && !busy) {
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", onKeyDown);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", onKeyDown);
|
||||||
|
};
|
||||||
|
}, [open, busy, onCancel]);
|
||||||
|
|
||||||
|
if (!open) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-50 flex items-center justify-center bg-[#2B170A]/60 px-4"
|
||||||
|
onClick={() => {
|
||||||
|
if (!busy) onCancel();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
className="w-full max-w-md rounded-xl border border-[#C99763] bg-[#F5D1A9] p-5 shadow-xl"
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
>
|
||||||
|
<h3 className="text-lg font-semibold text-[#4C250E]">{title}</h3>
|
||||||
|
{description && <p className="mt-2 text-sm text-[#70421E]">{description}</p>}
|
||||||
|
{contextText && <p className="mt-1 text-sm font-medium text-[#4C250E]">{contextText}</p>}
|
||||||
|
|
||||||
|
<div className="mt-4 flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onCancel}
|
||||||
|
disabled={busy}
|
||||||
|
className="rounded-md border border-[#A56C38] bg-[#EED5B8] px-3 py-1.5 text-sm text-[#70421E] hover:bg-[#E3A977] disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{cancelLabel}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onConfirm}
|
||||||
|
disabled={busy}
|
||||||
|
className="rounded-md border border-[#70421E] bg-[#8E4F24] px-3 py-1.5 text-sm text-[#FFF7EE] hover:bg-[#70421E] disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{busy ? busyLabel ?? confirmLabel : confirmLabel}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
updateLokOpenHours,
|
updateLokOpenHours,
|
||||||
type LokOpenHours,
|
type LokOpenHours,
|
||||||
} from "~/api";
|
} from "~/api";
|
||||||
|
import ConfirmDialog from "~/components/ConfirmDialog";
|
||||||
import { useT } from "~/i18n";
|
import { useT } from "~/i18n";
|
||||||
import { openHoursAtom, toastsAtom, type Toast } from "~/state/appState";
|
import { openHoursAtom, toastsAtom, type Toast } from "~/state/appState";
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ export default function OpenHoursForm() {
|
|||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const [editingVersionId, setEditingVersionId] = useState("");
|
const [editingVersionId, setEditingVersionId] = useState("");
|
||||||
const [deletingId, setDeletingId] = useState("");
|
const [deletingId, setDeletingId] = useState("");
|
||||||
|
const [confirmDeleteVersion, setConfirmDeleteVersion] = useState<LokOpenHours | null>(null);
|
||||||
const [activatingId, setActivatingId] = useState("");
|
const [activatingId, setActivatingId] = useState("");
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [form, setForm] = useState<FormState>(EMPTY_FORM);
|
const [form, setForm] = useState<FormState>(EMPTY_FORM);
|
||||||
@@ -217,6 +219,21 @@ export default function OpenHoursForm() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const askDeleteConfirmation = (version: LokOpenHours) => {
|
||||||
|
setConfirmDeleteVersion(version);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelDeleteConfirmation = () => {
|
||||||
|
if (deletingId) return;
|
||||||
|
setConfirmDeleteVersion(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
if (!confirmDeleteVersion) return;
|
||||||
|
await onDelete(confirmDeleteVersion);
|
||||||
|
setConfirmDeleteVersion(null);
|
||||||
|
};
|
||||||
|
|
||||||
const onSetActive = async (version: LokOpenHours) => {
|
const onSetActive = async (version: LokOpenHours) => {
|
||||||
if (version.isActive) return;
|
if (version.isActive) return;
|
||||||
|
|
||||||
@@ -328,7 +345,7 @@ export default function OpenHoursForm() {
|
|||||||
disabled={active || deleting}
|
disabled={active || deleting}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
void onDelete(version);
|
askDeleteConfirmation(version);
|
||||||
}}
|
}}
|
||||||
className={`rounded-md border border-[#8E4F24] bg-[#EED5B8] px-3 py-1.5 text-sm text-[#70421E] disabled:cursor-not-allowed disabled:opacity-50 ${!active ? "hover:bg-[#E3A977]" : ""
|
className={`rounded-md border border-[#8E4F24] bg-[#EED5B8] px-3 py-1.5 text-sm text-[#70421E] disabled:cursor-not-allowed disabled:opacity-50 ${!active ? "hover:bg-[#E3A977]" : ""
|
||||||
}`}
|
}`}
|
||||||
@@ -416,6 +433,21 @@ export default function OpenHoursForm() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
open={Boolean(confirmDeleteVersion)}
|
||||||
|
title={t("home.openHours.deleteConfirmTitle")}
|
||||||
|
description={t("home.openHours.deleteConfirmMessage")}
|
||||||
|
contextText={confirmDeleteVersion?.name || t("home.openHours.latest")}
|
||||||
|
cancelLabel={t("home.openHours.cancel")}
|
||||||
|
confirmLabel={t("home.openHours.deleteConfirmAction")}
|
||||||
|
busy={Boolean(deletingId)}
|
||||||
|
busyLabel={t("home.openHours.deleting")}
|
||||||
|
onCancel={cancelDeleteConfirmation}
|
||||||
|
onConfirm={() => {
|
||||||
|
void confirmDelete();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ const translations = {
|
|||||||
"home.openHours.saved": "New version saved",
|
"home.openHours.saved": "New version saved",
|
||||||
"home.openHours.updated": "Version updated",
|
"home.openHours.updated": "Version updated",
|
||||||
"home.openHours.deleted": "Version deleted",
|
"home.openHours.deleted": "Version deleted",
|
||||||
|
"home.openHours.deleting": "Deleting...",
|
||||||
|
"home.openHours.deleteConfirmTitle": "Delete version?",
|
||||||
|
"home.openHours.deleteConfirmMessage": "This action cannot be undone.",
|
||||||
|
"home.openHours.deleteConfirmAction": "Delete version",
|
||||||
"about.title": "About",
|
"about.title": "About",
|
||||||
"about.description":
|
"about.description":
|
||||||
"Livonsaaren Tietokonepaja is a local project providing IT services for our dear archipelago.",
|
"Livonsaaren Tietokonepaja is a local project providing IT services for our dear archipelago.",
|
||||||
@@ -125,6 +129,10 @@ const translations = {
|
|||||||
"home.openHours.saved": "Uusi versio tallennettu",
|
"home.openHours.saved": "Uusi versio tallennettu",
|
||||||
"home.openHours.updated": "Versio päivitetty",
|
"home.openHours.updated": "Versio päivitetty",
|
||||||
"home.openHours.deleted": "Versio poistettu",
|
"home.openHours.deleted": "Versio poistettu",
|
||||||
|
"home.openHours.deleting": "Poistetaan...",
|
||||||
|
"home.openHours.deleteConfirmTitle": "Poistetaanko versio?",
|
||||||
|
"home.openHours.deleteConfirmMessage": "Tätä toimintoa ei voi perua.",
|
||||||
|
"home.openHours.deleteConfirmAction": "Poista versio",
|
||||||
"about.title": "Tietoja",
|
"about.title": "Tietoja",
|
||||||
"about.description":
|
"about.description":
|
||||||
"Livonsaaren Tietokonepaja on paikallisprojekti, joka tuottaa IT-palveluita rakkaalle lähisaaristollemme.",
|
"Livonsaaren Tietokonepaja on paikallisprojekti, joka tuottaa IT-palveluita rakkaalle lähisaaristollemme.",
|
||||||
@@ -207,6 +215,11 @@ const translations = {
|
|||||||
"home.openHours.saved": "Nová verzia uložená",
|
"home.openHours.saved": "Nová verzia uložená",
|
||||||
"home.openHours.updated": "Verzia bola aktualizovaná",
|
"home.openHours.updated": "Verzia bola aktualizovaná",
|
||||||
"home.openHours.deleted": "Verzia bola odstránená",
|
"home.openHours.deleted": "Verzia bola odstránená",
|
||||||
|
"home.openHours.deleting": "Odstraňuje sa...",
|
||||||
|
"home.openHours.deleteConfirmTitle": "Odstrániť verziu?",
|
||||||
|
"home.openHours.deleteConfirmMessage":
|
||||||
|
"Túto akciu nie je možné vrátiť späť.",
|
||||||
|
"home.openHours.deleteConfirmAction": "Odstrániť verziu",
|
||||||
"about.title": "O aplikácii",
|
"about.title": "O aplikácii",
|
||||||
"about.description":
|
"about.description":
|
||||||
"Livonsaaren Tietokonepaja je lokálny projekt poskytujúci IT služby pre naše milované súostrovie.",
|
"Livonsaaren Tietokonepaja je lokálny projekt poskytujúci IT služby pre naše milované súostrovie.",
|
||||||
|
|||||||
Reference in New Issue
Block a user