023ec1096f
- 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.
91 lines
2.7 KiB
TypeScript
91 lines
2.7 KiB
TypeScript
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);
|
|
}
|