diff --git a/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx b/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx index 9b23701..6714f80 100644 --- a/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx @@ -19,7 +19,7 @@ const Level = ({ isLast = false, }: { isActive?: boolean; - color: 'destructive' | 'success' | 'warning'; + color: 'destructive' | 'success' | 'warning' | 'gray-200'; isFirst?: boolean; isLast?: boolean; }) => { @@ -40,6 +40,14 @@ const Level = ({ ); }; +export const AnalysisLevelBarSkeleton = () => { + return ( +
+ +
+ ); +}; + const AnalysisLevelBar = ({ normLowerIncluded = true, normUpperIncluded = true, @@ -50,7 +58,7 @@ const AnalysisLevelBar = ({ level: AnalysisResultLevel; }) => { return ( -
+
{normLowerIncluded && ( <> { + const name = analysisElement.analysis_name_lab || ''; + const status = results?.norm_status || AnalysisStatus.NORMAL; + const value = results?.response_value || 0; + const unit = results?.unit || ''; + const normLowerIncluded = results?.norm_lower_included || false; + const normUpperIncluded = results?.norm_upper_included || false; + const normLower = results?.norm_lower || 0; + const normUpper = results?.norm_upper || 0; + const [showTooltip, setShowTooltip] = useState(false); const isUnderNorm = value < normLower; const getAnalysisResultLevel = () => { + if (!results) { + return null; + } if (isUnderNorm) { switch (status) { case AnalysisStatus.MEDIUM: @@ -59,38 +59,56 @@ const Analysis = ({ }; return ( -
+
{name} -
setShowTooltip(!showTooltip)} - onMouseLeave={() => setShowTooltip(false)} - > - {' '} + {results?.response_time && ( -
+ )}
-
-
{value}
-
{unit}
-
-
- {normLower} - {normUpper} -
Normaalne vahemik
-
- + {results ? ( + <> +
+
{value}
+
{unit}
+
+
+ {normLower} - {normUpper} +
+ +
+
+ + + ) : ( + <> +
+
+ +
+
+
+ + + )}
); }; diff --git a/app/home/(user)/(dashboard)/analysis-results/page.tsx b/app/home/(user)/(dashboard)/analysis-results/page.tsx index 0d3687c..60c43d5 100644 --- a/app/home/(user)/(dashboard)/analysis-results/page.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/page.tsx @@ -1,4 +1,5 @@ -import { Fragment } from 'react'; +import Link from 'next/link'; + import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; import { withI18n } from '@/lib/i18n/with-i18n'; @@ -7,11 +8,17 @@ import { PageBody } from '@kit/ui/page'; import { Button } from '@kit/ui/shadcn/button'; import { loadUserAnalysis } from '../../_lib/server/load-user-analysis'; -import Analysis, { AnalysisStatus } from './_components/analysis'; +import Analysis from './_components/analysis'; +import pathsConfig from '~/config/paths.config'; +import { redirect } from 'next/navigation'; +import { getAnalysisOrders } from '~/lib/services/order.service'; +import { getAnalysisElements } from '~/lib/services/analysis-element.service'; +import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account'; +import { createPageViewLog } from '~/lib/services/audit/pageView.service'; export const generateMetadata = async () => { const i18n = await createI18nServerInstance(); - const title = i18n.t('account:analysisResults.pageTitle'); + const title = i18n.t('analysis-results:pageTitle'); return { title, @@ -19,47 +26,81 @@ export const generateMetadata = async () => { }; async function AnalysisResultsPage() { - const analysisList = await loadUserAnalysis(); + const account = await loadCurrentUserAccount() + if (!account) { + throw new Error('Account not found'); + } + + const analysisResponses = await loadUserAnalysis(); + const analysisResponseElements = analysisResponses?.flatMap(({ elements }) => elements); + + const analysisOrders = await getAnalysisOrders().catch(() => null); + + if (!analysisOrders) { + redirect(pathsConfig.auth.signIn); + } + + await createPageViewLog({ + accountId: account.id, + action: 'VIEW_ANALYSIS_RESULTS', + }); + + const analysisElementIds = [ + ...new Set(analysisOrders?.flatMap((order) => order.analysis_element_ids).filter(Boolean) as number[]), + ]; + const analysisElements = await getAnalysisElements({ ids: analysisElementIds }); + const analysisElementsWithResults = analysisResponseElements + ?.sort((a, b) => { + if (!a.response_time || !b.response_time) { + return 0; + } + return new Date(b.response_time).getTime() - new Date(a.response_time).getTime(); + }) + .map((results) => ({ results })) ?? []; + const analysisElementsWithoutResults = analysisElements + .filter((element) => !analysisElementsWithResults?.some(({ results }) => results.analysis_element_original_id === element.analysis_id_original)); + + const hasNoAnalysisElements = analysisElementsWithResults.length === 0 && analysisElementsWithoutResults.length === 0; return ( -
+

- +

- {analysisList && analysisList.length > 0 ? ( - + {analysisResponses && analysisResponses.length > 0 ? ( + ) : ( - + )}

-
- {analysisList?.map((analysis) => ( - - {analysis.elements.map((element) => ( - - ))} - + {analysisElementsWithResults.map(({ results }) => { + const analysisElement = analysisElements.find((element) => element.analysis_id_original === results.analysis_element_original_id); + if (!analysisElement) { + return null; + } + return ( + + ); + })} + {analysisElementsWithoutResults.map((element) => ( + ))} + {hasNoAnalysisElements && ( +
+ +
+ )}
); diff --git a/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts b/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts index e48ac85..bede603 100644 --- a/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts +++ b/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts @@ -91,7 +91,7 @@ export async function processMontonioCallback(orderToken: string) { const medusaOrder = await placeOrder(cartId, { revalidateCacheTags: false }); const orderedAnalysisElements = await getOrderedAnalysisElementsIds({ medusaOrder }); - await createOrder({ medusaOrder, orderedAnalysisElements }); + const orderId = await createOrder({ medusaOrder, orderedAnalysisElements }); const { productTypes } = await listProductTypes(); const analysisPackagesType = productTypes.find(({ metadata }) => metadata?.handle === ANALYSIS_PACKAGES_TYPE_HANDLE); @@ -119,10 +119,9 @@ export async function processMontonioCallback(orderToken: string) { console.error("Missing email or analysisPackageName", orderResult); } - // Send order to Medipost (no await to avoid blocking) sendOrderToMedipost({ medusaOrderId, orderedAnalysisElements }); - return { success: true }; + return { success: true, orderId }; } catch (error) { console.error("Failed to place order", error); throw new Error(`Failed to place order, message=${error}`); diff --git a/app/home/(user)/(dashboard)/cart/montonio-callback/client-component.tsx b/app/home/(user)/(dashboard)/cart/montonio-callback/client-component.tsx index f90efa4..c388a6d 100644 --- a/app/home/(user)/(dashboard)/cart/montonio-callback/client-component.tsx +++ b/app/home/(user)/(dashboard)/cart/montonio-callback/client-component.tsx @@ -29,8 +29,8 @@ export default function MontonioCallbackClient({ orderToken, error }: { setHasProcessed(true); try { - await processMontonioCallback(orderToken); - router.push('/home/order'); + const { orderId } = await processMontonioCallback(orderToken); + router.push(`/home/order/${orderId}/confirmed`); } catch (error) { console.error("Failed to place order", error); router.push('/home/cart/montonio-callback/error'); diff --git a/app/home/(user)/(dashboard)/cart/montonio-callback/page.tsx b/app/home/(user)/(dashboard)/cart/montonio-callback/page.tsx index 6893a97..d4f54f4 100644 --- a/app/home/(user)/(dashboard)/cart/montonio-callback/page.tsx +++ b/app/home/(user)/(dashboard)/cart/montonio-callback/page.tsx @@ -7,7 +7,6 @@ export default async function MontonioCallbackPage({ searchParams }: { }) { const orderToken = (await searchParams)['order-token']; - console.log('orderToken', orderToken); if (!orderToken) { return ; } diff --git a/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx index 2ca1b55..f49e5cb 100644 --- a/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx +++ b/app/home/(user)/(dashboard)/order/[orderId]/confirmed/page.tsx @@ -1,13 +1,16 @@ -import { notFound } from 'next/navigation'; +import { redirect } from 'next/navigation'; +import { PageBody, PageHeader } from '@kit/ui/page'; -import { retrieveOrder } from '~/medusa/lib/data/orders'; import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; -import OrderCompleted from '@/app/home/(user)/_components/order/order-completed'; import { withI18n } from '~/lib/i18n/with-i18n'; - -type Props = { - params: Promise<{ orderId: string }>; -}; +import { getOrder } from '~/lib/services/order.service'; +import { retrieveOrder } from '@lib/data/orders'; +import pathsConfig from '~/config/paths.config'; +import Divider from "@modules/common/components/divider" +import OrderDetails from '@/app/home/(user)/_components/order/order-details'; +import OrderItems from '@/app/home/(user)/_components/order/order-items'; +import CartTotals from '@/app/home/(user)/_components/order/cart-totals'; +import { Trans } from '@kit/ui/trans'; export async function generateMetadata() { const { t } = await createI18nServerInstance(); @@ -17,15 +20,33 @@ export async function generateMetadata() { }; } -async function OrderConfirmedPage(props: Props) { +async function OrderConfirmedPage(props: { + params: Promise<{ orderId: string }>; +}) { const params = await props.params; - const order = await retrieveOrder(params.orderId).catch(() => null); + const order = await getOrder({ orderId: Number(params.orderId) }).catch(() => null); if (!order) { - return notFound(); + redirect(pathsConfig.app.myOrders); } - return ; + const medusaOrder = await retrieveOrder(order.medusa_order_id).catch(() => null); + if (!medusaOrder) { + redirect(pathsConfig.app.myOrders); + } + + return ( + + } /> + +
+ + + + +
+
+ ); } export default withI18n(OrderConfirmedPage); diff --git a/app/home/(user)/(dashboard)/order/[orderId]/page.tsx b/app/home/(user)/(dashboard)/order/[orderId]/page.tsx new file mode 100644 index 0000000..b9e8597 --- /dev/null +++ b/app/home/(user)/(dashboard)/order/[orderId]/page.tsx @@ -0,0 +1,52 @@ +import { redirect } from 'next/navigation'; +import { PageBody, PageHeader } from '@kit/ui/page'; + +import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; +import { withI18n } from '~/lib/i18n/with-i18n'; +import { getOrder } from '~/lib/services/order.service'; +import { retrieveOrder } from '@lib/data/orders'; +import pathsConfig from '~/config/paths.config'; +import Divider from "@modules/common/components/divider" +import OrderDetails from '@/app/home/(user)/_components/order/order-details'; +import OrderItems from '@/app/home/(user)/_components/order/order-items'; +import CartTotals from '@/app/home/(user)/_components/order/cart-totals'; +import { Trans } from '@kit/ui/trans'; + +export async function generateMetadata() { + const { t } = await createI18nServerInstance(); + + return { + title: t('cart:order.title'), + }; +} + +async function OrderConfirmedPage(props: { + params: Promise<{ orderId: string }>; +}) { + const params = await props.params; + + const order = await getOrder({ orderId: Number(params.orderId) }).catch(() => null); + if (!order) { + redirect(pathsConfig.app.myOrders); + } + + const medusaOrder = await retrieveOrder(order.medusa_order_id).catch(() => null); + if (!medusaOrder) { + redirect(pathsConfig.app.myOrders); + } + + return ( + + } /> + +
+ + + + +
+
+ ); +} + +export default withI18n(OrderConfirmedPage); diff --git a/app/home/(user)/(dashboard)/order/page.tsx b/app/home/(user)/(dashboard)/order/page.tsx index 98c1c4f..4b1d747 100644 --- a/app/home/(user)/(dashboard)/order/page.tsx +++ b/app/home/(user)/(dashboard)/order/page.tsx @@ -3,7 +3,6 @@ import { redirect } from 'next/navigation'; import { listOrders } from '~/medusa/lib/data/orders'; import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; import { listProductTypes } from '@lib/data/products'; -import { retrieveCustomer } from '@lib/data/customer'; import { PageBody } from '@kit/ui/makerkit/page'; import pathsConfig from '~/config/paths.config'; import { Trans } from '@kit/ui/trans'; @@ -11,6 +10,7 @@ import { HomeLayoutPageHeader } from '../../_components/home-page-header'; import OrdersTable from '../../_components/orders/orders-table'; import { withI18n } from '~/lib/i18n/with-i18n'; import type { IOrderLineItem } from '../../_components/orders/types'; +import { getAnalysisOrders } from '~/lib/services/order.service'; export async function generateMetadata() { const { t } = await createI18nServerInstance(); @@ -21,24 +21,49 @@ export async function generateMetadata() { } async function OrdersPage() { - const customer = await retrieveCustomer(); - const orders = await listOrders().catch(() => null); + const medusaOrders = await listOrders(); + const analysisOrders = await getAnalysisOrders(); const { productTypes } = await listProductTypes(); - if (!customer || !orders || !productTypes) { + if (!medusaOrders || !productTypes) { redirect(pathsConfig.auth.signIn); } const analysisPackagesType = productTypes.find(({ metadata }) => metadata?.handle === 'analysis-packages'); - const analysisPackageOrders: IOrderLineItem[] = orders.flatMap(({ id, items, payment_status, fulfillment_status }) => items + const analysisPackageOrders: IOrderLineItem[] = medusaOrders.flatMap(({ id, items, payment_status, fulfillment_status }) => items ?.filter((item) => item.product_type_id === analysisPackagesType?.id) - .map((item) => ({ item, orderId: id, orderStatus: `${payment_status}/${fulfillment_status}` })) + .map((item) => { + const localOrder = analysisOrders.find((order) => order.medusa_order_id === id); + if (!localOrder) { + return null; + } + return { + item, + medusaOrderId: id, + orderId: localOrder?.id, + orderStatus: localOrder.status, + analysis_element_ids: localOrder.analysis_element_ids, + } + }) + .filter((order) => order !== null) || []); - const otherOrders: IOrderLineItem[] = orders + const otherOrders: IOrderLineItem[] = medusaOrders .filter(({ items }) => items?.some((item) => item.product_type_id !== analysisPackagesType?.id)) .flatMap(({ id, items, payment_status, fulfillment_status }) => items - ?.map((item) => ({ item, orderId: id, orderStatus: `${payment_status}/${fulfillment_status}` })) + ?.map((item) => { + const analysisOrder = analysisOrders.find((order) => order.medusa_order_id === id); + if (!analysisOrder) { + return null; + } + return { + item, + medusaOrderId: id, + orderId: analysisOrder.id, + orderStatus: analysisOrder.status, + } + }) + .filter((order) => order !== null) || []); return ( diff --git a/app/home/(user)/_components/order/cart-totals.tsx b/app/home/(user)/_components/order/cart-totals.tsx index dc25aad..2df2237 100644 --- a/app/home/(user)/_components/order/cart-totals.tsx +++ b/app/home/(user)/_components/order/cart-totals.tsx @@ -6,8 +6,8 @@ import React from "react" import { useTranslation } from "react-i18next" import { Trans } from '@kit/ui/trans'; -export default function CartTotals({ order }: { - order: StoreOrder +export default function CartTotals({ medusaOrder }: { + medusaOrder: StoreOrder }) { const { i18n: { language } } = useTranslation() const { @@ -17,7 +17,7 @@ export default function CartTotals({ order }: { tax_total, discount_total, gift_card_total, - } = order + } = medusaOrder return (
diff --git a/app/home/(user)/_components/order/order-completed.tsx b/app/home/(user)/_components/order/order-completed.tsx deleted file mode 100644 index f7529a0..0000000 --- a/app/home/(user)/_components/order/order-completed.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Trans } from '@kit/ui/trans'; -import { PageBody, PageHeader } from '@kit/ui/page'; -import { StoreOrder } from "@medusajs/types" -import Divider from "@modules/common/components/divider" - -import CartTotals from "./cart-totals" -import OrderDetails from "./order-details" -import OrderItems from "./order-items" - -export default async function OrderCompleted({ - order, -}: { - order: StoreOrder, -}) { - return ( - - } /> - -
- - - - -
-
- ) -} diff --git a/app/home/(user)/_components/order/order-details.tsx b/app/home/(user)/_components/order/order-details.tsx index e6bc6cf..b750c85 100644 --- a/app/home/(user)/_components/order/order-details.tsx +++ b/app/home/(user)/_components/order/order-details.tsx @@ -1,47 +1,21 @@ -import { StoreOrder } from "@medusajs/types" import { Trans } from '@kit/ui/trans'; +import { formatDate } from 'date-fns'; +import { AnalysisOrder } from "~/lib/services/order.service"; -export default function OrderDetails({ order, showStatus }: { - order: StoreOrder - showStatus?: boolean +export default function OrderDetails({ order }: { + order: AnalysisOrder }) { - const formatStatus = (str: string) => { - const formatted = str.split("_").join(" ") - - return formatted.slice(0, 1).toUpperCase() + formatted.slice(1) - } - return (
:{" "} - {new Date(order.created_at).toLocaleDateString()} + {formatDate(order.created_at, 'dd.MM.yyyy HH:mm')} - : {order.display_id} + : {order.medusa_order_id} - - {showStatus && ( - <> - - :{" "} - - {formatStatus(order.fulfillment_status)} - - - - :{" "} - - {formatStatus(order.payment_status)} - - - - )}
) } diff --git a/app/home/(user)/_components/order/order-item.tsx b/app/home/(user)/_components/order/order-item.tsx index 4cd4e7a..cad98e6 100644 --- a/app/home/(user)/_components/order/order-item.tsx +++ b/app/home/(user)/_components/order/order-item.tsx @@ -1,7 +1,7 @@ import { StoreCartLineItem, StoreOrderLineItem } from "@medusajs/types" import { TableCell, TableRow } from "@kit/ui/table" -import LineItemOptions from "@modules/common/components/line-item-options" +// import LineItemOptions from "@modules/common/components/line-item-options" import LineItemPrice from "@modules/common/components/line-item-price" import LineItemUnitPrice from "@modules/common/components/line-item-unit-price" @@ -9,6 +9,7 @@ export default function OrderItem({ item, currencyCode }: { item: StoreCartLineItem | StoreOrderLineItem currencyCode: string }) { + const partnerLocationName = item.metadata?.partner_location_name; return ( {/* @@ -22,9 +23,9 @@ export default function OrderItem({ item, currencyCode }: { className="txt-medium-plus text-ui-fg-base" data-testid="product-name" > - {item.product_title}{` (${item.metadata?.partner_location_name ?? "-"})`} + {item.product_title}{` ${partnerLocationName ? `(${partnerLocationName})` : ''}`} - + {/* */} diff --git a/app/home/(user)/_components/order/order-items.tsx b/app/home/(user)/_components/order/order-items.tsx index 25dbe31..5375314 100644 --- a/app/home/(user)/_components/order/order-items.tsx +++ b/app/home/(user)/_components/order/order-items.tsx @@ -7,10 +7,10 @@ import OrderItem from "./order-item" import { Heading } from "@kit/ui/heading" import { Trans } from '@kit/ui/trans'; -export default function OrderItems({ order }: { - order: StoreOrder +export default function OrderItems({ medusaOrder }: { + medusaOrder: StoreOrder }) { - const items = order.items + const items = medusaOrder.items return (
@@ -27,7 +27,7 @@ export default function OrderItems({ order }: { )) : repeat(5).map((i) => )} diff --git a/app/home/(user)/_components/orders/orders-item.tsx b/app/home/(user)/_components/orders/orders-item.tsx index 2d80f7d..ea8943d 100644 --- a/app/home/(user)/_components/orders/orders-item.tsx +++ b/app/home/(user)/_components/orders/orders-item.tsx @@ -6,6 +6,7 @@ import { Eye } from "lucide-react"; import Link from "next/link"; import { formatDate } from "date-fns"; import { IOrderLineItem } from "./types"; +import { Trans } from '@kit/ui/trans'; export default function OrdersItem({ orderItem }: { orderItem: IOrderLineItem, @@ -22,15 +23,13 @@ export default function OrdersItem({ orderItem }: { {formatDate(orderItem.item.created_at, 'dd.MM.yyyy HH:mm')} - {orderItem.orderStatus && ( - - {orderItem.orderStatus} - - )} + + + - +