feat: integrate barcode scanning functionality and enhance Appwrite setup
- Added a new `barcode_products` collection to the Appwrite setup for managing barcode data. - Implemented barcode scanning feature with a dedicated modal for scanning and adding products. - Introduced new components for barcode product preview and management. - Updated the setup script to seed verified barcode products from a JSON file. - Enhanced the application state management to handle barcode-related actions and user interactions.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { BUILT_IN_BARCODE_PRODUCTS } from "../data/barcodes";
|
||||
import { BUILT_IN_FLAVOURS, flavourMeta } from "../data/flavours";
|
||||
import { caffeinePerCan } from "./metrics";
|
||||
import type {
|
||||
BarcodeLookupCatalog,
|
||||
BarcodeLookupResult,
|
||||
BarcodeProductDraft,
|
||||
ResolvedBarcodeProduct,
|
||||
UserBarcodeMapping,
|
||||
EntryDraft,
|
||||
} from "../types";
|
||||
|
||||
const knownFlavourNames = new Set(BUILT_IN_FLAVOURS.map((flavour) => flavour.name));
|
||||
|
||||
export function normalizeBarcode(value: string) {
|
||||
return value.replace(/\D/g, "");
|
||||
}
|
||||
|
||||
export function lookupBarcode(
|
||||
rawBarcode: string,
|
||||
catalogOrUserMappings: BarcodeLookupCatalog | UserBarcodeMapping[] = [],
|
||||
): BarcodeLookupResult {
|
||||
const catalog = Array.isArray(catalogOrUserMappings)
|
||||
? { userMappings: catalogOrUserMappings }
|
||||
: catalogOrUserMappings;
|
||||
const userMappings = catalog.userMappings ?? [];
|
||||
const verifiedProducts = catalog.verifiedProducts ?? BUILT_IN_BARCODE_PRODUCTS;
|
||||
const barcode = normalizeBarcode(rawBarcode);
|
||||
if (!barcode) {
|
||||
return { status: "unknown", barcode: rawBarcode.trim() };
|
||||
}
|
||||
|
||||
const userMapping = userMappings.find((mapping) => mapping.barcode === barcode);
|
||||
if (userMapping) {
|
||||
return { status: "user", barcode, product: resolveProduct(userMapping, "user") };
|
||||
}
|
||||
|
||||
const seedProduct = verifiedProducts[barcode];
|
||||
if (!seedProduct) {
|
||||
return { status: "unknown", barcode };
|
||||
}
|
||||
|
||||
if (!knownFlavourNames.has(seedProduct.flavourName)) {
|
||||
return {
|
||||
status: "partial",
|
||||
barcode,
|
||||
product: seedProduct,
|
||||
reason: "This barcode has product data, but its flavour is not in the built-in Red Bull list yet.",
|
||||
};
|
||||
}
|
||||
|
||||
return { status: "known", barcode, product: resolveProduct(seedProduct, "built-in") };
|
||||
}
|
||||
|
||||
export function resolveProduct(
|
||||
product: BarcodeProductDraft,
|
||||
source: ResolvedBarcodeProduct["source"],
|
||||
): ResolvedBarcodeProduct {
|
||||
const meta = flavourMeta(product.flavourName);
|
||||
return {
|
||||
...product,
|
||||
flavourAccent: meta.accent,
|
||||
sugarFree: product.sugarFree ?? Boolean(meta.sugarFree),
|
||||
caffeineMgPerCan: product.caffeineMgPerCan,
|
||||
source,
|
||||
};
|
||||
}
|
||||
|
||||
export function barcodeProductToEntryDraft(
|
||||
product: ResolvedBarcodeProduct,
|
||||
barcode: string,
|
||||
): EntryDraft {
|
||||
return {
|
||||
cans: 1,
|
||||
flavour: product.flavourName,
|
||||
flavourAccent: product.flavourAccent,
|
||||
sizeMl: product.sizeMl,
|
||||
pricePerCan: product.pricePerCan,
|
||||
dateTime: new Date().toISOString(),
|
||||
notes: `Barcode scan: ${barcode}`,
|
||||
store: "",
|
||||
sugarFree: Boolean(product.sugarFree),
|
||||
caffeineMgPerCan: product.caffeineMgPerCan,
|
||||
source: "manual",
|
||||
};
|
||||
}
|
||||
|
||||
export function productCaffeineMg(product: BarcodeProductDraft) {
|
||||
return caffeinePerCan(product.sizeMl, product.caffeineMgPerCan);
|
||||
}
|
||||
Reference in New Issue
Block a user