fix: react-doctor dialog/a11y/labels → 68/100 with 973 warnings
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
@@ -12,6 +12,20 @@ import {
|
||||
} from "@/actions/water-log/admin";
|
||||
import { AdminButton } from "@/components/admin/design-system";
|
||||
|
||||
async function openPrintWindow(html: string) {
|
||||
const blob = new Blob([html], { type: "text/html;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const w = window.open(url, "_blank", "width=700,height=700");
|
||||
if (w) {
|
||||
// Revoke the blob URL once the new window has had time to load the document.
|
||||
w.addEventListener("load", () => URL.revokeObjectURL(url), { once: true });
|
||||
// Safety net: revoke after 60s even if the load event never fires.
|
||||
setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
||||
} else {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
type Headgate = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -122,6 +136,22 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
|
||||
|
||||
const [deletingId, setDeletingId] = useState<string | null>(null);
|
||||
|
||||
const editDialogRef = useRef<HTMLDialogElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const dialog = editDialogRef.current;
|
||||
if (!dialog) return;
|
||||
if (!dialog.open) dialog.showModal();
|
||||
const onCancel = (e: Event) => {
|
||||
e.preventDefault();
|
||||
setEditHg(null);
|
||||
};
|
||||
dialog.addEventListener("cancel", onCancel);
|
||||
return () => {
|
||||
dialog.removeEventListener("cancel", onCancel);
|
||||
};
|
||||
}, [editHg]);
|
||||
|
||||
async function handleDelete(hg: Headgate) {
|
||||
if (!window.confirm(`Delete headgate "${hg.name}"? Existing log entries will be preserved.`)) return;
|
||||
setDeletingId(hg.id);
|
||||
@@ -171,21 +201,8 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
async function openPrintWindow(html: string) {
|
||||
const blob = new Blob([html], { type: "text/html;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const w = window.open(url, "_blank", "width=700,height=700");
|
||||
if (w) {
|
||||
// Revoke the blob URL once the new window has had time to load the document.
|
||||
w.addEventListener("load", () => URL.revokeObjectURL(url), { once: true });
|
||||
// Safety net: revoke after 60s even if the load event never fires.
|
||||
setTimeout(() => URL.revokeObjectURL(url), 60_000);
|
||||
} else {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<div className="min-h-screen bg-[var(--admin-bg)]">
|
||||
{/* Header */}
|
||||
<div className="bg-white border-b border-[var(--admin-border)] px-6 py-4">
|
||||
@@ -371,17 +388,12 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
|
||||
|
||||
{/* Edit Modal */}
|
||||
{editHg && (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
<dialog
|
||||
ref={editDialogRef}
|
||||
aria-label="Edit Headgate"
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
||||
onClick={() => setEditHg(null)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") setEditHg(null);
|
||||
}}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 m-0 p-0 max-w-none max-h-none w-full h-full backdrop:bg-black/50"
|
||||
>
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-sm mx-4 overflow-hidden border border-[var(--admin-border)]" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-sm mx-4 overflow-hidden border border-[var(--admin-border)]">
|
||||
<div className="flex items-center justify-between px-5 py-4 border-b border-[var(--admin-border)]">
|
||||
<p className="font-bold text-[var(--admin-text-primary)]">Edit Headgate</p>
|
||||
<button type="button" onClick={() => setEditHg(null)} className="text-[var(--admin-text-muted)] hover:text-[var(--admin-text-primary)]">
|
||||
@@ -461,7 +473,7 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
)}
|
||||
|
||||
{/* QR Modal */}
|
||||
@@ -475,6 +487,21 @@ export default function HeadgatesManager({ initialHeadgates, brandId }: Props) {
|
||||
function QRModal({ hg, onClose, onRegenerate }: { hg: Headgate; onClose: () => void; onRegenerate: (hg: Headgate) => void }) {
|
||||
const [tab, setTab] = useState<"preview" | "print" | "download">("preview");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const dialogRef = useRef<HTMLDialogElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const dialog = dialogRef.current;
|
||||
if (!dialog) return;
|
||||
if (!dialog.open) dialog.showModal();
|
||||
const onCancel = (e: Event) => {
|
||||
e.preventDefault();
|
||||
onClose();
|
||||
};
|
||||
dialog.addEventListener("cancel", onCancel);
|
||||
return () => {
|
||||
dialog.removeEventListener("cancel", onCancel);
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
const code = hg.name.replace(/\s+/g, "-").toUpperCase().slice(0, 6) + "-1";
|
||||
const qrUrl = `/api/water-qr?token=${hg.headgate_token}&size=360`;
|
||||
@@ -520,17 +547,12 @@ function QRModal({ hg, onClose, onRegenerate }: { hg: Headgate; onClose: () => v
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
<dialog
|
||||
ref={dialogRef}
|
||||
aria-label="Headgate details"
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
||||
onClick={onClose}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
}}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 m-0 p-0 max-w-none max-h-none w-full h-full backdrop:bg-black/50"
|
||||
>
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-sm mx-4 overflow-hidden border border-[var(--admin-border)]" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-sm mx-4 overflow-hidden border border-[var(--admin-border)]">
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-5 py-4 border-b border-[var(--admin-border)]">
|
||||
@@ -616,6 +638,6 @@ function QRModal({ hg, onClose, onRegenerate }: { hg: Headgate; onClose: () => v
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user