feat(MED-100): analysis location select in cart

This commit is contained in:
2025-07-24 08:04:25 +03:00
parent 23f656ccc9
commit b9f40d6a2d
5 changed files with 145 additions and 10 deletions

View File

@@ -5,7 +5,7 @@ import { notFound } from 'next/navigation';
import { retrieveCart } from '~/medusa/lib/data/cart';
import Cart from '../../_components/cart';
import { listCollections } from '@lib/data';
import { listProductTypes } from '@lib/data';
import CartTimer from '../../_components/cart/cart-timer';
import { Trans } from '@kit/ui/trans';
@@ -23,15 +23,12 @@ export default async function CartPage() {
return notFound();
});
const { collections } = await listCollections({
limit: "100",
});
const analysisPackagesCollection = collections.find(({ handle }) => handle === 'analysis-packages');
const analysisPackages = analysisPackagesCollection && cart?.items
? cart.items.filter((item) => item.product?.collection_id === analysisPackagesCollection.id)
const { productTypes } = await listProductTypes();
const analysisPackagesType = productTypes.find(({ metadata }) => metadata?.handle === 'analysis-packages');
const analysisPackages = analysisPackagesType && cart?.items
? cart.items.filter((item) => item.product?.type_id === analysisPackagesType.id)
: [];
const otherItems = cart?.items?.filter((item) => item.product?.collection_id !== analysisPackagesCollection?.id) ?? [];
const otherItems = cart?.items?.filter((item) => item.product?.type_id !== analysisPackagesType?.id) ?? [];
const otherItemsSorted = otherItems.sort((a, b) => (a.updated_at ?? "") > (b.updated_at ?? "") ? -1 : 1);
const item = otherItemsSorted[0];

View File

@@ -0,0 +1,99 @@
"use client"
import { toast } from 'sonner';
import { useForm } from "react-hook-form";
import { z } from "zod";
import { updateCart } from "@lib/data/cart"
import { StoreCart } from "@medusajs/types"
import { Form } from "@kit/ui/form";
import { Trans } from '@kit/ui/trans';
import { useTranslation } from "react-i18next";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from '@kit/ui/select';
const AnalysisLocationSchema = z.object({
locationId: z.string().min(1),
});
const MOCK_LOCATIONS: { id: string, name: string }[] = [
{ id: "synlab-tallinn-1", name: "SYNLAB - Tallinn" },
{ id: "synlab-tartu-1", name: "SYNLAB - Tartu" },
{ id: "synlab-parnu-1", name: "SYNLAB - Pärnu" },
]
export default function AnalysisLocation({ cart }: { cart: StoreCart }) {
const { t } = useTranslation('cart');
const form = useForm<z.infer<typeof AnalysisLocationSchema>>({
defaultValues: {
locationId: cart.metadata?.partner_location_id as string ?? '',
},
resolver: zodResolver(AnalysisLocationSchema),
});
const onSubmit = async ({ locationId }: z.infer<typeof AnalysisLocationSchema>) => {
const promise = updateCart({
metadata: {
partner_location_name: MOCK_LOCATIONS.find(({ id }) => id === locationId)?.name ?? '',
partner_location_id: locationId,
},
});
toast.promise(promise, {
success: t(`cart:items.analysisLocation.success`),
loading: t(`cart:items.analysisLocation.loading`),
error: t(`cart:items.analysisLocation.error`),
});
}
return (
<div className="w-full bg-white flex flex-col txt-medium">
<Form {...form}>
<form
onSubmit={form.handleSubmit((data) => onSubmit(data))}
className="w-full mb-2 flex gap-x-2"
>
<Select
value={form.watch('locationId')}
onValueChange={(value) => {
form.setValue('locationId', value, {
shouldValidate: true,
shouldDirty: true,
shouldTouch: true,
});
return onSubmit(form.getValues());
}}
>
<SelectTrigger>
<SelectValue placeholder={t('cart:locations.locationSelect')} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>{t('cart:locations.locationSelect')}</SelectLabel>
{MOCK_LOCATIONS.map((location) => (
<SelectItem key={location.id} value={location.id}>{location.name}</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</form>
</Form>
<p className="text-sm text-muted-foreground">
<Trans i18nKey={'cart:locations.description'} />
</p>
</div>
)
}

View File

@@ -16,6 +16,7 @@ import { initiatePaymentSession } from "@lib/data/cart";
import { formatCurrency } from "@/packages/shared/src/utils";
import { useTranslation } from "react-i18next";
import { handleNavigateToPayment } from "@/lib/services/medusaCart.service";
import AnalysisLocation from "./analysis-location";
const IS_DISCOUNT_SHOWN = false as boolean;
@@ -66,13 +67,16 @@ export default function Cart({
}
}
const hasCartItems = Array.isArray(cart.items) && cart.items.length > 0;
const isLocationsShown = analysisPackages.length > 0;
return (
<div className="grid grid-cols-1 small:grid-cols-[1fr_360px] gap-x-40 lg:px-4">
<div className="flex flex-col bg-white gap-y-6">
<CartItems cart={cart} items={analysisPackages} productColumnLabelKey="cart:items.analysisPackages.productColumnLabel" />
<CartItems cart={cart} items={otherItems} productColumnLabelKey="cart:items.services.productColumnLabel" />
</div>
{Array.isArray(cart.items) && cart.items.length > 0 && (
{hasCartItems && (
<div className="flex justify-end gap-x-4 px-6 py-4">
<div className="mr-[36px]">
<p className="ml-0 font-bold text-sm">
@@ -106,6 +110,21 @@ export default function Cart({
</CardContent>
</Card>
)}
{isLocationsShown && (
<Card
className="flex flex-col justify-between w-1/2"
>
<CardHeader className="pb-4">
<h5>
<Trans i18nKey="cart:locations.title" />
</h5>
</CardHeader>
<CardContent>
<AnalysisLocation cart={{ ...cart }} />
</CardContent>
</Card>
)}
</div>
<div>

View File

@@ -40,6 +40,11 @@
"success": "Item removed from cart",
"loading": "Removing item from cart",
"error": "Failed to remove item from cart"
},
"analysisLocation": {
"success": "Location updated",
"loading": "Updating location",
"error": "Failed to update location"
}
},
"orderConfirmed": {
@@ -57,5 +62,10 @@
"montonioCallback": {
"title": "Montonio checkout",
"description": "Please wait while we process your payment."
},
"locations": {
"title": "Location for analysis",
"description": "If you are unable to go to the lab to collect the sample, you can go to any other suitable collection point.",
"locationSelect": "Select location"
}
}

View File

@@ -41,6 +41,11 @@
"success": "Toode eemaldatud ostukorvist",
"loading": "Toote eemaldamine ostukorvist",
"error": "Toote eemaldamine ostukorvist ebaõnnestus"
},
"analysisLocation": {
"success": "Asukoht uuendatud",
"loading": "Asukoha uuendamine",
"error": "Asukoha uuendamine ebaõnnestus"
}
},
"orderConfirmed": {
@@ -58,5 +63,10 @@
"montonioCallback": {
"title": "Montonio makseprotsess",
"description": "Palun oodake, kuni me töötleme sinu makseprotsessi lõpuni."
},
"locations": {
"title": "Asukoht analüüside andmiseks",
"description": "Kui Teil ei ole võimalik valitud asukohta minna analüüse andma, siis võite minna Teile sobivasse verevõtupunkti.",
"locationSelect": "Vali asukoht"
}
}