commit 6017c52d9116683999f68f1cd212863ad272bcd1
parent 502fb7130d5b247587518be48e96b299d774ef12
Author: triesap <137732411+triesap@users.noreply.github.com>
Date: Thu, 14 Nov 2024 23:37:02 +0000
Edit `/models/trade-product` add list card and line entry components. Edit `/models/trade-product/add` handlers. Add/edit types, utils.
Diffstat:
8 files changed, 342 insertions(+), 66 deletions(-)
diff --git a/src/lib/components/line_entries_between.svelte b/src/lib/components/line_entries_between.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import { fmt_cl, type IClOpt } from "@radroots/svelte-lib";
+ import LineEntryData from "./line_entry_data.svelte";
+ import LineEntryLabel from "./line_entry_label.svelte";
+
+ export let basis: IClOpt & {
+ label: IClOpt & {
+ value: string;
+ value_f?: string;
+ };
+ data: IClOpt & {
+ value: string;
+ value_f?: string;
+ };
+ };
+</script>
+
+<div
+ class={`${fmt_cl(basis.classes)} flex flex-row h-6 w-full justify-between items-center`}
+>
+ <LineEntryLabel basis={basis.label} />
+ <LineEntryData basis={basis.data} />
+</div>
diff --git a/src/lib/components/line_entry_data.svelte b/src/lib/components/line_entry_data.svelte
@@ -0,0 +1,17 @@
+<script lang="ts">
+ import { fmt_cl, type IClOpt, type IClWrapOpt } from "@radroots/svelte-lib";
+
+ export let basis: IClOpt &
+ IClWrapOpt & {
+ value: string;
+ value_f?: string;
+ };
+</script>
+
+<div class={`${fmt_cl(basis.classes_wrap)} flex flex-row h-6`}>
+ <p
+ class={`${fmt_cl(basis.classes)} font-sans font-[500] text-[1.05rem] text-justify truncate text-layer-1-glyph-shade`}
+ >
+ {basis.value || basis.value_f || ``}
+ </p>
+</div>
diff --git a/src/lib/components/line_entry_label.svelte b/src/lib/components/line_entry_label.svelte
@@ -0,0 +1,17 @@
+<script lang="ts">
+ import { fmt_cl, type IClOpt, type IClWrapOpt } from "@radroots/svelte-lib";
+
+ export let basis: IClOpt &
+ IClWrapOpt & {
+ value: string;
+ value_f?: string;
+ };
+</script>
+
+<div class={`${fmt_cl(basis.classes_wrap)} flex flex-row h-6`}>
+ <p
+ class={`${fmt_cl(basis.classes)} font-sans font-[400] text-layer-1-glyph_d text-[1.05rem] capitalize`}
+ >
+ {basis.value || basis.value_f || ``}
+ </p>
+</div>
diff --git a/src/lib/components/trade_product_list_card.svelte b/src/lib/components/trade_product_list_card.svelte
@@ -0,0 +1,245 @@
+<script lang="ts">
+ import type { TradeProductBundle } from "$lib/types";
+ import { fmt_location_gcs } from "@radroots/models";
+ import {
+ app_layout,
+ fmt_geol_latitude,
+ fmt_geol_longitude,
+ Glyph,
+ locale,
+ t,
+ } from "@radroots/svelte-lib";
+ import {
+ fmt_currency_price,
+ fmt_plural_agreement,
+ mass_tf_str,
+ num_min,
+ parse_currency_price,
+ } from "@radroots/utils";
+ import LineEntriesBetween from "./line_entries_between.svelte";
+ import LineEntryData from "./line_entry_data.svelte";
+ import LineEntryLabel from "./line_entry_label.svelte";
+
+ export let basis: {
+ result: TradeProductBundle;
+ };
+ $: ({
+ result: { trade_product, location_gcs },
+ } = basis);
+
+ $: tradeproduct_qty_sold = 0;
+ $: tradeproduct_qty_avail =
+ num_min(trade_product.qty_avail, 1) - tradeproduct_qty_sold;
+</script>
+
+<div
+ class={`flex flex-col min-h-[22rem] w-${$app_layout} justify-start items-start bg-layer-1-surface touch-layer-1 touch-layer-1-raise-less round-44`}
+>
+ <div
+ class={`flex flex-row h-[10rem] w-full justify-center items-center border-b-line border-b-layer-1-surface-edge`}
+ >
+ <button
+ class={`group flex flex-row w-full justify-center items-center`}
+ on:click={async () => {}}
+ >
+ <div
+ class={`relative flex flex-col w-full justify-start items-center`}
+ >
+ <div
+ class={`relative flex flex-row py-2 px-[0.8rem] justify-center items-center`}
+ >
+ <Glyph
+ basis={{
+ classes: `text-layer-0-glyph group-active:text-layer-0-glyph_a el-re`,
+ dim: `xl`,
+ weight: `bold`,
+ key: `camera`,
+ }}
+ />
+ <div
+ class={`absolute top-0 right-0 flex flex-row justify-center items-center`}
+ >
+ <Glyph
+ basis={{
+ classes: `text-layer-0-glyph group-active:text-layer-0-glyph_a el-re`,
+ dim: `xs`,
+ weight: `bold`,
+ key: `plus`,
+ }}
+ />
+ </div>
+ </div>
+ <div
+ class={`absolute -bottom-4 left-0 flex flex-row w-full justify-center items-center`}
+ >
+ <p
+ class={`font-sans font-[500] text-[1rem] text-layer-0-glyph group-active:text-layer-0-glyph_a el-re`}
+ >
+ {`${$t(`icu.no_*`, { value: `${$t(`common.photos`)}`.toLowerCase() })}`}
+ </p>
+ </div>
+ </div>
+ </button>
+ </div>
+ {#if location_gcs}
+ <div
+ class={`flex flex-col min-h-[11rem] w-full pt-8 pb-12 justify-start items-start`}
+ >
+ <div
+ class={`flex flex-col w-full px-5 gap-6 justify-center items-center`}
+ >
+ <div class={`grid grid-cols-12 w-full`}>
+ <LineEntryLabel
+ basis={{
+ classes_wrap: `col-span-7`,
+ value: `"${trade_product.title}"`,
+ }}
+ />
+ <LineEntryData
+ basis={{
+ classes_wrap: `col-span-5 justify-end capitalize`,
+ value: `${trade_product.key}`,
+ }}
+ />
+ </div>
+ <div class={`grid grid-cols-12 w-full gap-y-[2px]`}>
+ <LineEntriesBetween
+ basis={{
+ classes: `col-span-12`,
+ label: {
+ value: `${$t(`common.origin`)}`,
+ },
+ data: {
+ value: `${fmt_geol_latitude(
+ location_gcs.lat,
+ `d`,
+ 4,
+ )}, ${fmt_geol_longitude(
+ location_gcs.lng,
+ `d`,
+ 4,
+ )}`,
+ },
+ }}
+ />
+ <LineEntryData
+ basis={{
+ classes_wrap: `col-span-12 justify-end capitalize`,
+ value: `${fmt_location_gcs(location_gcs, `city`)}`,
+ }}
+ />
+ </div>
+ <div class={`grid grid-cols-12 w-full gap-y-[2px]`}>
+ {#await parse_currency_price($locale, trade_product.price_currency, trade_product.price_amt) then price}
+ <LineEntriesBetween
+ basis={{
+ classes: `col-span-12`,
+ label: {
+ value: `${$t(`common.price`)}`,
+ },
+ data: {
+ value: `${price ? fmt_currency_price(price) : ``} / ${`${$t(`measurement.mass.unit.${trade_product.price_qty_unit}_ab`)}`}`,
+ },
+ }}
+ />
+ {/await}
+ <LineEntriesBetween
+ basis={{
+ classes: `col-span-12`,
+ label: {
+ value: `${$t(`icu.*_order`, { value: `${$t(`common.quantity`)}` })}`,
+ },
+ data: {
+ value: `${trade_product.qty_amt} / ${`${$t(`measurement.mass.unit.${trade_product.qty_unit}_ab`)}`}`,
+ },
+ }}
+ />
+ <LineEntriesBetween
+ basis={{
+ classes: `col-span-12`,
+ label: {
+ value: `${$t(`icu.*_available`, { value: `${$t(`common.quantity`)}` })}`,
+ },
+ data: {
+ value: `${tradeproduct_qty_avail} ${trade_product.qty_label || fmt_plural_agreement(tradeproduct_qty_avail, `${$t(`common.bag`)}`, `${$t(`common.bags`)}`)}`,
+ },
+ }}
+ />
+ <LineEntriesBetween
+ basis={{
+ classes: `col-span-12`,
+ label: {
+ value: `${$t(`icu.*_sold`, { value: `${$t(`common.quantity`)}` })}`,
+ },
+ data: {
+ value: `${tradeproduct_qty_sold} ${trade_product.qty_label || fmt_plural_agreement(tradeproduct_qty_sold, `${$t(`common.bag`)}`, `${$t(`common.bags`)}`)}`,
+ },
+ }}
+ />
+ <LineEntriesBetween
+ basis={{
+ classes: `col-span-12`,
+ label: {
+ value: `${$t(`common.lot`)}`,
+ },
+ data: {
+ classes: `capitalize`,
+ value: `${trade_product.lot}`,
+ },
+ }}
+ />
+ <LineEntriesBetween
+ basis={{
+ classes: `col-span-12`,
+ label: {
+ value: `${$t(`common.process`)}`,
+ },
+ data: {
+ classes: `capitalize`,
+ value: `${trade_product.process.replaceAll(`_`, ` `)}`,
+ },
+ }}
+ />
+ <LineEntriesBetween
+ basis={{
+ classes: `col-span-12`,
+ label: {
+ value: `${$t(`common.profile`)}`,
+ },
+ data: {
+ classes: `capitalize`,
+ value: `${trade_product.profile.replaceAll(`_`, ` `)}`,
+ },
+ }}
+ />
+ <LineEntriesBetween
+ basis={{
+ classes: `col-span-12`,
+ label: {
+ value: `${$t(`common.year`)}`,
+ },
+ data: {
+ value: `${trade_product.year}`,
+ },
+ }}
+ />
+ </div>
+ <div class={`grid grid-cols-12 w-full gap-y-[2px]`}>
+ {#await parse_currency_price($locale, trade_product.price_currency, trade_product.price_amt * Math.floor(Math.max(trade_product.price_qty_amt, 1)) * mass_tf_str(trade_product.price_qty_unit, trade_product.qty_unit, trade_product.qty_amt)) then price}
+ <LineEntriesBetween
+ basis={{
+ classes: `col-span-12`,
+ label: {
+ value: `${$t(`icu.total_*`, { value: `${$t(`common.value`)}` })}`,
+ },
+ data: {
+ value: `${price ? fmt_currency_price(price) : ``} / ${`${$t(`measurement.mass.unit.${trade_product.price_qty_unit}_ab`)}`}`,
+ },
+ }}
+ />
+ {/await}
+ </div>
+ </div>
+ </div>
+ {/if}
+</div>
diff --git a/src/lib/types.ts b/src/lib/types.ts
@@ -0,0 +1,6 @@
+import type { LocationGcs, TradeProduct } from "@radroots/models";
+
+export type TradeProductBundle = {
+ trade_product: TradeProduct;
+ location_gcs?: LocationGcs;
+};
+\ No newline at end of file
diff --git a/src/lib/utils/trade_product.ts b/src/lib/utils/trade_product.ts
@@ -1,13 +1,13 @@
import { parse_trade_product_form_keys, trade_product_form_fields, trade_product_form_vals, type IModelsForm, type TradeProductFormFields } from "@radroots/models";
import { fmt_id, kv } from "@radroots/svelte-lib";
-import { err_msg, type ErrorMessage, type ResultPass } from "@radroots/utils";
+import { err_msg, obj_en, type ErrorMessage, type ResultPass } from "@radroots/utils";
-const trade_products_field_validate = (field: IModelsForm, field_val: string): boolean => {
+const trade_products_field_validate = (field_basis: IModelsForm, field_val: string): boolean => {
if (
- (!field.optional && !field.validation.test(field_val)) ||
- (field.optional &&
+ (!field_basis.optional && !field_basis.validation.test(field_val)) ||
+ (field_basis.optional &&
field_val &&
- !field.validation.test(field_val))
+ !field_basis.validation.test(field_val))
) return false;
return true;
};
@@ -21,10 +21,7 @@ export const trade_product_fields_assign = async (opts?: {
const fields = {
...trade_product_form_vals
};
- for (const [field_ks, field] of Object.entries(
- trade_product_form_fields,
- )) {
- const field_k = parse_trade_product_form_keys(field_ks);
+ for (const [field_k, _] of obj_en(trade_product_form_fields, parse_trade_product_form_keys)) {
if (!field_k) continue;
const field_val = await kv.get(`${opts?.kv_pref || fmt_id()}-${field_k}`);
if (field_val) fields[field_k] = field_val;
@@ -46,21 +43,15 @@ export const trade_product_fields_validate = async (opts: {
const fields = {
...trade_product_form_vals
};
- for (const [field_ks, field] of Object.entries(
- trade_product_form_fields,
- )) {
- const field_k = parse_trade_product_form_keys(field_ks);
+ for (const [field_k, _] of obj_en(trade_product_form_fields, parse_trade_product_form_keys)) {
if (!field_k) continue;
const field_val = await kv.get(`${opts?.kv_pref || fmt_id()}-${field_k}`);
if (field_val) fields[field_k] = field_val;
}
if (opts?.field_defaults && opts?.field_defaults?.length > 0) for (const [field_k, field_v] of opts?.field_defaults) if (!fields[field_k]) fields[field_k] = field_v;
-
- for (const [field_ks, field_val] of Object.entries(fields)) {
- const field_k = parse_trade_product_form_keys(field_ks);
+ for (const [field_k, field] of obj_en(fields, parse_trade_product_form_keys)) {
if (!field_k) continue;
- const field = trade_product_form_fields[field_k]
- if (!trade_products_field_validate(field, field_val)) {
+ if (!trade_products_field_validate(trade_product_form_fields[field_k], field)) {
if (opts.fields_pass?.includes(field_k)) continue;
return err_msg(field_k);
}
@@ -80,10 +71,7 @@ export const tradeproduct_validate_kv = async (opts?: {
const vals = {
...trade_product_form_vals
};
- for (const [k, field] of Object.entries(
- trade_product_form_fields,
- )) {
- const field_k = parse_trade_product_form_keys(k);
+ for (const [field_k, field] of obj_en(trade_product_form_fields, parse_trade_product_form_keys)) {
if (!field_k) continue;
const field_id = `${opts?.kv_pref || fmt_id()}-${field_k}`;
const field_val = await kv.get(field_id);
@@ -104,10 +92,7 @@ export const tradeproduct_validate_kv = async (opts?: {
export const tradeproduct_init_kv = async (kv_pref: string): Promise<void> => {
try {
- for (const k of Object.keys(
- trade_product_form_fields,
- )) {
- const field_k = parse_trade_product_form_keys(k);
+ for (const [field_k, _] of obj_en(trade_product_form_fields, parse_trade_product_form_keys)) {
if (!field_k) continue;
const field_id = `${kv_pref}-${field_k}`
await kv.delete(field_id);
@@ -122,9 +107,8 @@ export const tradeproduct_validate_fields = async (opts: {
fields: string[];
}): Promise<ResultPass | ErrorMessage<string>> => {
try {
- for (const field of opts.fields) {
- const field_k = parse_trade_product_form_keys(field);
- if (!field_k) return err_msg(field);
+ for (const field_k of opts.fields.map(parse_trade_product_form_keys)) {
+ if (!field_k) return err_msg(field_k);
const field_id = `${opts.kv_pref}-${field_k}`;
const field_val = await kv.get(field_id);
if (!trade_product_form_fields[field_k].validation.test(field_val)) return err_msg(field_k);
diff --git a/src/routes/(app)/models/trade-product/+page.svelte b/src/routes/(app)/models/trade-product/+page.svelte
@@ -1,8 +1,8 @@
<script lang="ts">
import { db } from "$lib/client";
- import { type LocationGcs, type TradeProduct } from "@radroots/models";
+ import TradeProductListCard from "$lib/components/trade_product_list_card.svelte";
+ import type { TradeProductBundle } from "$lib/types";
import {
- app_layout,
app_notify,
LayoutTrellis,
LayoutView,
@@ -12,12 +12,8 @@
} from "@radroots/svelte-lib";
import { onMount } from "svelte";
- type LoadDataResult = {
- trade_product: TradeProduct;
- location_gcs?: LocationGcs;
- };
type LoadData = {
- results: LoadDataResult[];
+ results: TradeProductBundle[];
};
let ld: LoadData | undefined = undefined;
@@ -41,7 +37,7 @@
return;
}
- const results: LoadDataResult[] = [];
+ const results: TradeProductBundle[] = [];
for (const trade_product of trade_products.results) {
const location_gcs = await db.location_gcs_get({
list: [`on_trade_product`, { id: trade_product.id }],
@@ -73,24 +69,11 @@
<LayoutView>
<LayoutTrellis>
{#each ld.results as li, li_i}
- <div
- class={`flex flex-col h-[22rem] w-${$app_layout} justify-start items-start bg-layer-1-surface round-44`}
- >
- <div
- class={`flex flex-row h-[11rem] w-${$app_layout} justify-center items-center border-b-line border-b-layer-1-surface-edge`}
- >
- <p class={`font-sans font-[400] text-layer-0-glyph`}>
- photos
- </p>
- </div>
- <div
- class={`flex flex-row h-[11rem] w-full justify-center items-center`}
- >
- <p class={`font-sans font-[400] text-layer-0-glyph`}>
- body
- </p>
- </div>
- </div>
+ <TradeProductListCard
+ basis={{
+ result: li,
+ }}
+ />
{/each}
</LayoutTrellis>
</LayoutView>
diff --git a/src/routes/(app)/models/trade-product/add/+page.svelte b/src/routes/(app)/models/trade-product/add/+page.svelte
@@ -189,17 +189,14 @@
try {
tradepr_key_sel = page_param.default.tradepr_key;
tradepr_process_sel = `washed`;
-
- tradepr_price_amt_val = `1200.07`;
-
+ tradepr_price_amt_val = `4.30`;
tradepr_qty_tup_sel.set(`60-kg-bag`);
-
await kv_sync([
[fmt_id(`title`), `Green Coffee Beans`],
- [fmt_id(`lot`), `mountain #1`],
+ [fmt_id(`lot`), `Ancestor slope`],
[
fmt_id(`summary`),
- `Good coffee, an amazing batch from our secret hillside with world leading terroir and tasting notes of honey.`,
+ `Good coffee, an amazing year, wonderful flavour, we love it!`,
],
]);
} catch (e) {
@@ -249,7 +246,6 @@
num_str(tradepr_parsed_quantity.mass),
);
await kv.set(fmt_id(`qty_unit`), tradepr_parsed_quantity.mass_unit);
- await kv.set(fmt_id(`qty_label`), tradepr_parsed_quantity.label);
}
});
@@ -435,10 +431,10 @@
if (!tradepr_photo_paths.length) {
const confirm = await dialog.confirm({
message: `${`${$t(`icu.the_listing_will_be_created_without_a_*`, { value: `${$t(`common.photo`)}`.toLowerCase() })}`}. ${$t(`common.do_you_want_to_continue_q`)}`,
- ok_label: `${$t(`icu.add_*`, { value: `${$t(`common.photo`)}` })}`,
- cancel_label: `${$t(`common.continue`)}`,
+ cancel_label: `${$t(`icu.add_*`, { value: `${$t(`common.photo`)}` })}`,
+ ok_label: `${$t(`common.continue`)}`,
});
- if (confirm) {
+ if (!confirm) {
await el_focus(
fmt_id(`image-upload-control`),
async () => await handle_back(2),
@@ -497,6 +493,10 @@
[`year`, year_curr()],
],
});
+ console.log(
+ JSON.stringify(trade_product_fields, null, 4),
+ `trade_product_fields`,
+ );
if (`err` in trade_product_fields) {
await dialog.alert(
`${$t(`icu.the_*_is_incomplete`, { value: `${$t(`models.trade_product.fields.${trade_product_fields.err}.label`)}`.toLowerCase() })}`,
@@ -523,7 +523,7 @@
},
});
if (`pass` in trade_product_location_set) {
- route(`/models/nostr-relay/view`);
+ route(`/models/trade-product`);
return;
}
await dialog.alert(