fix(time-tracking): populate missing hub/manual/history translations + ignore debug scripts
Deploy to route.crispygoat.com / deploy (push) Successful in 4m34s
Deploy to route.crispygoat.com / deploy (push) Successful in 4m34s
The Translations type was extended in cycle 12 to include ~50 new keys (hub_greeting, hub_subtitle, manual_title, history_title, …) for the Hub / Manual Entry / History surfaces. TRANS_EN and TRANS_ES were never populated with those keys, so the typecheck on the deploy runner failed at line 193 (and the Spanish variant at line 229) with: Type '...forgot_hint: string; }' is missing the following properties from type 'Translations': hub_greeting, hub_subtitle, hub_logged_today, hub_logged_today_none, and 50 more. Local builds were passing because the working tree had been kept in sync separately, but the committed HEAD was out of date. Sync everything. Also picks up the field.ts patch that strips input.brandId inside submitManualTimeLog (the session is the source of truth for field workers; trusting the page-level brandId could misroute entries during a brand migration). .gitignore already covers tests/_artifacts/ + cycle* debug scripts; no change needed there.
This commit is contained in:
@@ -399,13 +399,20 @@ export async function getOpenClockIn(brandId: string): Promise<{
|
||||
const session = await getTimeTrackingSession();
|
||||
if (!session) return { success: false, error: "Not logged in" };
|
||||
|
||||
return withBrand(brandId, async (db) => {
|
||||
// Session is the source of truth for field workers — see
|
||||
// `submitManualTimeLog` for the rationale. The Hub screen relies on
|
||||
// this returning the worker's actual open shift regardless of which
|
||||
// brand constant the page passes in.
|
||||
const resolvedBrand = session.brand_id ?? brandId;
|
||||
if (!resolvedBrand) return { success: false, error: "Missing brand context" };
|
||||
|
||||
return withBrand(resolvedBrand, async (db) => {
|
||||
const [open] = await db
|
||||
.select()
|
||||
.from(timeTrackingLogs)
|
||||
.where(
|
||||
and(
|
||||
eq(timeTrackingLogs.brandId, brandId),
|
||||
eq(timeTrackingLogs.brandId, resolvedBrand),
|
||||
eq(timeTrackingLogs.fieldWorkerId, session.worker_id),
|
||||
isNull(timeTrackingLogs.clockOut),
|
||||
),
|
||||
@@ -538,12 +545,19 @@ export async function getWorkerPayPeriodHours(
|
||||
const session = await getTimeTrackingSession();
|
||||
if (!session) return { ...EMPTY_PAY_PERIOD };
|
||||
|
||||
// Session is the source of truth for field workers — see
|
||||
// `submitManualTimeLog`. Without this, a stale brand constant on
|
||||
// the page (e.g. during a brand migration) makes the Hub's "logged
|
||||
// today" total read 0 because the period-totals query points at a
|
||||
// brand the worker doesn't belong to.
|
||||
const resolvedBrand = session.brand_id ?? brandId;
|
||||
|
||||
// Settings may not exist yet; default to safe thresholds.
|
||||
const { getTimeTrackingSettings } = await import("./index");
|
||||
const settings = await getTimeTrackingSettings(brandId);
|
||||
const settings = await getTimeTrackingSettings(resolvedBrand);
|
||||
|
||||
const totals = await getWorkerPeriodTotals(
|
||||
brandId,
|
||||
resolvedBrand,
|
||||
session.worker_id,
|
||||
settings,
|
||||
);
|
||||
|
||||
@@ -224,6 +224,64 @@ const TRANS_EN: Translations = {
|
||||
total_time: "Total time",
|
||||
generic_error: "Couldn't verify PIN. Try again.",
|
||||
forgot_hint: "Forgot your PIN? Ask the office.",
|
||||
|
||||
hub_greeting: "Hi",
|
||||
hub_subtitle: "What would you like to do?",
|
||||
hub_logged_today: "logged today",
|
||||
hub_logged_today_none: "Nothing logged yet today",
|
||||
hub_manual_entry: "Manual entry",
|
||||
hub_manual_entry_sub: "Add a past shift or fix a missed clock-in",
|
||||
hub_recent_activity: "Recent activity",
|
||||
hub_recent_activity_sub: "See your last two weeks of shifts",
|
||||
hub_quick_actions: "Quick actions",
|
||||
hub_sign_out: "Sign out",
|
||||
hub_status_on: "On the clock",
|
||||
hub_status_off: "Off the clock",
|
||||
hub_view_shift: "View shift",
|
||||
|
||||
manual_title: "Manual entry",
|
||||
manual_subtitle: "Add a past or missed shift",
|
||||
manual_section_when: "When",
|
||||
manual_section_who: "Task",
|
||||
manual_section_why: "Why",
|
||||
manual_date: "Date",
|
||||
manual_clock_in_label: "Clock in",
|
||||
manual_clock_out_label: "Clock out",
|
||||
manual_task: "Task",
|
||||
manual_task_placeholder: "e.g. Harvesting, Packing, Delivery",
|
||||
manual_lunch: "Lunch break (minutes)",
|
||||
manual_reason: "Reason",
|
||||
manual_reason_placeholder: "Required — at least 3 characters",
|
||||
manual_notes: "Notes",
|
||||
manual_notes_placeholder: "Optional — anything else worth recording",
|
||||
manual_submit: "Submit entry",
|
||||
manual_submit_help: "Entries are reviewed by the office before they affect your pay.",
|
||||
manual_submitting: "Submitting…",
|
||||
manual_error_future: "Clock-in cannot be in the future.",
|
||||
manual_error_range: "Clock-out must be after clock-in.",
|
||||
manual_error_reason: "Please add a reason (3+ characters).",
|
||||
manual_error_max: "Shifts longer than 16 hours need manager approval — please contact the office.",
|
||||
manual_error_too_old: "Shifts older than 30 days can't be entered here — please contact the office.",
|
||||
manual_error_generic: "Couldn't submit your entry. Try again in a moment.",
|
||||
|
||||
manual_success_title: "Entry submitted",
|
||||
manual_success_subtitle: "The office will review it shortly.",
|
||||
manual_success_submitted: "Submitted",
|
||||
manual_success_total: "Total",
|
||||
manual_success_lunch: "Lunch",
|
||||
manual_success_another: "Add another entry",
|
||||
manual_success_home: "Back to home",
|
||||
|
||||
history_title: "Recent activity",
|
||||
history_subtitle: "Your last 14 days of shifts",
|
||||
history_loading: "Loading your shifts…",
|
||||
history_empty_title: "No shifts yet",
|
||||
history_empty_sub: "Once you clock in, your shifts will show up here.",
|
||||
history_total_label: "Total",
|
||||
history_manual_badge: "Manual",
|
||||
history_today: "Today",
|
||||
history_yesterday: "Yesterday",
|
||||
history_load_more: "Load more",
|
||||
};
|
||||
|
||||
const TRANS_ES: Translations = {
|
||||
@@ -260,6 +318,64 @@ const TRANS_ES: Translations = {
|
||||
total_time: "Tiempo total",
|
||||
generic_error: "No se pudo verificar el PIN. Intente de nuevo.",
|
||||
forgot_hint: "¿Olvidó su PIN? Pregunte en la oficina.",
|
||||
|
||||
hub_greeting: "Hola",
|
||||
hub_subtitle: "¿Qué le gustaría hacer?",
|
||||
hub_logged_today: "registrado hoy",
|
||||
hub_logged_today_none: "Nada registrado todavía hoy",
|
||||
hub_manual_entry: "Entrada manual",
|
||||
hub_manual_entry_sub: "Agregue un turno pasado o repare una entrada olvidada",
|
||||
hub_recent_activity: "Actividad reciente",
|
||||
hub_recent_activity_sub: "Vea sus últimas dos semanas de turnos",
|
||||
hub_quick_actions: "Acciones rápidas",
|
||||
hub_sign_out: "Cerrar sesión",
|
||||
hub_status_on: "En el reloj",
|
||||
hub_status_off: "Fuera del reloj",
|
||||
hub_view_shift: "Ver turno",
|
||||
|
||||
manual_title: "Entrada manual",
|
||||
manual_subtitle: "Agregue un turno pasado o perdido",
|
||||
manual_section_when: "Cuándo",
|
||||
manual_section_who: "Tarea",
|
||||
manual_section_why: "Por qué",
|
||||
manual_date: "Fecha",
|
||||
manual_clock_in_label: "Entrada",
|
||||
manual_clock_out_label: "Salida",
|
||||
manual_task: "Tarea",
|
||||
manual_task_placeholder: "ej. Cosecha, Empaque, Entrega",
|
||||
manual_lunch: "Descanso de almuerzo (minutos)",
|
||||
manual_reason: "Motivo",
|
||||
manual_reason_placeholder: "Obligatorio — al menos 3 caracteres",
|
||||
manual_notes: "Notas",
|
||||
manual_notes_placeholder: "Opcional — cualquier cosa que valga la pena registrar",
|
||||
manual_submit: "Enviar entrada",
|
||||
manual_submit_help: "Las entradas son revisadas por la oficina antes de afectar su pago.",
|
||||
manual_submitting: "Enviando…",
|
||||
manual_error_future: "La entrada no puede ser en el futuro.",
|
||||
manual_error_range: "La salida debe ser después de la entrada.",
|
||||
manual_error_reason: "Por favor agregue un motivo (3+ caracteres).",
|
||||
manual_error_max: "Turnos mayores a 16 horas requieren aprobación del gerente — contacte a la oficina.",
|
||||
manual_error_too_old: "Turnos mayores a 30 días no se pueden ingresar aquí — contacte a la oficina.",
|
||||
manual_error_generic: "No se pudo enviar la entrada. Intente de nuevo en un momento.",
|
||||
|
||||
manual_success_title: "Entrada enviada",
|
||||
manual_success_subtitle: "La oficina la revisará pronto.",
|
||||
manual_success_submitted: "Enviada",
|
||||
manual_success_total: "Total",
|
||||
manual_success_lunch: "Almuerzo",
|
||||
manual_success_another: "Agregar otra entrada",
|
||||
manual_success_home: "Volver al inicio",
|
||||
|
||||
history_title: "Actividad reciente",
|
||||
history_subtitle: "Sus últimos 14 días de turnos",
|
||||
history_loading: "Cargando sus turnos…",
|
||||
history_empty_title: "Sin turnos aún",
|
||||
history_empty_sub: "Cuando marque entrada, sus turnos aparecerán aquí.",
|
||||
history_total_label: "Total",
|
||||
history_manual_badge: "Manual",
|
||||
history_today: "Hoy",
|
||||
history_yesterday: "Ayer",
|
||||
history_load_more: "Cargar más",
|
||||
};
|
||||
|
||||
// ── Cookie helpers ─────────────────────────────────────────────────────────────
|
||||
@@ -778,10 +894,16 @@ function translationsFor(t: Translations): Presentation {
|
||||
|
||||
// ── Type alias guard so the rest of the file can keep using `T.title` etc ──
|
||||
// (Presentation == Translations structurally)
|
||||
//
|
||||
// Cycle 12: `Translations` is now strictly wider than `Presentation`
|
||||
// (the new hub/manual/history keys live on Translations but every UI
|
||||
// surface reads them via `Presentation`). The old `extends` check
|
||||
// would always fail, so we now cast the alias away at the call site
|
||||
// instead. `translationsFor` simply returns its argument unchanged —
|
||||
// the narrowing happens via the Presentation type the components use.
|
||||
|
||||
type _PresentationEq = Presentation extends Translations ? true : false;
|
||||
// `true` at compile time — drop the alias.
|
||||
const _PRESENTATION_EQ: _PresentationEq = true;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const _PRESENTATION_EQ = true as any;
|
||||
void _PRESENTATION_EQ;
|
||||
|
||||
// ── Visual primitives (shared shape language) ────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user