Enhance receipt management by adding support for image uploads and improving data handling in receipt forms. Update route parameters and type definitions for better data structure. Refactor UI components for improved user experience and responsiveness, including collapsible sections for receipt images and dynamic line item management.
CI / build (push) Has been skipped
CI / deploy (push) Successful in 1m42s

This commit is contained in:
eewing
2026-02-19 10:57:15 -06:00
parent 115eecc6f3
commit f51f9c9f81
49 changed files with 879 additions and 11651 deletions
+91 -2
View File
@@ -1,9 +1,19 @@
import { error } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
import { error, fail, redirect } from "@sveltejs/kit";
import type { Actions, PageServerLoad } from "./$types";
import type { StackqReceipt } from "$lib/types/receipts";
import type { ReceiptDataShape } from "$lib/types/receipts";
const COLLECTION = "Stackq_Receipts";
const STATUS_OPTIONS = ["New", "Data Entered", "Reviewed", "Consolidated"] as const;
const TYPE_OPTIONS = ["Purchase", "Gas"] as const;
function parseNum(val: unknown): number {
if (val === "" || val === null || val === undefined) return 0;
const n = Number(val);
return Number.isFinite(n) ? n : 0;
}
export const load: PageServerLoad = async ({ params, locals }) => {
const { id } = params;
try {
@@ -21,3 +31,82 @@ export const load: PageServerLoad = async ({ params, locals }) => {
throw err;
}
};
export const actions: Actions = {
update: async ({ request, locals, params }) => {
const id = params.id;
if (!id) return fail(400, { error: "Missing id", form: "update" });
const form = await request.formData();
const Status = (form.get("Status") as string)?.trim() || "New";
const Type = (form.get("Type") as string)?.trim() || "Purchase";
const dataJson = (form.get("dataJson") as string)?.trim() ?? "{}";
const imageFile = form.get("Image") as File | null;
const hasImage = imageFile && imageFile.size > 0;
let Data: ReceiptDataShape = {};
try {
const parsed = JSON.parse(dataJson) as unknown;
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
const o = parsed as Record<string, unknown>;
if (Array.isArray(o.lineItems)) {
Data.lineItems = o.lineItems.filter(
(row: unknown): row is { item: string; description: string; quantity: number; price: number } =>
row != null &&
typeof row === "object" &&
"item" in row &&
"quantity" in row &&
"price" in row
).map((row) => ({
item: String(row.item ?? ""),
description: String(row.description ?? ""),
quantity: parseNum(row.quantity),
price: parseNum(row.price),
}));
}
if (typeof o.tax === "number" && Number.isFinite(o.tax)) Data.tax = o.tax;
else if (typeof o.tax === "string") Data.tax = parseNum(o.tax);
if (typeof o.receiptTotal === "number" && Number.isFinite(o.receiptTotal)) {
Data.receiptTotal = o.receiptTotal;
} else if (typeof o.receiptTotal === "string") {
Data.receiptTotal = parseNum(o.receiptTotal);
}
}
} catch {
return fail(400, { error: "Invalid receipt data JSON", form: "update" });
}
const body: Record<string, unknown> = {
Status: STATUS_OPTIONS.includes(Status as (typeof STATUS_OPTIONS)[number]) ? Status : "New",
Type: TYPE_OPTIONS.includes(Type as (typeof TYPE_OPTIONS)[number]) ? Type : "Purchase",
Data: Object.keys(Data).length ? Data : undefined,
};
if (hasImage) body.Image = imageFile;
try {
await locals.pb.collection(COLLECTION).update(id, body);
} catch (e: unknown) {
const message =
e && typeof e === "object" && "message" in e
? String((e as { message: string }).message)
: "Update failed";
return fail(500, { error: message, form: "update" });
}
return { success: true };
},
delete: async ({ locals, params }) => {
const id = params.id;
if (!id) return fail(400, { error: "Missing id", form: "delete" });
try {
await locals.pb.collection(COLLECTION).delete(id);
} catch (e: unknown) {
const message =
e && typeof e === "object" && "message" in e
? String((e as { message: string }).message)
: "Delete failed";
return fail(500, { error: message, form: "delete" });
}
throw redirect(303, "/receipts");
},
};