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.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
import type { Snippet } from "svelte";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: CollapsiblePrimitive.ContentProps & HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Content
|
||||
bind:ref
|
||||
class={cn("overflow-hidden", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</CollapsiblePrimitive.Content>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
import type { Snippet } from "svelte";
|
||||
import type { HTMLButtonAttributes } from "svelte/elements";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: CollapsiblePrimitive.TriggerProps & HTMLButtonAttributes<HTMLButtonElement> & { children?: Snippet } = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Trigger bind:ref class={cn("flex items-center gap-2", className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</CollapsiblePrimitive.Trigger>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Collapsible as CollapsiblePrimitive } from "bits-ui";
|
||||
import type { HTMLAttributes } from "svelte/elements";
|
||||
import type { Snippet } from "svelte";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
}: CollapsiblePrimitive.RootProps & HTMLAttributes<HTMLDivElement> & { children?: Snippet } = $props();
|
||||
</script>
|
||||
|
||||
<CollapsiblePrimitive.Root bind:open class={cn(className)} {...restProps}>
|
||||
{@render children?.()}
|
||||
</CollapsiblePrimitive.Root>
|
||||
@@ -0,0 +1,5 @@
|
||||
import Root from "./collapsible.svelte";
|
||||
import Content from "./collapsible-content.svelte";
|
||||
import Trigger from "./collapsible-trigger.svelte";
|
||||
|
||||
export { Root, Content, Trigger, Root as Collapsible, Content as CollapsibleContent, Trigger as CollapsibleTrigger };
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Convert HEIC/HEIF blob to JPEG. Uses heic2any only when this function is called
|
||||
* (dynamic import), so the receipt page can load even if heic2any fails to resolve.
|
||||
* Suppresses the library's "Could not parse HEIF file" console errors during conversion.
|
||||
*/
|
||||
export async function convertHeicBlobToJpeg(blob: Blob): Promise<Blob> {
|
||||
const originalError = console.error;
|
||||
console.error = (...args: unknown[]) => {
|
||||
const msg = args[0]?.toString?.() ?? "";
|
||||
if (msg.includes("HEIF") || msg.includes("parse HEIF")) return;
|
||||
originalError.apply(console, args);
|
||||
};
|
||||
try {
|
||||
const mod = await import(/* @vite-ignore */ "heic2any");
|
||||
const heic2any = (mod as unknown as {
|
||||
default: (opts: { blob: Blob; toType: string; quality?: number }) => Promise<Blob | Blob[]>;
|
||||
}).default;
|
||||
const out = await heic2any({ blob, toType: "image/jpeg", quality: 0.92 });
|
||||
return Array.isArray(out) ? out[0] : out;
|
||||
} finally {
|
||||
console.error = originalError;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,19 @@
|
||||
/** Line item stored in receipt Data.lineItems */
|
||||
export interface ReceiptLineItem {
|
||||
item: string;
|
||||
description: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
}
|
||||
|
||||
/** JSON shape stored in Stackq_Receipts.Data */
|
||||
export interface ReceiptDataShape {
|
||||
lineItems?: ReceiptLineItem[];
|
||||
tax?: number;
|
||||
/** Total printed on receipt (for validation) */
|
||||
receiptTotal?: number;
|
||||
}
|
||||
|
||||
/** PocketBase Stackq_Receipts collection record (and expanded relation). */
|
||||
export interface StackqReceipt {
|
||||
id: string;
|
||||
@@ -5,7 +21,7 @@ export interface StackqReceipt {
|
||||
Type: string;
|
||||
User: string;
|
||||
Image?: string;
|
||||
Data?: Record<string, unknown>;
|
||||
Data?: ReceiptDataShape | Record<string, unknown>;
|
||||
created: string;
|
||||
updated: string;
|
||||
expand?: {
|
||||
|
||||
Reference in New Issue
Block a user