Modified by www.SourceFiles.app
This commit is contained in:
@@ -0,0 +1,116 @@
|
|||||||
|
import type { Database } from "@/integrations/supabase/types";
|
||||||
|
|
||||||
|
export type PlanType = Database["public"]["Enums"]["plan_type"];
|
||||||
|
export type PlanStatus = Database["public"]["Enums"]["plan_status"];
|
||||||
|
export type GoalStatus = Database["public"]["Enums"]["plan_goal_status"];
|
||||||
|
export type MeetingType = Database["public"]["Enums"]["plan_meeting_type"];
|
||||||
|
|
||||||
|
export const PLAN_TYPE_LABEL: Record<PlanType, string> = {
|
||||||
|
iep: "IEP",
|
||||||
|
section_504: "504 Plan",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PLAN_STATUS_LABEL: Record<PlanStatus, string> = {
|
||||||
|
draft: "Draft",
|
||||||
|
active: "Active",
|
||||||
|
archived: "Archived",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GOAL_STATUS_LABEL: Record<GoalStatus, string> = {
|
||||||
|
not_started: "Not started",
|
||||||
|
in_progress: "In progress",
|
||||||
|
met: "Met",
|
||||||
|
not_met: "Not met",
|
||||||
|
discontinued: "Discontinued",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MEETING_TYPE_LABEL: Record<MeetingType, string> = {
|
||||||
|
initial: "Initial",
|
||||||
|
annual_review: "Annual review",
|
||||||
|
reevaluation: "Re-evaluation",
|
||||||
|
eligibility: "Eligibility",
|
||||||
|
amendment: "Amendment",
|
||||||
|
other: "Other",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ACCOMMODATION_CATEGORIES = [
|
||||||
|
{ value: "instructional", label: "Instructional" },
|
||||||
|
{ value: "environmental", label: "Environmental" },
|
||||||
|
{ value: "assessment", label: "Assessment / testing" },
|
||||||
|
{ value: "behavioral", label: "Behavioral" },
|
||||||
|
{ value: "other", label: "Other" },
|
||||||
|
];
|
||||||
|
export const accommodationLabel = (v: string) =>
|
||||||
|
ACCOMMODATION_CATEGORIES.find((c) => c.value === v)?.label ?? v;
|
||||||
|
|
||||||
|
// ── Compliance clocks ───────────────────────────────────────────────────────
|
||||||
|
// A date within this many days counts as "due soon" rather than merely upcoming.
|
||||||
|
export const DUE_SOON_DAYS = 30;
|
||||||
|
|
||||||
|
export type ComplianceState = "none" | "ok" | "due_soon" | "overdue";
|
||||||
|
|
||||||
|
// "YYYY-MM-DD" through `new Date()` parses as UTC midnight, which lands on the
|
||||||
|
// previous day for anyone west of Greenwich. Build a local date instead.
|
||||||
|
function parseISODate(d: string): Date {
|
||||||
|
const [y, m, day] = d.split("-").map(Number);
|
||||||
|
return new Date(y, m - 1, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function daysUntil(date: string | null | undefined, today = new Date()): number | null {
|
||||||
|
if (!date) return null;
|
||||||
|
const midnight = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
||||||
|
return Math.round((parseISODate(date).getTime() - midnight.getTime()) / 86_400_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function complianceState(
|
||||||
|
date: string | null | undefined,
|
||||||
|
today = new Date(),
|
||||||
|
): ComplianceState {
|
||||||
|
const d = daysUntil(date, today);
|
||||||
|
if (d === null) return "none";
|
||||||
|
if (d < 0) return "overdue";
|
||||||
|
if (d <= DUE_SOON_DAYS) return "due_soon";
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmbraceIEP shows overdue dates in red italics; due-soon gets an amber warning.
|
||||||
|
export const COMPLIANCE_CLASS: Record<ComplianceState, string> = {
|
||||||
|
none: "text-muted-foreground",
|
||||||
|
ok: "text-foreground",
|
||||||
|
due_soon: "text-amber-600 font-medium",
|
||||||
|
overdue: "text-destructive font-semibold italic",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function complianceLabel(state: ComplianceState, days: number | null): string {
|
||||||
|
switch (state) {
|
||||||
|
case "overdue":
|
||||||
|
return `${Math.abs(days ?? 0)} day${Math.abs(days ?? 0) === 1 ? "" : "s"} overdue`;
|
||||||
|
case "due_soon":
|
||||||
|
return days === 0 ? "Due today" : `Due in ${days} day${days === 1 ? "" : "s"}`;
|
||||||
|
case "ok":
|
||||||
|
return "On track";
|
||||||
|
default:
|
||||||
|
return "Not set";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatDate = (d: string | null | undefined) =>
|
||||||
|
d ? parseISODate(d).toLocaleDateString() : "—";
|
||||||
|
|
||||||
|
// Default compliance clocks when a plan takes effect: annual review at +1 year,
|
||||||
|
// re-evaluation at +3 years (the IDEA triennial). Both are editable afterwards.
|
||||||
|
export function defaultReviewDates(effective: string): { annual: string; reeval: string } {
|
||||||
|
const iso = (dt: Date) =>
|
||||||
|
`${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, "0")}-${String(dt.getDate()).padStart(2, "0")}`;
|
||||||
|
const base = parseISODate(effective);
|
||||||
|
const annual = new Date(base);
|
||||||
|
annual.setFullYear(annual.getFullYear() + 1);
|
||||||
|
const reeval = new Date(base);
|
||||||
|
reeval.setFullYear(reeval.getFullYear() + 3);
|
||||||
|
return { annual: iso(annual), reeval: iso(reeval) };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const todayISO = () => {
|
||||||
|
const t = new Date();
|
||||||
|
return `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, "0")}-${String(t.getDate()).padStart(2, "0")}`;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user