diff --git a/frontend/src/app/(dashboard)/appointments/page.tsx b/frontend/src/app/(dashboard)/appointments/page.tsx index ead192c..1f6a85b 100644 --- a/frontend/src/app/(dashboard)/appointments/page.tsx +++ b/frontend/src/app/(dashboard)/appointments/page.tsx @@ -14,9 +14,10 @@ import { AppointmentScheduleGrid } from '@/components/ui/appointments/Appointmen import { AppointmentsPatientSearch } from '@/components/ui/appointments/AppointmentsPatientSearch'; import { AppointmentScheduleLegend } from '@/components/ui/appointments/AppointmentScheduleLegend'; import { ScheduleDayPicker } from '@/components/ui/common/ScheduleDayPicker'; +import { Toast } from '@/components/ui/common/Toast'; import type { AppointmentPurpose } from '@/types/appointment'; import { formatApiErrorMessage } from '@/lib/formatApiError'; -import { getLocalDayIsoRange, startOfLocalDay } from '@/lib/appointmentTime'; +import { compareLocalDayStart, getLocalDayIsoRange, startOfLocalDay } from '@/lib/appointmentTime'; const EMPTY_PATIENT_FORM: CreatePatientInput = { firstName: '', @@ -47,6 +48,7 @@ export default function AppointmentsPage() { const [bookingHour, setBookingHour] = useState(9); const [bookingProviderId, setBookingProviderId] = useState(null); const [bookingProviderName, setBookingProviderName] = useState(''); + const [editingAppointmentId, setEditingAppointmentId] = useState(null); const [savingAppointment, setSavingAppointment] = useState(false); const [toastError, setToastError] = useState(''); @@ -57,6 +59,14 @@ export default function AppointmentsPage() { const canEditPatients = hasPermission(currentOrganization, 'TAB_PATIENTS_EDIT'); const todayStart = useMemo(() => startOfLocalDay(new Date()), []); + const isViewingPastDay = useMemo( + () => compareLocalDayStart(scheduleDate, todayStart) < 0, + [scheduleDate, todayStart], + ); + const activeEditingAppointment = useMemo( + () => appointments.find((a) => a.id === editingAppointmentId) ?? null, + [appointments, editingAppointmentId], + ); const scheduleLoadGen = useRef(0); @@ -154,6 +164,12 @@ export default function AppointmentsPage() { } function handleSlotClick(hour: number, providerUserId: string, providerName: string) { + if (isViewingPastDay) { + setToastSuccess(''); + setToastError(''); + setToastInfo('Past appointments are view-only.'); + return; + } if (!selectedPatient) { setToastSuccess(''); setToastError(''); @@ -163,6 +179,22 @@ export default function AppointmentsPage() { setBookingHour(hour); setBookingProviderId(providerUserId); setBookingProviderName(providerName); + setEditingAppointmentId(null); + setBookingOpen(true); + } + + function handleAppointmentClick(appointment: AppointmentRecord) { + if (isViewingPastDay) { + setToastSuccess(''); + setToastError(''); + setToastInfo('Past appointments are view-only.'); + return; + } + const provider = providers.find((p) => p.userId === appointment.providerUserId); + setBookingHour(new Date(appointment.startAt).getHours()); + setBookingProviderId(appointment.providerUserId); + setBookingProviderName(provider?.name ?? bookingProviderName); + setEditingAppointmentId(appointment.id); setBookingOpen(true); } @@ -178,15 +210,22 @@ export default function AppointmentsPage() { setToastSuccess(''); setToastInfo(''); try { - await appointmentsApi.create(payload); + if (activeEditingAppointment) { + await appointmentsApi.update(activeEditingAppointment.id, payload); + } else { + await appointmentsApi.create(payload); + } setBookingOpen(false); - setToastSuccess('Appointment saved.'); + setEditingAppointmentId(null); + setToastSuccess(activeEditingAppointment ? 'Appointment updated.' : 'Appointment saved.'); await loadSchedule(); } catch (err: unknown) { const message = err && typeof err === 'object' && 'message' in err ? String((err as { message: unknown }).message) - : 'Could not save appointment.'; + : activeEditingAppointment + ? 'Could not update appointment.' + : 'Could not save appointment.'; setToastError(message); } finally { setSavingAppointment(false); @@ -280,20 +319,15 @@ export default function AppointmentsPage() { )} - {scheduleError && ( -
- {scheduleError} -
- )} - void handleDeleteAppointment(id)} onSlotClick={(hour, uid, name) => handleSlotClick(hour, uid, name)} + onAppointmentClick={(apt) => handleAppointmentClick(apt)} /> @@ -305,7 +339,11 @@ export default function AppointmentsPage() { providerUserId={bookingProviderId} providerName={bookingProviderName} initialHour={bookingHour} - onClose={() => setBookingOpen(false)} + editingAppointment={activeEditingAppointment} + onClose={() => { + setBookingOpen(false); + setEditingAppointmentId(null); + }} onSubmit={handleSaveAppointment} loading={savingAppointment} /> @@ -323,24 +361,13 @@ export default function AppointmentsPage() { )} - {(toastError || toastSuccess || toastInfo) && ( -
-
- {toastError && ( -
- {toastError} -
- )} - {toastInfo && ( -
- {toastInfo} -
- )} - {toastSuccess && ( -
- {toastSuccess} -
- )} + {(scheduleError || toastError || toastSuccess || toastInfo) && ( +
+
+ {scheduleError && {scheduleError}} + {toastError && {toastError}} + {toastInfo && {toastInfo}} + {toastSuccess && {toastSuccess}}
)} diff --git a/frontend/src/app/(dashboard)/billing/page.tsx b/frontend/src/app/(dashboard)/billing/page.tsx index 6464010..b28de1c 100644 --- a/frontend/src/app/(dashboard)/billing/page.tsx +++ b/frontend/src/app/(dashboard)/billing/page.tsx @@ -4,6 +4,7 @@ import { useState } from 'react'; import { Pencil } from 'lucide-react'; import { Button } from '@/components/ui/common/Button'; import { Badge } from '@/components/ui/common/Badge'; +import { Card } from '@/components/ui/common/Card'; import { Table } from '@/components/ui/common/Table'; import { SearchBar } from '@/components/ui/common/SearchBar'; import { useAuth } from '@/lib/hooks/useAuth'; @@ -195,19 +196,19 @@ export default function BillingPage() { } function StatCard({ title, count, amount, color }: StatCardProps) { const colors: Record = { - blue: 'bg-sky-900/30 text-sky-300 border-sky-700/60', - yellow: 'bg-amber-900/30 text-amber-300 border-amber-700/60', - green: 'bg-emerald-900/30 text-emerald-300 border-emerald-700/60', - red: 'bg-red-950/30 text-red-300 border-red-700/60', + blue: '!bg-purpose-visit-bg !text-purpose-visit-fg !border-purpose-visit-border', + yellow: '!bg-badge-warning-bg !text-badge-warning-fg !border-badge-warning-border', + green: '!bg-badge-success-bg !text-badge-success-fg !border-badge-success-border', + red: '!bg-badge-danger-bg !text-badge-danger-fg !border-badge-danger-border', }; return ( -
-

{title}

+ +

{title}

{count}

${amount.toLocaleString()}

-
+ ); } diff --git a/frontend/src/app/(dashboard)/settings/subscriptions/page.tsx b/frontend/src/app/(dashboard)/settings/subscriptions/page.tsx index e5ab6b1..e6f6450 100644 --- a/frontend/src/app/(dashboard)/settings/subscriptions/page.tsx +++ b/frontend/src/app/(dashboard)/settings/subscriptions/page.tsx @@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation'; import { useAuth } from '@/lib/hooks/useAuth'; import { authApi } from '@/lib/api/auth'; import { Button } from '@/components/ui/common/Button'; +import { Toast } from '@/components/ui/common/Toast'; import type { SubscriptionAlertData } from '@/types/subscription'; const PLAN_OPTIONS = [ @@ -69,7 +70,7 @@ export default function SubscriptionsSettingsPage() { : 'text-red-400'; return ( -
+
Start purchase process - {purchaseNotice && ( -
-

{purchaseNotice}

-
- )}
+ + {purchaseNotice && ( +
+
+ {purchaseNotice} +
+
+ )}
); } diff --git a/frontend/src/app/(dashboard)/today/page.tsx b/frontend/src/app/(dashboard)/today/page.tsx index 5ac0d34..7ac754e 100644 --- a/frontend/src/app/(dashboard)/today/page.tsx +++ b/frontend/src/app/(dashboard)/today/page.tsx @@ -2,6 +2,7 @@ import Link from 'next/link'; import { useAuth } from '@/lib/hooks/useAuth'; +import { Card } from '@/components/ui/common/Card'; export default function TodayPage() { const { currentOrganization } = useAuth(); @@ -27,29 +28,26 @@ export default function TodayPage() { )}
- - - - + +

Today's Appointments

+

12

+

Monday 2/5/2026

+
+ +

Active Patients

+

675

+
+ +

New Lab Case

+

5

+

35 ↑

+
+ +

Today invoices

+

1200$

+

21,300 $

+
); -} - -function Card({ - title, - value, - sub, -}: { - title: string; - value: string; - sub?: string; -}) { - return ( -
-

{title}

-

{value}

- {sub &&

{sub}

} -
- ); } \ No newline at end of file diff --git a/frontend/src/components/ui/appointments/AppointmentBookingModal.tsx b/frontend/src/components/ui/appointments/AppointmentBookingModal.tsx index d5f4903..770f753 100644 --- a/frontend/src/components/ui/appointments/AppointmentBookingModal.tsx +++ b/frontend/src/components/ui/appointments/AppointmentBookingModal.tsx @@ -4,11 +4,12 @@ import { useEffect, useState } from 'react'; import { X } from 'lucide-react'; import { Button } from '@/components/ui/common/Button'; import { Dropdown } from '@/components/ui/common/Dropdown'; -import type { AppointmentPurpose } from '@/types/appointment'; +import type { AppointmentPurpose, AppointmentRecord } from '@/types/appointment'; import { APPOINTMENT_PURPOSE_LABEL } from '@/components/ui/appointments/appointmentPurposeStyles'; import type { Patient } from '@/types/patient'; import { combineLocalDateAndTime, + compareLocalDayStart, formatTimeForInput, isSameLocalCalendarDay, } from '@/lib/appointmentTime'; @@ -28,6 +29,7 @@ interface AppointmentBookingModalProps { endAt: string; purpose: AppointmentPurpose; }) => Promise; + editingAppointment?: AppointmentRecord | null; loading?: boolean; } @@ -40,6 +42,7 @@ export function AppointmentBookingModal({ initialHour, onClose, onSubmit, + editingAppointment = null, loading = false, }: AppointmentBookingModalProps) { const [startTime, setStartTime] = useState('09:00'); @@ -61,29 +64,37 @@ export function AppointmentBookingModal({ if (!open) { return; } - const start = new Date( - scheduleDate.getFullYear(), - scheduleDate.getMonth(), - scheduleDate.getDate(), - initialHour, - 0, - 0, - 0, - ); - const end = new Date( - scheduleDate.getFullYear(), - scheduleDate.getMonth(), - scheduleDate.getDate(), - initialHour < 23 ? initialHour + 1 : 23, - initialHour < 23 ? 0 : 59, - 0, - 0, - ); - setStartTime(formatTimeForInput(start)); - setEndTime(formatTimeForInput(end)); - setPurpose('consultation'); + if (editingAppointment) { + const start = new Date(editingAppointment.startAt); + const end = new Date(editingAppointment.endAt); + setStartTime(formatTimeForInput(start)); + setEndTime(formatTimeForInput(end)); + setPurpose((editingAppointment.purpose as AppointmentPurpose) ?? 'consultation'); + } else { + const start = new Date( + scheduleDate.getFullYear(), + scheduleDate.getMonth(), + scheduleDate.getDate(), + initialHour, + 0, + 0, + 0, + ); + const end = new Date( + scheduleDate.getFullYear(), + scheduleDate.getMonth(), + scheduleDate.getDate(), + initialHour < 23 ? initialHour + 1 : 23, + initialHour < 23 ? 0 : 59, + 0, + 0, + ); + setStartTime(formatTimeForInput(start)); + setEndTime(formatTimeForInput(end)); + setPurpose('consultation'); + } setError(''); - }, [open, scheduleDate, initialHour]); + }, [open, scheduleDate, initialHour, editingAppointment]); if (!open || !providerUserId) { return null; @@ -97,7 +108,7 @@ export function AppointmentBookingModal({ if (!providerUserId) { return; } - if (!patient) { + if (!editingAppointment && !patient) { setError('Select a patient first.'); return; } @@ -116,9 +127,22 @@ export function AppointmentBookingModal({ return; } + const today = new Date(); + if (compareLocalDayStart(scheduleDate, today) < 0) { + setError('Past appointments are view-only.'); + return; + } + + const effectivePatientId = editingAppointment?.patientId ?? patient?.id; + const effectiveProviderId = editingAppointment?.providerUserId ?? providerUserId; + if (!effectivePatientId || !effectiveProviderId) { + setError('Missing appointment details.'); + return; + } + await onSubmit({ - patientId: patient.id, - providerUserId, + patientId: effectivePatientId, + providerUserId: effectiveProviderId, startAt: startAt.toISOString(), endAt: endAt.toISOString(), purpose, @@ -135,7 +159,7 @@ export function AppointmentBookingModal({ >

- New appointment + {editingAppointment ? 'Edit appointment' : 'New appointment'}

diff --git a/frontend/src/components/ui/appointments/AppointmentScheduleGrid.tsx b/frontend/src/components/ui/appointments/AppointmentScheduleGrid.tsx index fd0a98f..acb47a4 100644 --- a/frontend/src/components/ui/appointments/AppointmentScheduleGrid.tsx +++ b/frontend/src/components/ui/appointments/AppointmentScheduleGrid.tsx @@ -3,7 +3,10 @@ import { Trash2 } from 'lucide-react'; import type { AppointmentColumnProvider, AppointmentRecord } from '@/types/appointment'; import { formatHourLabel } from '@/lib/appointmentTime'; -import { purposeDeleteIconClass, purposeStyle } from '@/components/ui/appointments/appointmentPurposeStyles'; +import { + purposeDeleteIconClass, + purposeStyle, +} from '@/components/ui/appointments/appointmentPurposeStyles'; const HOUR_PX = 40; const HOURS = Array.from({ length: 24 }, (_, i) => i); @@ -32,6 +35,7 @@ interface AppointmentScheduleGridProps { canDelete?: boolean; onDeleteAppointment?: (id: string) => void; onSlotClick: (hour: number, providerUserId: string, providerName: string) => void; + onAppointmentClick?: (appointment: AppointmentRecord) => void; } export function AppointmentScheduleGrid({ @@ -42,6 +46,7 @@ export function AppointmentScheduleGrid({ canDelete = false, onDeleteAppointment, onSlotClick, + onAppointmentClick, }: AppointmentScheduleGridProps) { const gridHeight = HOURS.length * HOUR_PX; @@ -117,9 +122,11 @@ export function AppointmentScheduleGrid({ return null; } return ( -
onAppointmentClick?.(apt)} + className={`absolute left-0.5 right-0.5 rounded-[var(--radius-sm)] border pointer-events-auto z-10 flex flex-row items-center gap-1.5 px-1.5 py-1 min-h-[36px] text-left ${purposeStyle(apt.purpose)} focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/35`} style={{ top: pos.top, height: pos.height, minHeight: 36 }} >
@@ -133,7 +140,7 @@ export function AppointmentScheduleGrid({ {canDelete && onDeleteAppointment && ( )} -
+ ); })}
diff --git a/frontend/src/components/ui/appointments/appointmentPurposeStyles.ts b/frontend/src/components/ui/appointments/appointmentPurposeStyles.ts index 6b1e5e5..6230ad5 100644 --- a/frontend/src/components/ui/appointments/appointmentPurposeStyles.ts +++ b/frontend/src/components/ui/appointments/appointmentPurposeStyles.ts @@ -10,11 +10,12 @@ export const APPOINTMENT_PURPOSE_LABEL: Record = { /** Background + border for blocks / legend (matches reference palette). */ export const APPOINTMENT_PURPOSE_STYLES: Record = { - consultation: 'bg-violet-500/25 border-violet-400/50 text-violet-100', - filling: 'bg-orange-500/25 border-orange-400/50 text-orange-100', - endo: 'bg-red-500/25 border-red-400/50 text-red-100', - visit: 'bg-sky-500/25 border-sky-400/50 text-sky-100', - hygiene: 'bg-lime-500/20 border-lime-400/45 text-lime-100', + consultation: + 'bg-purpose-consultation-bg border-purpose-consultation-border text-purpose-consultation-fg', + filling: 'bg-purpose-filling-bg border-purpose-filling-border text-purpose-filling-fg', + endo: 'bg-purpose-endo-bg border-purpose-endo-border text-purpose-endo-fg', + visit: 'bg-purpose-visit-bg border-purpose-visit-border text-purpose-visit-fg', + hygiene: 'bg-purpose-hygiene-bg border-purpose-hygiene-border text-purpose-hygiene-fg', }; export function purposeStyle(purpose: string): string { @@ -26,13 +27,13 @@ export function purposeStyle(purpose: string): string { export function purposeDeleteIconClass(purpose: string): string { const p = purpose as AppointmentPurpose; const map: Record = { - consultation: '!text-violet-400 group-hover:!text-violet-300', - filling: '!text-orange-400 group-hover:!text-orange-300', - endo: '!text-red-400 group-hover:!text-red-300', - visit: '!text-sky-400 group-hover:!text-sky-300', - hygiene: '!text-lime-600 group-hover:!text-lime-500', + consultation: '!text-purpose-consultation-fg', + filling: '!text-purpose-filling-fg', + endo: '!text-purpose-endo-fg', + visit: '!text-purpose-visit-fg', + hygiene: '!text-purpose-hygiene-fg', }; - return map[p] ?? '!text-text-muted group-hover:!text-text-secondary'; + return map[p] ?? '!text-text-muted'; } /** Small swatch for legend (background + border only). */ diff --git a/frontend/src/components/ui/common/Badge.tsx b/frontend/src/components/ui/common/Badge.tsx index afc8f5f..823419d 100644 --- a/frontend/src/components/ui/common/Badge.tsx +++ b/frontend/src/components/ui/common/Badge.tsx @@ -14,10 +14,10 @@ interface BadgeProps { } const variantStyles: Record = { - success: 'bg-emerald-900/30 text-emerald-300 border-emerald-700/60', - warning: 'bg-amber-900/30 text-amber-300 border-amber-700/60', - danger: 'bg-red-950/30 text-red-300 border-red-700/60', - default: 'bg-background-secondary text-text-secondary border-border', + success: 'bg-badge-success-bg text-badge-success-fg border-badge-success-border', + warning: 'bg-badge-warning-bg text-badge-warning-fg border-badge-warning-border', + danger: 'bg-badge-danger-bg text-badge-danger-fg border-badge-danger-border', + default: 'bg-badge-default-bg text-badge-default-fg border-badge-default-border', }; /** Explicit width + height so every row matches; flex centers label optically. */ diff --git a/frontend/src/components/ui/common/Card.tsx b/frontend/src/components/ui/common/Card.tsx new file mode 100644 index 0000000..6470d90 --- /dev/null +++ b/frontend/src/components/ui/common/Card.tsx @@ -0,0 +1,36 @@ +import type { ElementType, ReactNode } from 'react'; + +type CardPadding = 'none' | 'sm' | 'md' | 'lg'; + +type CardProps = { + children: ReactNode; + padding?: CardPadding; + as?: T; + className?: string; +} & Omit, 'as' | 'children' | 'className'>; + +const paddingClassMap: Record = { + none: '', + sm: 'p-3', + md: 'p-4', + lg: 'p-6', +}; + +export function Card({ + children, + as, + className = '', + padding = 'md', + ...props +}: CardProps) { + const Component = as ?? 'div'; + + return ( + + {children} + + ); +} diff --git a/frontend/src/components/ui/common/ScheduleDayPicker.tsx b/frontend/src/components/ui/common/ScheduleDayPicker.tsx index 744f308..e174d21 100644 --- a/frontend/src/components/ui/common/ScheduleDayPicker.tsx +++ b/frontend/src/components/ui/common/ScheduleDayPicker.tsx @@ -1,19 +1,17 @@ 'use client'; import { ChevronLeft, ChevronRight } from 'lucide-react'; -import { addCalendarDays, compareLocalDayStart } from '@/lib/appointmentTime'; +import { addCalendarDays } from '@/lib/appointmentTime'; interface ScheduleDayPickerProps { value: Date; onChange: (day: Date) => void; - /** Inclusive minimum calendar day (typically today at local midnight). */ - minDate: Date; + /** Optional lower bound; picker navigation is unrestricted for history browsing. */ + minDate?: Date; label?: string; } -export function ScheduleDayPicker({ value, onChange, minDate, label = 'Schedule date' }: ScheduleDayPickerProps) { - const canGoPrev = compareLocalDayStart(value, minDate) > 0; - +export function ScheduleDayPicker({ value, onChange, label = 'Schedule date' }: ScheduleDayPickerProps) { const labelText = value.toLocaleDateString(undefined, { weekday: 'short', month: 'short', @@ -27,9 +25,8 @@ export function ScheduleDayPicker({ value, onChange, minDate, label = 'Schedule
-
+ ); } return ( -
+
@@ -89,12 +90,14 @@ export function AppointmentsStrip({ })}`; const palette = purposeStyle(a.purpose); return ( - + ); })}
-
+
); } diff --git a/frontend/src/components/ui/treatment/TreatmentWorkspace.tsx b/frontend/src/components/ui/treatment/TreatmentWorkspace.tsx index e18ce0f..0c20616 100644 --- a/frontend/src/components/ui/treatment/TreatmentWorkspace.tsx +++ b/frontend/src/components/ui/treatment/TreatmentWorkspace.tsx @@ -8,6 +8,7 @@ import { Checkbox } from '@/components/ui/common/Checkbox'; import { Button } from '@/components/ui/common/Button'; import { Dropdown } from '@/components/ui/common/Dropdown'; import { SearchBar } from '@/components/ui/common/SearchBar'; +import { Toast } from '@/components/ui/common/Toast'; import { isSameLocalCalendarDay, startOfLocalDay } from '@/lib/appointmentTime'; import { fetchLinkedOrganizations, @@ -325,7 +326,7 @@ export function TreatmentWorkspace({ userId, currentOrganization }: TreatmentWor } return ( -
+

Treatment

@@ -345,12 +346,6 @@ export function TreatmentWorkspace({ userId, currentOrganization }: TreatmentWor loading={apptsLoading} /> - {banner && ( -

- {banner} -
- )} -
{selectedAppointment ? ( @@ -651,6 +646,14 @@ export function TreatmentWorkspace({ userId, currentOrganization }: TreatmentWor
+ + {banner && ( +
+
+ {banner} +
+
+ )}
); } diff --git a/frontend/src/lib/api/appointments.ts b/frontend/src/lib/api/appointments.ts index 76237b0..9ba35dc 100644 --- a/frontend/src/lib/api/appointments.ts +++ b/frontend/src/lib/api/appointments.ts @@ -9,6 +9,8 @@ export interface CreateAppointmentBody { purpose: string; } +export type UpdateAppointmentBody = Partial; + export const appointmentsApi = { columnProviders: async (): Promise<{ success: boolean; data: AppointmentColumnProvider[] }> => { const response = await apiClient.get('/appointments/column-providers'); @@ -25,6 +27,14 @@ export const appointmentsApi = { return response.data; }, + update: async ( + id: string, + body: UpdateAppointmentBody, + ): Promise<{ success: boolean; data: AppointmentRecord }> => { + const response = await apiClient.patch(`/appointments/${id}`, body); + return response.data; + }, + remove: async (id: string): Promise<{ success: boolean }> => { const response = await apiClient.delete(`/appointments/${id}`); return response.data; diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index 27dc929..6205780 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -18,6 +18,40 @@ --color-border: #d2dcec; --color-border-strong: #aec0de; + --color-card-background: #ffffff; + --color-card-foreground: #0f172a; + --color-card-muted: #475569; + --color-card-border: #d2dcec; + + --color-badge-default-bg: #edf3fb; + --color-badge-default-fg: #1e293b; + --color-badge-default-border: #c9d8ec; + --color-badge-success-bg: #e6f7ef; + --color-badge-success-fg: #14532d; + --color-badge-success-border: #98d8b5; + --color-badge-warning-bg: #fff4df; + --color-badge-warning-fg: #92400e; + --color-badge-warning-border: #f6c88b; + --color-badge-danger-bg: #ffe8e8; + --color-badge-danger-fg: #991b1b; + --color-badge-danger-border: #f2b0b0; + + --color-purpose-consultation-bg: #f1eafe; + --color-purpose-consultation-fg: #5b21b6; + --color-purpose-consultation-border: #cfb8f7; + --color-purpose-filling-bg: #fff1e5; + --color-purpose-filling-fg: #9a3412; + --color-purpose-filling-border: #f8c9a6; + --color-purpose-endo-bg: #ffe9e9; + --color-purpose-endo-fg: #991b1b; + --color-purpose-endo-border: #f4b6b6; + --color-purpose-visit-bg: #e7f4ff; + --color-purpose-visit-fg: #0c4a6e; + --color-purpose-visit-border: #abd8f3; + --color-purpose-hygiene-bg: #effadf; + --color-purpose-hygiene-fg: #3f6212; + --color-purpose-hygiene-border: #cbe9a1; + --color-primary: #009cae; --color-primary-contrast: #031014; --color-primary-soft: rgba(0, 156, 174, 0.16); @@ -42,6 +76,40 @@ --color-border: #29456a; --color-border-strong: #3b5f8f; + --color-card-background: #14253d; + --color-card-foreground: #f5f9ff; + --color-card-muted: #b6c6dd; + --color-card-border: #29456a; + + --color-badge-default-bg: #1b2f4e; + --color-badge-default-fg: #b6c6dd; + --color-badge-default-border: #3b5f8f; + --color-badge-success-bg: rgba(6, 78, 59, 0.45); + --color-badge-success-fg: #86efac; + --color-badge-success-border: rgba(21, 128, 61, 0.5); + --color-badge-warning-bg: rgba(120, 53, 15, 0.48); + --color-badge-warning-fg: #fcd34d; + --color-badge-warning-border: rgba(180, 83, 9, 0.55); + --color-badge-danger-bg: rgba(127, 29, 29, 0.42); + --color-badge-danger-fg: #fca5a5; + --color-badge-danger-border: rgba(185, 28, 28, 0.52); + + --color-purpose-consultation-bg: rgba(139, 92, 246, 0.25); + --color-purpose-consultation-fg: #ddd6fe; + --color-purpose-consultation-border: rgba(167, 139, 250, 0.5); + --color-purpose-filling-bg: rgba(249, 115, 22, 0.25); + --color-purpose-filling-fg: #fed7aa; + --color-purpose-filling-border: rgba(251, 146, 60, 0.5); + --color-purpose-endo-bg: rgba(239, 68, 68, 0.25); + --color-purpose-endo-fg: #fecaca; + --color-purpose-endo-border: rgba(248, 113, 113, 0.5); + --color-purpose-visit-bg: rgba(14, 165, 233, 0.25); + --color-purpose-visit-fg: #bae6fd; + --color-purpose-visit-border: rgba(56, 189, 248, 0.5); + --color-purpose-hygiene-bg: rgba(132, 204, 22, 0.2); + --color-purpose-hygiene-fg: #d9f99d; + --color-purpose-hygiene-border: rgba(163, 230, 53, 0.45); + --color-primary: #09a9bc; --color-primary-contrast: #001117; --color-primary-soft: rgba(9, 169, 188, 0.2); @@ -66,6 +134,40 @@ --color-border: #29456a; --color-border-strong: #3b5f8f; + --color-card-background: #14253d; + --color-card-foreground: #f5f9ff; + --color-card-muted: #b6c6dd; + --color-card-border: #29456a; + + --color-badge-default-bg: #1b2f4e; + --color-badge-default-fg: #b6c6dd; + --color-badge-default-border: #3b5f8f; + --color-badge-success-bg: rgba(6, 78, 59, 0.45); + --color-badge-success-fg: #86efac; + --color-badge-success-border: rgba(21, 128, 61, 0.5); + --color-badge-warning-bg: rgba(120, 53, 15, 0.48); + --color-badge-warning-fg: #fcd34d; + --color-badge-warning-border: rgba(180, 83, 9, 0.55); + --color-badge-danger-bg: rgba(127, 29, 29, 0.42); + --color-badge-danger-fg: #fca5a5; + --color-badge-danger-border: rgba(185, 28, 28, 0.52); + + --color-purpose-consultation-bg: rgba(139, 92, 246, 0.25); + --color-purpose-consultation-fg: #ddd6fe; + --color-purpose-consultation-border: rgba(167, 139, 250, 0.5); + --color-purpose-filling-bg: rgba(249, 115, 22, 0.25); + --color-purpose-filling-fg: #fed7aa; + --color-purpose-filling-border: rgba(251, 146, 60, 0.5); + --color-purpose-endo-bg: rgba(239, 68, 68, 0.25); + --color-purpose-endo-fg: #fecaca; + --color-purpose-endo-border: rgba(248, 113, 113, 0.5); + --color-purpose-visit-bg: rgba(14, 165, 233, 0.25); + --color-purpose-visit-fg: #bae6fd; + --color-purpose-visit-border: rgba(56, 189, 248, 0.5); + --color-purpose-hygiene-bg: rgba(132, 204, 22, 0.2); + --color-purpose-hygiene-fg: #d9f99d; + --color-purpose-hygiene-border: rgba(163, 230, 53, 0.45); + --color-primary: #09a9bc; --color-primary-contrast: #001117; --color-primary-soft: rgba(9, 169, 188, 0.2); @@ -89,6 +191,37 @@ --color-border: var(--color-border); --color-border-strong: var(--color-border-strong); + --color-card: var(--color-card-background); + --color-card-foreground: var(--color-card-foreground); + --color-card-muted: var(--color-card-muted); + --color-card-border: var(--color-card-border); + --color-badge-default-bg: var(--color-badge-default-bg); + --color-badge-default-fg: var(--color-badge-default-fg); + --color-badge-default-border: var(--color-badge-default-border); + --color-badge-success-bg: var(--color-badge-success-bg); + --color-badge-success-fg: var(--color-badge-success-fg); + --color-badge-success-border: var(--color-badge-success-border); + --color-badge-warning-bg: var(--color-badge-warning-bg); + --color-badge-warning-fg: var(--color-badge-warning-fg); + --color-badge-warning-border: var(--color-badge-warning-border); + --color-badge-danger-bg: var(--color-badge-danger-bg); + --color-badge-danger-fg: var(--color-badge-danger-fg); + --color-badge-danger-border: var(--color-badge-danger-border); + --color-purpose-consultation-bg: var(--color-purpose-consultation-bg); + --color-purpose-consultation-fg: var(--color-purpose-consultation-fg); + --color-purpose-consultation-border: var(--color-purpose-consultation-border); + --color-purpose-filling-bg: var(--color-purpose-filling-bg); + --color-purpose-filling-fg: var(--color-purpose-filling-fg); + --color-purpose-filling-border: var(--color-purpose-filling-border); + --color-purpose-endo-bg: var(--color-purpose-endo-bg); + --color-purpose-endo-fg: var(--color-purpose-endo-fg); + --color-purpose-endo-border: var(--color-purpose-endo-border); + --color-purpose-visit-bg: var(--color-purpose-visit-bg); + --color-purpose-visit-fg: var(--color-purpose-visit-fg); + --color-purpose-visit-border: var(--color-purpose-visit-border); + --color-purpose-hygiene-bg: var(--color-purpose-hygiene-bg); + --color-purpose-hygiene-fg: var(--color-purpose-hygiene-fg); + --color-purpose-hygiene-border: var(--color-purpose-hygiene-border); --color-primary: var(--color-primary); --color-primary-contrast: var(--color-primary-contrast); --color-primary-soft: var(--color-primary-soft); @@ -115,8 +248,9 @@ body { } .surface-card { - background: color-mix(in srgb, var(--color-background-card) 92%, transparent); - border: 1px solid var(--color-border); + background: color-mix(in srgb, var(--color-card-background) 92%, transparent); + border: 1px solid var(--color-card-border); + color: var(--color-card-foreground); border-radius: var(--radius-lg); }