Throughput Overview: Horse Mix and Grain Mix are now the first two cards and
show TODAY's output only. Removed the 7d/4w/6w/12w range selector; the cards
are fixed to Horse mix today, Grain mix today, Today, This week, 4-week average.
Mix Editor formula save: fix HTTP 500 on PUT /editor/mixes/{id}/formula. The
audit-diff path read the resolved formula by attribute, but the resolver returns
dicts -> AttributeError. Read by key and expire stale ORM state so the response
reflects the just-saved rows. Adds regression tests for both save branches.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
71 lines
2.2 KiB
TypeScript
71 lines
2.2 KiB
TypeScript
import type { ThroughputEntry } from '$lib/types';
|
|
|
|
export type SortKey = 'date' | 'product' | 'packed' | 'total' | 'staff' | 'destination' | 'notes';
|
|
export type SortDirection = 'asc' | 'desc';
|
|
|
|
export type ConfettiPiece = {
|
|
left: number;
|
|
delay: number;
|
|
duration: number;
|
|
color: string;
|
|
rotate: number;
|
|
drift: number;
|
|
size: number;
|
|
};
|
|
|
|
export const CONFETTI_COLORS = ['#16a34a', '#f59e0b', '#3b82f6', '#ec4899', '#8b5cf6', '#ef4444'];
|
|
|
|
export function compareText(a: string | null | undefined, b: string | null | undefined) {
|
|
return (a ?? '').localeCompare(b ?? '', undefined, { sensitivity: 'base' });
|
|
}
|
|
|
|
export function compareDate(a: string | null | undefined, b: string | null | undefined) {
|
|
const aTime = a ? Date.parse(a) : Number.NEGATIVE_INFINITY;
|
|
const bTime = b ? Date.parse(b) : Number.NEGATIVE_INFINITY;
|
|
return aTime - bTime;
|
|
}
|
|
|
|
export function toISODate(d: Date): string {
|
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
}
|
|
|
|
export function ausToday(): Date {
|
|
const ymd = new Intl.DateTimeFormat('en-CA', {
|
|
timeZone: 'Australia/Sydney',
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit'
|
|
}).format(new Date());
|
|
const [y, m, d] = ymd.split('-').map(Number);
|
|
return new Date(y, m - 1, d);
|
|
}
|
|
|
|
export function startOfWeekMonday(d: Date): Date {
|
|
const start = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
const dow = (start.getDay() + 6) % 7;
|
|
start.setDate(start.getDate() - dow);
|
|
return start;
|
|
}
|
|
|
|
export function addDays(d: Date, days: number): Date {
|
|
const next = new Date(d);
|
|
next.setDate(next.getDate() + days);
|
|
return next;
|
|
}
|
|
|
|
export function buildConfetti(colors = CONFETTI_COLORS): ConfettiPiece[] {
|
|
return Array.from({ length: 42 }, (_, i) => ({
|
|
left: Math.random() * 100,
|
|
delay: Math.random() * 1.2,
|
|
duration: 1 + Math.random() * 0.8,
|
|
color: colors[i % colors.length],
|
|
rotate: 220 + Math.random() * 360,
|
|
drift: (Math.random() - 0.5) * 60,
|
|
size: 6 + Math.random() * 6
|
|
}));
|
|
}
|
|
|
|
export function isStockEntry(entry: ThroughputEntry): boolean {
|
|
return entry.for_stock || (!entry.for_order && /stock/i.test(entry.notes ?? ''));
|
|
}
|