diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..e06221c --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Bulk reformats. `git config blame.ignoreRevsFile .git-blame-ignore-revs` +2294ac57371ee75c597505bf4a3a4f5c7e1834ef # prettier, 304 files diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 29dcf88..f814320 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -19,10 +19,10 @@ name: Auto-merge on: workflow_run: - workflows: ['CI'] + workflows: ["CI"] types: [completed] schedule: - - cron: '*/10 * * * *' + - cron: "*/10 * * * *" workflow_dispatch: {} # Declared on the CALLER as well as inside the reusable workflow: a called @@ -46,4 +46,4 @@ jobs: # SPACE-separated: the sweep word-splits this. A comma would become one # bogus token, every dispatch would fail, and the only symptom would be # that nothing deploys — while the sweep still reported success. - rearm_workflows: 'ci.yml deploy.yml' + rearm_workflows: "ci.yml deploy.yml" diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1ed5ea5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,21 @@ +# Build output and vendored trees — formatting these is noise. +node_modules +.next +dist +build +out +coverage +.turbo +.vercel +*.min.js +*.min.css + +# Lockfiles are generated; prettier would rewrite them wholesale. +package-lock.json +pnpm-lock.yaml +yarn.lock + +# Markdown is deliberately out of scope for now. Prettier rewraps prose, which +# is where it is most opinionated and least useful, and it would bury the real +# diff. Remove this line when you want docs formatted too. +*.md diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a2f11f0 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": true, + "singleQuote": false, + "printWidth": 100, + "tabWidth": 2, + "trailingComma": "all", + "arrowParens": "always", + "endOfLine": "lf" +} diff --git a/app/(admin)/admin.module.css b/app/(admin)/admin.module.css index de35a01..65b860c 100644 --- a/app/(admin)/admin.module.css +++ b/app/(admin)/admin.module.css @@ -67,7 +67,9 @@ color: var(--white-60); text-decoration: none; font-size: 0.82rem; - transition: background var(--transition-fast), color var(--transition-fast); + transition: + background var(--transition-fast), + color var(--transition-fast); display: flex; align-items: center; gap: 0.65rem; @@ -142,7 +144,6 @@ background: var(--warn); } - .mainWrap { composes: mainWrap from "../shared.module.css"; } @@ -163,7 +164,6 @@ composes: main from "../shared.module.css"; } - /* Shared admin UI — geometry from app/shared.module.css */ .pageTitle { composes: pageTitle from "../shared.module.css"; @@ -181,9 +181,15 @@ composes: cardMb from "../shared.module.css"; } -.checkboxRow { composes: checkboxRow from "../shared.module.css"; } -.checkboxInput { composes: checkboxInput from "../shared.module.css"; } -.checkboxHint { composes: checkboxHint from "../shared.module.css"; } +.checkboxRow { + composes: checkboxRow from "../shared.module.css"; +} +.checkboxInput { + composes: checkboxInput from "../shared.module.css"; +} +.checkboxHint { + composes: checkboxHint from "../shared.module.css"; +} .table { width: 100%; @@ -310,12 +316,31 @@ margin-top: 0.2rem; } -.phasePill[data-phase="intake"] { color: var(--muted); background: var(--muted-10); } -.phasePill[data-phase="assessment"] { color: var(--teal); background: var(--teal-10); } -.phasePill[data-phase="planning"] { color: var(--warn); background: var(--warn-10); } -.phasePill[data-phase="active"] { color: var(--teal); background: var(--teal-15); font-weight: 600; } -.phasePill[data-phase="review"] { color: var(--warn); background: var(--warn-10); } -.phasePill[data-phase="completed"] { color: var(--muted); background: var(--muted-8); } +.phasePill[data-phase="intake"] { + color: var(--muted); + background: var(--muted-10); +} +.phasePill[data-phase="assessment"] { + color: var(--teal); + background: var(--teal-10); +} +.phasePill[data-phase="planning"] { + color: var(--warn); + background: var(--warn-10); +} +.phasePill[data-phase="active"] { + color: var(--teal); + background: var(--teal-15); + font-weight: 600; +} +.phasePill[data-phase="review"] { + color: var(--warn); + background: var(--warn-10); +} +.phasePill[data-phase="completed"] { + color: var(--muted); + background: var(--muted-8); +} /* ─── Sparkline ──────────────────────────────────────────────────────────────── */ @@ -333,9 +358,15 @@ flex-shrink: 0; } -.sparkDot[data-level="low"] { background: var(--danger); } -.sparkDot[data-level="mid"] { background: var(--warn); } -.sparkDot[data-level="high"] { background: var(--teal); } +.sparkDot[data-level="low"] { + background: var(--danger); +} +.sparkDot[data-level="mid"] { + background: var(--warn); +} +.sparkDot[data-level="high"] { + background: var(--teal); +} /* ─── Card label (used in section headers within detail cards) ─────────────── */ @@ -903,7 +934,7 @@ } .stretchedLink::after { - content: ''; + content: ""; position: absolute; inset: 0; z-index: 0; @@ -1010,7 +1041,10 @@ cursor: pointer; text-transform: capitalize; letter-spacing: 0.04em; - transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast); + transition: + background var(--transition-fast), + color var(--transition-fast), + border-color var(--transition-fast); } .filterTab:hover, @@ -1903,9 +1937,18 @@ /* Tone is the whole design of this screen: a section that shouts must be one the reader can trust, so only two of the five are allowed to. */ -.inboxTitleUrgent { composes: inboxTitle; color: var(--danger); } -.inboxTitleAttention { composes: inboxTitle; color: var(--warn); } -.inboxTitleRoutine { composes: inboxTitle; color: var(--muted); } +.inboxTitleUrgent { + composes: inboxTitle; + color: var(--danger); +} +.inboxTitleAttention { + composes: inboxTitle; + color: var(--warn); +} +.inboxTitleRoutine { + composes: inboxTitle; + color: var(--muted); +} .inboxCount { font-size: 0.7rem; diff --git a/app/(admin)/admin/bookings/page.tsx b/app/(admin)/admin/bookings/page.tsx index ec3d5f4..17941fe 100644 --- a/app/(admin)/admin/bookings/page.tsx +++ b/app/(admin)/admin/bookings/page.tsx @@ -4,16 +4,24 @@ import { useState, useEffect, useCallback } from "react"; import Link from "next/link"; import styles from "../../admin.module.css"; import { - BOOKING_STATUS, BOOKING_STATUS_CONFIG, BOOKING_STATUS_VALUES, - BOOKING_TYPE_CONFIG, MACHINE_TYPE_CONFIG, - type BookingRowWithUser, type BookingStatus, + BOOKING_STATUS, + BOOKING_STATUS_CONFIG, + BOOKING_STATUS_VALUES, + BOOKING_TYPE_CONFIG, + MACHINE_TYPE_CONFIG, + type BookingRowWithUser, + type BookingStatus, } from "@/lib/config/booking-status"; import { BookingActions } from "@/components/clinical/BookingActions"; -import { formatDateShort, formatDateNumeric, formatSlotDay, formatSlotTime } from "@/lib/utils/format"; +import { + formatDateShort, + formatDateNumeric, + formatSlotDay, + formatSlotTime, +} from "@/lib/utils/format"; import { ADMIN_ROUTES } from "@/lib/config/routes"; import { LoadingState } from "@/components/LoadingState"; - // "upcoming" answers the clinician's daily question — who am I seeing next? — // and is the default: slot bookings are born confirmed, so the old "pending" // default showed an empty list on a normal day. @@ -33,7 +41,10 @@ export default function AdminBookingsPage() { const load = useCallback(async () => { try { const res = await fetch("/api/bookings"); - if (!res.ok) { setLoadError(true); return; } + if (!res.ok) { + setLoadError(true); + return; + } const data = await res.json(); setBookings(data.data ?? []); } catch { @@ -43,7 +54,9 @@ export default function AdminBookingsPage() { } }, []); - useEffect(() => { load(); }, [load]); + useEffect(() => { + load(); + }, [load]); // Clears the admin nav's "new bookings" badge — fire-and-forget, the same // "I looked at this page" signal the patient portal's goals page sends. @@ -72,7 +85,12 @@ export default function AdminBookingsPage() { const now = Date.now(); const upcoming = bookings - .filter((b) => b.scheduledAt && new Date(b.scheduledAt).getTime() >= now && ACTIVE_STATUSES.includes(b.status)) + .filter( + (b) => + b.scheduledAt && + new Date(b.scheduledAt).getTime() >= now && + ACTIVE_STATUSES.includes(b.status), + ) .sort((a, b) => new Date(a.scheduledAt!).getTime() - new Date(b.scheduledAt!).getTime()); const filtered = filter === "upcoming" @@ -100,7 +118,9 @@ export default function AdminBookingsPage() { went unused: nobody had connected a real calendar. */}
Working hours, intake and calendar sync now live on{" "} - My Profile → + + My Profile → +
Patient documents and lab results
-