mirror of
https://github.com/temetro/temetro.git
synced 2026-08-30 04:09:05 +00:00
frontend: clear the 99 lint errors
`npm run lint` reported 99 errors and 26 warnings across 65 files and had presumably been failing for a while — next.config.ts sets eslint.ignoreDuringBuilds, so the build never surfaced it. 68 were in vendored code: components/charts and components/ai-elements, pulled from upstream registries. Re-linting those reports upstream's style back at us, and "fixing" them means diverging and eating conflicts on every update — ai-elements already has exactly this carve-out on the TypeScript side via ignoreBuildErrors. Ignore both, plus the two registry files in components/ui (carousel publishes its api from an effect; the sidebar skeleton picks a random width). The other 31 were ours, nearly all react-hooks/set-state-in-effect on the same shape: an effect that re-seeds form state when a dialog opens or a selection changes. Moved to render-phase adjustment, which is both what React recommends and a real fix — the effect version paints one frame of the *previous* record's values before correcting itself. Two carried sharper bugs: the employee dialog could keep a typed password across a switch to another member, and use-wallet-sync could carry `linked` over to a newly-selected patient, briefly offering to push a record to someone else's wallet. The rest: useIsMobile and speech-support detection become useSyncExternalStore (correct on first paint, no mount flash); refs mirroring state are written in effects rather than during render; the care-team fetch moves into its effect behind a reload key, dropping an exhaustive-deps suppression. Two effects in chat-panel keep the rule disabled with a reason. Both are what effects are for: draining the queued-message buffer when the transport goes idle, and resolving ?thread from the URL — the latter mints an id with nanoid(), so moving it into render would just trade this error for a purity one. Also removes a dead /explore-era import and unused directives found on the way. lint now exits 0, with the ai-elements tsc carve-out unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -79,8 +79,12 @@ export function ImportFromWalletDialog({
|
||||
}
|
||||
};
|
||||
|
||||
// Reset everything whenever the dialog is (re)opened.
|
||||
useEffect(() => {
|
||||
// Reset everything whenever the dialog is (re)opened. Adjusted during render
|
||||
// so a reopened dialog never flashes the previous import's wallet number or
|
||||
// state before clearing.
|
||||
const [prevOpen, setPrevOpen] = useState(false);
|
||||
if (prevOpen !== open) {
|
||||
setPrevOpen(open);
|
||||
if (open) {
|
||||
setMode("number");
|
||||
setWalletNumber("");
|
||||
@@ -92,8 +96,10 @@ export function ImportFromWalletDialog({
|
||||
setPairUri(null);
|
||||
setReviewOpen(false);
|
||||
}
|
||||
return stopPolling;
|
||||
}, [open]);
|
||||
}
|
||||
|
||||
// Stop polling when the dialog closes or unmounts.
|
||||
useEffect(() => stopPolling, [open]);
|
||||
|
||||
// Poll the request until the patient approves/denies on their device.
|
||||
useEffect(() => {
|
||||
|
||||
@@ -102,14 +102,29 @@ export function PatientDetailSheet({
|
||||
// Bumped on open so the editor remounts with the latest patient data.
|
||||
const [editKey, setEditKey] = useState(0);
|
||||
|
||||
// Clear the previous chart the moment a different one is opened, so the sheet
|
||||
// can't show one patient's data under another's name while the fetch is in
|
||||
// flight. Adjusted during render rather than in the effects below, which are
|
||||
// only for the fetching itself. Keyed on the role too, so losing clinical
|
||||
// access drops the wallet state instead of leaving the "Push update" button
|
||||
// lit from the previous render.
|
||||
const chart = open ? `${fileNumber ?? ""}|${role ?? ""}` : null;
|
||||
const [prevChart, setPrevChart] = useState<string | null>(null);
|
||||
if (prevChart !== chart) {
|
||||
setPrevChart(chart);
|
||||
setWalletLinked(false);
|
||||
if (chart) {
|
||||
setStatus("loading");
|
||||
setPatient(null);
|
||||
setPrescriptions([]);
|
||||
setAppointments([]);
|
||||
setInvoices([]);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !fileNumber) return;
|
||||
let active = true;
|
||||
setStatus("loading");
|
||||
setPatient(null);
|
||||
setPrescriptions([]);
|
||||
setAppointments([]);
|
||||
setInvoices([]);
|
||||
getPatient(fileNumber)
|
||||
.then((data) => {
|
||||
if (!active) return;
|
||||
@@ -138,9 +153,10 @@ export function PatientDetailSheet({
|
||||
|
||||
// Whether this patient is wallet-linked (drives the "Push update" button).
|
||||
// Separate from the main load so it re-checks once the role resolves without
|
||||
// refetching the record. Only clinicians can push.
|
||||
// refetching the record. Only clinicians can push. The reset rides on the
|
||||
// same render-phase clear as the chart above, so the button can't stay lit
|
||||
// from the previous patient.
|
||||
useEffect(() => {
|
||||
setWalletLinked(false);
|
||||
if (!open || !fileNumber || !hasClinicalAccess(role)) return;
|
||||
let active = true;
|
||||
getWalletLink(fileNumber)
|
||||
|
||||
@@ -76,9 +76,10 @@ export function PatientsView() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
|
||||
// Runs once on mount, and `loading` already starts true, so there's nothing
|
||||
// to flip synchronously here.
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
setLoading(true);
|
||||
listPatients()
|
||||
.then((data) => {
|
||||
if (!active) return;
|
||||
@@ -97,6 +98,9 @@ export function PatientsView() {
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
// `t` intentionally omitted: it is only read to build a failure message,
|
||||
// and re-fetching on a language change would be pointless.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const q = query.trim().toLowerCase();
|
||||
|
||||
@@ -44,10 +44,19 @@ export function TransferPatientDialog({
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Re-seed the form each time the dialog opens. Adjusted during render so it
|
||||
// never paints the previous patient's provider.
|
||||
const [prevOpen, setPrevOpen] = useState(false);
|
||||
if (prevOpen !== open) {
|
||||
setPrevOpen(open);
|
||||
if (open) {
|
||||
setProviderId(patient.primaryProviderId ?? "");
|
||||
setError(null);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setProviderId(patient.primaryProviderId ?? "");
|
||||
setError(null);
|
||||
let active = true;
|
||||
listProviders()
|
||||
.then((list) => active && setProviders(list))
|
||||
|
||||
Reference in New Issue
Block a user