fix: mobile modal UX improvements (delete confirm, browser-back, z-index) (#206)
- Replace browser confirm() with ConfirmModal for delete confirmation - Add dedicated history entry for delete dialog so browser back dismisses it - Track unsaved-changes warning source to restore correct context on cancel - Add overlayClassName prop to ConfirmModal for nested z-index layering - Add .nested-confirm CSS class for proper modal stacking - Add i18n keys for delete confirmation dialog (EN + DE) Closes #202
This commit is contained in:
@@ -93,6 +93,9 @@ export function MedicationsPage() {
|
||||
const closeConfirmedRef = useRef(false);
|
||||
// Confirmation modal for unsaved changes
|
||||
const [showUnsavedConfirm, setShowUnsavedConfirm] = useState(false);
|
||||
const [unsavedConfirmSource, setUnsavedConfirmSource] = useState<"mobile-edit" | "desktop-form" | null>(null);
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [deleteCandidate, setDeleteCandidate] = useState<Medication | null>(null);
|
||||
|
||||
// Calculate total tablets
|
||||
const totalTablets = useMemo(() => {
|
||||
@@ -119,6 +122,7 @@ export function MedicationsPage() {
|
||||
if (showEditModal) {
|
||||
// Check for unsaved changes before closing
|
||||
if (formChanged) {
|
||||
setUnsavedConfirmSource("mobile-edit");
|
||||
setShowUnsavedConfirm(true);
|
||||
return;
|
||||
}
|
||||
@@ -131,6 +135,7 @@ export function MedicationsPage() {
|
||||
// Handle confirmed close (user clicked "Leave" in confirmation modal)
|
||||
function handleConfirmClose() {
|
||||
setShowUnsavedConfirm(false);
|
||||
setUnsavedConfirmSource(null);
|
||||
closeConfirmedRef.current = true;
|
||||
hasUnsavedHistoryState.current = false;
|
||||
if (showEditModal) {
|
||||
@@ -143,6 +148,10 @@ export function MedicationsPage() {
|
||||
// Handle cancelled close (user clicked "Stay" in confirmation modal)
|
||||
function handleCancelClose() {
|
||||
setShowUnsavedConfirm(false);
|
||||
if (unsavedConfirmSource === "mobile-edit") {
|
||||
setShowEditModal(true);
|
||||
}
|
||||
setUnsavedConfirmSource(null);
|
||||
}
|
||||
|
||||
// Helper to reset form and clear history state
|
||||
@@ -156,10 +165,26 @@ export function MedicationsPage() {
|
||||
setViewMode("grid");
|
||||
}
|
||||
|
||||
// Handle delete medication
|
||||
async function handleDeleteMed(id: number) {
|
||||
if (!confirm(t("medications.deleteConfirm"))) return;
|
||||
await deleteMed(id, editingId, resetForm);
|
||||
function requestDeleteMed(med: Medication) {
|
||||
setDeleteCandidate(med);
|
||||
setShowDeleteConfirm(true);
|
||||
window.history.pushState({ modal: "delete-confirm" }, "");
|
||||
}
|
||||
|
||||
async function handleConfirmDelete() {
|
||||
if (!deleteCandidate) return;
|
||||
await deleteMed(deleteCandidate.id, editingId, resetForm);
|
||||
setShowDeleteConfirm(false);
|
||||
setDeleteCandidate(null);
|
||||
// Pop the delete-confirm history entry
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
function handleCancelDelete() {
|
||||
setShowDeleteConfirm(false);
|
||||
setDeleteCandidate(null);
|
||||
// Pop the delete-confirm history entry
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
// Handle submit refill
|
||||
@@ -284,6 +309,13 @@ export function MedicationsPage() {
|
||||
// Handle browser back button for modals and unsaved changes
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
// Delete confirmation is open — dismiss it and stay where we are
|
||||
if (showDeleteConfirm) {
|
||||
setShowDeleteConfirm(false);
|
||||
setDeleteCandidate(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// If close was already confirmed programmatically, allow navigation
|
||||
if (closeConfirmedRef.current) {
|
||||
closeConfirmedRef.current = false;
|
||||
@@ -301,6 +333,7 @@ export function MedicationsPage() {
|
||||
// Re-push history state to stay in modal
|
||||
window.history.pushState({ modal: "edit" }, "");
|
||||
// Show confirmation modal
|
||||
setUnsavedConfirmSource("mobile-edit");
|
||||
setShowUnsavedConfirm(true);
|
||||
return;
|
||||
}
|
||||
@@ -314,12 +347,13 @@ export function MedicationsPage() {
|
||||
// Re-push history state to stay on page
|
||||
window.history.pushState({ unsavedChanges: true }, "");
|
||||
// Show confirmation modal
|
||||
setUnsavedConfirmSource("desktop-form");
|
||||
setShowUnsavedConfirm(true);
|
||||
}
|
||||
};
|
||||
window.addEventListener("popstate", handlePopState);
|
||||
return () => window.removeEventListener("popstate", handlePopState);
|
||||
}, [showEditModal, formChanged, resetForm]);
|
||||
}, [showDeleteConfirm, showEditModal, formChanged, resetForm]);
|
||||
|
||||
// Close modal on Escape key
|
||||
useEffect(() => {
|
||||
@@ -444,10 +478,12 @@ export function MedicationsPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="med-actions">
|
||||
<button className="info" onClick={() => handleEditClick(med)}>
|
||||
{t("common.edit")}
|
||||
</button>
|
||||
<button className="danger" onClick={() => handleDeleteMed(med.id)}>
|
||||
{editingId !== med.id && (
|
||||
<button className="info" onClick={() => handleEditClick(med)}>
|
||||
{t("common.edit")}
|
||||
</button>
|
||||
)}
|
||||
<button className="danger" onClick={() => requestDeleteMed(med)}>
|
||||
{t("common.delete")}
|
||||
</button>
|
||||
</div>
|
||||
@@ -547,10 +583,12 @@ export function MedicationsPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="med-actions">
|
||||
<button className="info" onClick={() => handleEditClick(med)}>
|
||||
{t("common.edit")}
|
||||
</button>
|
||||
<button className="danger" onClick={() => handleDeleteMed(med.id)}>
|
||||
{editingId !== med.id && (
|
||||
<button className="info" onClick={() => handleEditClick(med)}>
|
||||
{t("common.edit")}
|
||||
</button>
|
||||
)}
|
||||
<button className="danger" onClick={() => requestDeleteMed(med)}>
|
||||
{t("common.delete")}
|
||||
</button>
|
||||
</div>
|
||||
@@ -1145,10 +1183,27 @@ export function MedicationsPage() {
|
||||
title={t("common.unsavedChanges.title", "Unsaved Changes")}
|
||||
message={t("common.unsavedChanges.message")}
|
||||
confirmLabel={t("common.unsavedChanges.leave", "Leave")}
|
||||
cancelLabel={t("common.unsavedChanges.stay", "Stay")}
|
||||
cancelLabel={
|
||||
unsavedConfirmSource === "mobile-edit" ? t("common.back") : t("common.unsavedChanges.stay", "Stay")
|
||||
}
|
||||
onConfirm={handleConfirmClose}
|
||||
onCancel={handleCancelClose}
|
||||
confirmVariant="danger"
|
||||
overlayClassName={showEditModal ? "nested-confirm" : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Delete Medication Confirmation Modal */}
|
||||
{showDeleteConfirm && deleteCandidate && (
|
||||
<ConfirmModal
|
||||
title={t("medications.deleteModal.title")}
|
||||
message={t("medications.deleteModal.message", { name: deleteCandidate.name })}
|
||||
confirmLabel={t("common.delete")}
|
||||
cancelLabel={t("common.cancel")}
|
||||
onConfirm={handleConfirmDelete}
|
||||
onCancel={handleCancelDelete}
|
||||
confirmVariant="danger"
|
||||
overlayClassName={showEditModal ? "nested-confirm" : undefined}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user