Skip to content

Add audit log, duplicate prevention, quick-add staff, role editing#6

Merged
bradydibble merged 2 commits intomainfrom
claude/audit-log-duplicate-prevention-ebyEu
Apr 9, 2026
Merged

Add audit log, duplicate prevention, quick-add staff, role editing#6
bradydibble merged 2 commits intomainfrom
claude/audit-log-duplicate-prevention-ebyEu

Conversation

@bradydibble
Copy link
Copy Markdown
Owner

  • export_log table tracks every Google Sheets export (id, calc_id, exported_at, exported_by)
  • Export endpoint logs each export and returns Export ID + timestamp
  • Sheets rows now include Staff ID, Exported At, Export ID columns for full audit trail
  • Re-exporting a calculation shows a confirmation dialog with the previous export timestamp
  • calculate/[id] page shows last export time and Export # below the button; warns on re-export
  • Quick-add staff form on /calculate screen (available to shift leads AND managers)
    • New person is auto-selected for the current shift after add
    • Staff list updates reactively without a full page reload
  • Duplicate-name disambiguation: shows #ID badge when two staff share the same name
    • Visible on /calculate staff list and /settings/staff roster
  • Staff role can now be changed inline from Settings → Staff Roster (changeRole action)
    • No longer requires remove + re-add to change FOH ↔ Bar ↔ Kitchen

https://claude.ai/code/session_01Sa6KBxSzsg6gWDavRF9dke

- export_log table tracks every Google Sheets export (id, calc_id, exported_at, exported_by)
- Export endpoint logs each export and returns Export ID + timestamp
- Sheets rows now include Staff ID, Exported At, Export ID columns for full audit trail
- Re-exporting a calculation shows a confirmation dialog with the previous export timestamp
- calculate/[id] page shows last export time and Export # below the button; warns on re-export
- Quick-add staff form on /calculate screen (available to shift leads AND managers)
  - New person is auto-selected for the current shift after add
  - Staff list updates reactively without a full page reload
- Duplicate-name disambiguation: shows #ID badge when two staff share the same name
  - Visible on /calculate staff list and /settings/staff roster
- Staff role can now be changed inline from Settings → Staff Roster (changeRole action)
  - No longer requires remove + re-add to change FOH ↔ Bar ↔ Kitchen

https://claude.ai/code/session_01Sa6KBxSzsg6gWDavRF9dke
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an export audit trail for Google Sheets exports and improves staff management UX by enabling quick staff creation, duplicate-name disambiguation, and inline role editing.

Changes:

  • Introduces export_log persistence and threads Export ID / exported timestamp through the export API, UI, and Sheets rows.
  • Adds quick-add staff on /calculate with reactive list updates and duplicate-name ID badges.
  • Enables inline staff role changes from Settings → Staff Roster.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/routes/settings/staff/+page.svelte Shows duplicate-name ID badges; adds inline role change form per staff member.
src/routes/settings/staff/+page.server.ts Adds changeRole action (manager-only) to update staff role.
src/routes/calculate/+page.svelte Adds quick-add staff form, reactive staff list state, and duplicate-name ID badges.
src/routes/calculate/+page.server.ts Adds addStaff action to insert staff from /calculate.
src/routes/calculate/[id]/+page.svelte Adds re-export confirmation + last-export UI, shows staff IDs on distribution rows, updates local export log after export.
src/routes/calculate/[id]/+page.server.ts Loads export log for a calculation.
src/routes/api/export/+server.ts Creates export_log row per export, appends audit columns to Sheets rows, returns export metadata.
src/lib/server/sheets.ts Extends sheet header row with audit columns.
src/lib/server/db.ts Adds export_log table and ExportLogRow type.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +133 to +156
<form method="POST" action="?/addStaff" use:enhance={({ cancel }) => {
if (!newName.trim()) { addError = 'Name is required'; cancel(); return; }
addingStaff = true;
addError = '';
return async ({ result, update }) => {
addingStaff = false;
if (result.type === 'success' && result.data?.addedId) {
const newPerson: StaffRow = {
id: result.data.addedId as number,
name: newName.trim(),
role: newRole,
active: 1,
location_id: 1,
source: 'manual',
square_team_member_id: null,
};
staff = [...staff, newPerson].sort((a, b) => a.role.localeCompare(b.role) || a.name.localeCompare(b.name));
included = new Set([...included, newPerson.id]);
newName = '';
showAddForm = false;
} else {
await update();
}
};
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The quick-add form never surfaces server-side failures: in the non-success path you only call update() and addError remains empty, so users get no feedback if the action returns fail(400, { addError: ... }) (or any other failure). Consider handling result.type === 'failure' by pulling addError from result.data (and keeping the form open), and only calling update() for success/navigation as needed.

Copilot uses AI. Check for mistakes.
Comment on lines 33 to +40
const json = await res.json();
exportMsg = res.ok ? '✓ Exported to Google Sheets' : `Error: ${json.message}`;
if (res.ok) {
exportMsg = `Exported (Export #${json.exportId})`;
// Update local export log so button reflects new state without a page reload
exportLog = [{ id: json.exportId, calculation_id: data.calc.id, exported_at: Math.floor(Date.now() / 1000), exported_by: null, location_id: 1 }, ...exportLog];
} else {
exportMsg = `Error: ${json.message}`;
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On successful export you synthesize a new exportLog entry using Date.now() and set exported_by: null, even though the server generates/stores exported_at (unixepoch) and exported_by (locals.user.id). This can make the UI's “Last exported …” display diverge from the actual audit log (clock skew, wrong user). Prefer using server-returned values (e.g., have the endpoint return the inserted export_log row with exported_at/exported_by), or re-fetch/exportLog after success.

Copilot uses AI. Check for mistakes.
Comment on lines +80 to +95
<!-- Role change -->
<form method="POST" action="?/changeRole" use:enhance
style="margin-top:0.35rem;display:flex;align-items:center;gap:0.5rem;">
<input type="hidden" name="id" value={person.id} />
<span style="font-size:0.75rem;color:var(--muted);">Role:</span>
<select name="role" class="input"
style="font-size:0.75rem;padding:0.2rem 0.5rem;width:auto;height:auto;">
{#each ['FOH', 'Bar', 'Kitchen'] as r}
<option value={r} selected={person.role === r}>{r}</option>
{/each}
</select>
<button type="submit"
style="background:none;font-size:0.75rem;font-weight:600;color:var(--primary);">
Change
</button>
</form>
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change-role action can return fail(400, { roleError: ... }) (and also 403/404), but the page doesn't display any feedback for this form. With use:enhance default handling, failures will populate form, but nothing renders it here—so role changes can silently fail. Consider rendering form.roleError near the role form and/or adding an enhance callback that sets a per-row error state.

Copilot uses AI. Check for mistakes.
Comment thread src/lib/server/sheets.ts
Comment on lines 27 to 33
export const HEADER_ROW = [
'Date', 'Shift', 'Type', 'Calc ID',
'Gross Tips', 'CC Fee Rate', 'CC Fees', 'Net Tips',
'Kitchen %', 'Kitchen Pool', 'Liquor Sales', 'Bar Liquor %', 'Bar Pool', 'FOH Pool',
'Name', 'Role', 'FOH Share', 'Bar Share', 'Kitchen Share', 'Total',
'Staff ID', 'Exported At', 'Export ID',
];
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appendToSheet only writes the header row when the sheet is empty. After adding new columns ('Staff ID', 'Exported At', 'Export ID'), existing spreadsheets will start receiving extra columns but their header row will remain the old version (no labels), which undermines the audit trail UX. Consider detecting an existing header row and updating/patching it when it’s missing these columns (or documenting that users must clear/update the header manually).

Copilot uses AI. Check for mistakes.
- Quick-add form now surfaces server-side addError on failure instead of
  silently calling update(); failure path reads result.data.addError
- Export UI uses server-returned exportedAtUnix/exportedBy values instead of
  Date.now()/null so the displayed timestamp matches the audit log record exactly
- Sheets.ts now reads row 1 before appending; if the header exists but is missing
  the new audit columns it patches row 1 in-place via PUT before appending data
- Role-change form in staff settings now renders form.roleError so failures are
  visible rather than silently dropped

https://claude.ai/code/session_01Sa6KBxSzsg6gWDavRF9dke
@bradydibble bradydibble merged commit c631c3b into main Apr 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants