commit e90dcbc825884e6621c89911e9f50fb76a6b0a61
parent 9777d622d9023b748e95bf2ec09d2b617d1c82ae
Author: triesap <137732411+triesap@users.noreply.github.com>
Date: Sun, 27 Apr 2025 05:32:51 +0000
apps-lib: add farms add view, edit farms view, add validation utils
Diffstat:
5 files changed, 282 insertions(+), 49 deletions(-)
diff --git a/apps-lib/package.json b/apps-lib/package.json
@@ -57,6 +57,7 @@
"@radroots/util": "workspace:*",
"luxon": "^3.5.0",
"svelte-maplibre": "1.0.0-next.12",
- "sveltekit-search-params": "^3.0.0"
+ "sveltekit-search-params": "^3.0.0",
+ "zod": "^3.23.8"
}
}
\ No newline at end of file
diff --git a/apps-lib/src/lib/index.ts b/apps-lib/src/lib/index.ts
@@ -112,7 +112,9 @@ export * from "./util/nostr/nostr-poll-relays.js"
export * from "./util/nostr/nostr-sync.js"
export * from "./util/service/nostr-event-classified.js"
export * from "./util/service/nostr-sync.js"
+export * from "./util/validation/farm.js"
export * from "./util/view.js"
+export { default as FarmsAdd } from "./view/farms-add.svelte"
export { default as Farms } from "./view/farms.svelte"
export { default as Home } from "./view/home.svelte"
export { default as Notifications } from "./view/notifications.svelte"
diff --git a/apps-lib/src/lib/util/validation/farm.ts b/apps-lib/src/lib/util/validation/farm.ts
@@ -0,0 +1,12 @@
+import type { IViewFarmsAddSubmission } from "$lib/types/view/farm";
+import { form_fields, schema_geolocation_address, schema_geolocation_point, zf_numf_pos } from "@radroots/util";
+import { z } from "zod";
+
+export const schema_view_farms_add_submission: z.ZodSchema<IViewFarmsAddSubmission> = z.object({
+ farm_name: z.string().regex(form_fields.farm_name.validate),
+ farm_area: zf_numf_pos,
+ farm_area_unit: z.string().regex(form_fields.area_unit.validate),
+ farm_contact_name: z.string().regex(form_fields.contact_name.validate),
+ geolocation_point: schema_geolocation_point,
+ geolocation_address: schema_geolocation_address,
+});
+\ No newline at end of file
diff --git a/apps-lib/src/lib/view/farms-add.svelte b/apps-lib/src/lib/view/farms-add.svelte
@@ -0,0 +1,220 @@
+<script lang="ts">
+ import LayoutBottomButton from "$lib/components/layout/layout-bottom-button.svelte";
+ import Fade from "$lib/components/lib/fade.svelte";
+ import { schema_view_farms_add_submission } from "$lib/util/validation/farm";
+ import {
+ app_platform,
+ ButtonLayoutPair,
+ Carousel,
+ casl_dec,
+ casl_i,
+ casl_inc,
+ casl_init,
+ FarmsAddCasliDetail,
+ FarmsAddCasliMap,
+ Glyph,
+ handle_err,
+ LayoutView,
+ PageToolbar,
+ type CallbackRoute,
+ type IViewFarmsAddSubmission,
+ type LcGeocodeCallback,
+ type LcGeocodeCurrentCallback,
+ type LcGuiAlertCallback,
+ } from "$root";
+ import {
+ geol_lat_fmt,
+ geol_lng_fmt,
+ parse_float,
+ parse_geocode_address,
+ type CallbackPromiseGeneric,
+ type GeocoderReverseResult,
+ type GeolocationAddress,
+ type GeolocationPoint,
+ type I18nTranslateFunction,
+ type I18nTranslateLocale,
+ } from "@radroots/util";
+ import { onMount } from "svelte";
+
+ let {
+ basis,
+ ls,
+ locale,
+ }: {
+ basis: {
+ callback_route?: CallbackRoute<string>;
+ lc_gui_alert: LcGuiAlertCallback;
+ lc_geop_current: LcGeocodeCurrentCallback;
+ lc_geocode: LcGeocodeCallback;
+ lc_submit: CallbackPromiseGeneric<{
+ data_s: IViewFarmsAddSubmission;
+ }>;
+ };
+ ls: I18nTranslateFunction;
+ locale: I18nTranslateLocale;
+ } = $props();
+
+ let loading = $state(false);
+
+ let map_geop: GeolocationPoint = $state({ lat: 0, lng: 0 });
+ let map_geoc: GeocoderReverseResult | undefined = $state(undefined);
+
+ let val_farmname = $state(``);
+ let val_farmcontact = $state(``);
+ let val_farmarea = $state(``);
+ let val_farmarea_unit = $state(`ac`);
+
+ const disabled_submit = $derived($casl_i === 1 && !val_farmname);
+
+ onMount(async () => {
+ try {
+ casl_init(0, 2);
+ } catch (e) {
+ handle_err(e, `on_mount`);
+ }
+ });
+
+ const farm_geop_lat = $derived(
+ map_geop ? geol_lat_fmt(map_geop.lat, `dms`, $locale, 3) : ``,
+ );
+
+ const farm_geop_lng = $derived(
+ map_geop ? geol_lng_fmt(map_geop.lng, `dms`, $locale, 3) : ``,
+ );
+
+ const farm_geolocation_address: GeolocationAddress | undefined = $derived(
+ parse_geocode_address(map_geoc),
+ );
+
+ const handle_continue_0 = async (): Promise<void> => {
+ await casl_inc();
+ };
+
+ const handle_continue_1 = async (): Promise<void> => {
+ if (!map_geop)
+ return void basis.lc_gui_alert(`No farm location provided.`); //@todo
+ if (!farm_geolocation_address)
+ return void basis.lc_gui_alert(`No farm address provided.`); //@todo
+ const farms_add_submission = schema_view_farms_add_submission.safeParse(
+ {
+ farm_name: val_farmname,
+ farm_area: parse_float(val_farmarea),
+ farm_area_unit: val_farmarea_unit,
+ farm_contact_name: val_farmcontact,
+ geolocation_point: map_geop,
+ geolocation_address: farm_geolocation_address,
+ } satisfies IViewFarmsAddSubmission,
+ );
+
+ if (!farms_add_submission.success) {
+ return void basis.lc_gui_alert(
+ `Request invalid: ${farms_add_submission.error}`,
+ ); //@todo
+ }
+ loading = true;
+ await basis.lc_submit({ data_s: farms_add_submission.data });
+ loading = false;
+ };
+
+ const handle_continue = async (): Promise<void> => {
+ switch ($casl_i) {
+ case 0:
+ return await handle_continue_0();
+ case 1:
+ return await handle_continue_1();
+ }
+ };
+
+ const handle_back = async (): Promise<void> => {
+ switch ($casl_i) {
+ default:
+ return await casl_dec();
+ }
+ };
+</script>
+
+<LayoutView>
+ <PageToolbar
+ basis={{
+ header: {
+ label: `Farm / Add`,
+ callback_route: basis.callback_route,
+ },
+ }}
+ >
+ {#snippet header_option()}
+ {#if $casl_i > 0}
+ <Fade>
+ <button
+ class={`flex flex-row pr-3 justify-center items-center`}
+ onclick={async () => {
+ await handle_back();
+ }}
+ >
+ <p
+ class={`font-sans font-[600] text-lg text-layer-0-glyph`}
+ >
+ {`${$ls(`common.back`)}`}
+ </p>
+ </button>
+ </Fade>
+ {/if}
+ <button
+ class={`flex flex-row justify-center items-center`}
+ onclick={async () => {
+ await handle_continue();
+ }}
+ >
+ <p class={`font-sans font-[600] text-lg text-layer-0-glyph-hl`}>
+ {`${$ls(`common.details`)}`}
+ </p>
+ <Glyph
+ basis={{
+ classes: `text-layer-0-glyph-hl`,
+ dim: `md`,
+ key: `caret-right`,
+ }}
+ />
+ </button>
+ {/snippet}
+ </PageToolbar>
+ <Carousel>
+ <FarmsAddCasliMap
+ bind:map_geop
+ bind:map_geoc
+ {farm_geop_lat}
+ {farm_geop_lng}
+ basis={{
+ lc_geocode: basis.lc_geocode,
+ lc_geop_current: basis.lc_geop_current,
+ }}
+ />
+ <FarmsAddCasliDetail
+ bind:val_farmname
+ bind:val_farmcontact
+ bind:val_farmarea
+ bind:val_farmarea_unit
+ {farm_geop_lat}
+ {farm_geop_lng}
+ {ls}
+ />
+ </Carousel>
+</LayoutView>
+{#if $app_platform?.browser !== `safari`}
+ <LayoutBottomButton>
+ <ButtonLayoutPair
+ basis={{
+ continue: {
+ label: `${$ls(`common.continue`)}`,
+ disabled: disabled_submit,
+ callback: handle_continue,
+ },
+ back: {
+ label: `${$ls(`common.back`)}`,
+ visible: $casl_i > 0,
+ callback: handle_back,
+ },
+ }}
+ />
+ </LayoutBottomButton>
+{/if}
diff --git a/apps-lib/src/lib/view/farms.svelte b/apps-lib/src/lib/view/farms.svelte
@@ -47,53 +47,50 @@
});
</script>
-{#if basis.data}
- {@const { data: basis_data } = basis}
- <LayoutView>
- <PageToolbar
- basis={{
- header: {
- label: `${$ls(`common.farms`)}`,
- callback_route: basis.callback_route,
- },
- }}
- >
- {#snippet header_option()}
- {#if basis_data.list.length}
- <Fade>
- <GlyphButtonSimple
- basis={{
- label: `${$ls(`icu.add_*`, { value: `${$ls(`common.farm`)}` })}`,
- callback: async () => {
- await basis.lc_handle_farm_add();
- },
- }}
- />
- </Fade>
- {/if}
- {/snippet}
- </PageToolbar>
- <LayoutPage>
- {#if basis_data.list.length}
- {#each basis_data.list as li}
- <FarmsDisplayLiEl
- basis={li}
- lc_geocode={basis.lc_geocode}
- lc_handle_farm_view={basis.lc_handle_farm_view}
- {ls}
- {locale}
+<LayoutView>
+ <PageToolbar
+ basis={{
+ header: {
+ label: `${$ls(`common.farms`)}`,
+ callback_route: basis.callback_route,
+ },
+ }}
+ >
+ {#snippet header_option()}
+ {#if basis.data?.list.length}
+ <Fade>
+ <GlyphButtonSimple
+ basis={{
+ label: `${$ls(`icu.add_*`, { value: `${$ls(`common.farm`)}` })}`,
+ callback: async () => {
+ await basis.lc_handle_farm_add();
+ },
+ }}
/>
- {/each}
- {:else}
- <ButtonLabelDashed
- basis={{
- label: `Add farm`,
- callback: async () => {
- await basis.lc_handle_farm_add();
- },
- }}
- />
+ </Fade>
{/if}
- </LayoutPage>
- </LayoutView>
-{/if}
+ {/snippet}
+ </PageToolbar>
+ <LayoutPage>
+ {#if basis.data?.list.length}
+ {#each basis.data?.list as li}
+ <FarmsDisplayLiEl
+ basis={li}
+ lc_geocode={basis.lc_geocode}
+ lc_handle_farm_view={basis.lc_handle_farm_view}
+ {ls}
+ {locale}
+ />
+ {/each}
+ {:else}
+ <ButtonLabelDashed
+ basis={{
+ label: `Add farm`,
+ callback: async () => {
+ await basis.lc_handle_farm_add();
+ },
+ }}
+ />
+ {/if}
+ </LayoutPage>
+</LayoutView>