add fields to tto order view

This commit is contained in:
Danel Kungla
2025-10-01 11:55:47 +03:00
parent b967cecb80
commit 8493d0e9ec
15 changed files with 171 additions and 12 deletions

View File

@@ -4,6 +4,7 @@ import CartTotals from '@/app/home/(user)/_components/order/cart-totals';
import OrderDetails from '@/app/home/(user)/_components/order/order-details'; import OrderDetails from '@/app/home/(user)/_components/order/order-details';
import OrderItems from '@/app/home/(user)/_components/order/order-items'; import OrderItems from '@/app/home/(user)/_components/order/order-items';
import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client';
import { retrieveOrder } from '@lib/data/orders'; import { retrieveOrder } from '@lib/data/orders';
import Divider from '@modules/common/components/divider'; import Divider from '@modules/common/components/divider';
@@ -12,7 +13,6 @@ import { PageBody, PageHeader } from '@kit/ui/page';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import { withI18n } from '~/lib/i18n/with-i18n'; import { withI18n } from '~/lib/i18n/with-i18n';
import { getAnalysisOrder } from '~/lib/services/order.service';
export async function generateMetadata() { export async function generateMetadata() {
const { t } = await createI18nServerInstance(); const { t } = await createI18nServerInstance();
@@ -25,11 +25,42 @@ export async function generateMetadata() {
async function OrderConfirmedPage(props: { async function OrderConfirmedPage(props: {
params: Promise<{ orderId: string }>; params: Promise<{ orderId: string }>;
}) { }) {
const supabaseClient = getSupabaseServerClient();
const params = await props.params; const params = await props.params;
const medusaOrder = await retrieveOrder(params.orderId).catch(() => null); const medusaOrder = await retrieveOrder(params.orderId).catch(() => null);
if (!medusaOrder) { if (!medusaOrder) {
redirect(pathsConfig.app.myOrders); redirect(pathsConfig.app.myOrders);
} }
const ttoReservationId =
medusaOrder.items &&
(medusaOrder.items.find(
({ metadata }) => !!metadata?.connectedOnlineReservationId,
)?.metadata?.connectedOnlineReservationId as number);
const [{ data: ttoReservation }] = await Promise.all([
ttoReservationId
? await supabaseClient
.schema('medreport')
.from('connected_online_reservation')
.select('*')
.eq('id', ttoReservationId)
.single()
: { data: null },
]);
const [{ data: location }, { data: serviceProvider }] = await Promise.all([
supabaseClient
.schema('medreport')
.from('connected_online_locations')
.select('name,address')
.eq('sync_id', ttoReservation.location_sync_id || 0)
.single(),
supabaseClient
.schema('medreport')
.from('connected_online_providers')
.select('email,phone_number,name')
.eq('id', ttoReservation.clinic_id)
.single(),
]);
return ( return (
<PageBody> <PageBody>
@@ -40,6 +71,8 @@ async function OrderConfirmedPage(props: {
order={{ order={{
id: medusaOrder.id, id: medusaOrder.id,
created_at: medusaOrder.created_at, created_at: medusaOrder.created_at,
location,
serviceProvider,
}} }}
/> />
<Divider /> <Divider />

View File

@@ -66,6 +66,15 @@ async function OrdersPage() {
({ medusa_order_id }) => medusa_order_id === medusaOrder.id, ({ medusa_order_id }) => medusa_order_id === medusaOrder.id,
); );
const ttoReservation = ttoOrders.find(({ id }) => {
const connectedOnlineReservationId =
medusaOrder?.items &&
medusaOrder.items.find(
({ metadata }) => !!metadata?.connectedOnlineReservationId,
)?.metadata?.connectedOnlineReservationId;
return id === connectedOnlineReservationId;
});
const medusaOrderItems = medusaOrder.items || []; const medusaOrderItems = medusaOrder.items || [];
const medusaOrderItemsAnalysisPackages = medusaOrderItems.filter( const medusaOrderItemsAnalysisPackages = medusaOrderItems.filter(
(item) => item.product_type_id === analysisPackagesTypeId, (item) => item.product_type_id === analysisPackagesTypeId,
@@ -87,6 +96,7 @@ async function OrdersPage() {
<OrderBlock <OrderBlock
medusaOrderId={medusaOrder.id} medusaOrderId={medusaOrder.id}
analysisOrder={analysisOrder} analysisOrder={analysisOrder}
ttoReservation={ttoReservation}
medusaOrderStatus={medusaOrder.status} medusaOrderStatus={medusaOrder.status}
itemsAnalysisPackage={medusaOrderItemsAnalysisPackages} itemsAnalysisPackage={medusaOrderItemsAnalysisPackages}
itemsTtoService={medusaOrderItemsTtoServices} itemsTtoService={medusaOrderItemsTtoServices}

View File

@@ -1,27 +1,63 @@
import { formatDate } from 'date-fns'; import { formatDate } from 'date-fns';
import { Database } from '@kit/supabase/database';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
export default function OrderDetails({ export default function OrderDetails({
order, order,
}: { }: {
order: { id: string; created_at: string | Date }; order: {
id: string;
created_at: string | Date;
location: Pick<
Database['medreport']['Tables']['connected_online_locations']['Row'],
'name' | 'address'
> | null;
serviceProvider: Pick<
Database['medreport']['Tables']['connected_online_providers']['Row'],
'email' | 'phone_number' | 'name'
> | null;
};
}) { }) {
const { id, created_at, location, serviceProvider } = order;
return ( return (
<div className="flex flex-col gap-y-2"> <div className="flex flex-col gap-y-2">
<div> <div>
<span className="font-bold"> <span className="font-bold">
<Trans i18nKey="cart:orderConfirmed.orderNumber" />:{' '} <Trans i18nKey="cart:orderConfirmed.orderNumber" />:{' '}
</span> </span>
<span className="break-all">{order.id}</span> <span className="break-all">{id}</span>
</div> </div>
<div> <div>
<span className="font-bold"> <span className="font-bold">
<Trans i18nKey="cart:orderConfirmed.orderDate" />:{' '} <Trans i18nKey="cart:orderConfirmed.orderDate" />:{' '}
</span> </span>
<span>{formatDate(order.created_at, 'dd.MM.yyyy HH:mm')}</span> <span>{formatDate(created_at, 'dd.MM.yyyy HH:mm')}</span>
</div> </div>
{(location?.name || location?.address) && (
<div>
<span className="font-bold">
<Trans i18nKey="cart:orderConfirmed.location" />:{' '}
</span>
<span>
{location.name || location.address}{' '}
{location?.name ? location.address : ''}
</span>
</div>
)}
{serviceProvider && (
<div className="flex flex-col">
<span className="font-bold">
<Trans i18nKey="cart:orderConfirmed.serviceProvider" />:{' '}
</span>
<span>{serviceProvider.name}</span>
<span>{serviceProvider.phone_number}</span>
<span>{serviceProvider.email}</span>
</div>
)}
</div> </div>
); );
} }

View File

@@ -5,13 +5,14 @@ import { Eye } from 'lucide-react';
import { Trans } from '@kit/ui/makerkit/trans'; import { Trans } from '@kit/ui/makerkit/trans';
import type { AnalysisOrder } from '~/lib/types/order'; import type { AnalysisOrder, TTOOrder } from '~/lib/types/order';
import OrderItemsTable from './order-items-table'; import OrderItemsTable from './order-items-table';
export default function OrderBlock({ export default function OrderBlock({
analysisOrder, analysisOrder,
ttoLocation, ttoLocation,
ttoReservation,
medusaOrderStatus, medusaOrderStatus,
itemsAnalysisPackage, itemsAnalysisPackage,
itemsTtoService, itemsTtoService,
@@ -20,6 +21,7 @@ export default function OrderBlock({
}: { }: {
analysisOrder?: AnalysisOrder; analysisOrder?: AnalysisOrder;
ttoLocation?: { name: string }; ttoLocation?: { name: string };
ttoReservation?: TTOOrder;
medusaOrderStatus: string; medusaOrderStatus: string;
itemsAnalysisPackage: StoreOrderLineItem[]; itemsAnalysisPackage: StoreOrderLineItem[];
itemsTtoService: StoreOrderLineItem[]; itemsTtoService: StoreOrderLineItem[];
@@ -70,6 +72,8 @@ export default function OrderBlock({
status: medusaOrderStatus.toUpperCase(), status: medusaOrderStatus.toUpperCase(),
medusaOrderId, medusaOrderId,
location: ttoLocation?.name, location: ttoLocation?.name,
bookingCode: ttoReservation?.booking_code,
clinicId: ttoReservation?.clinic_id,
}} }}
/> />
)} )}

View File

@@ -1,7 +1,10 @@
'use client'; 'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import ConfirmationModal from '@/packages/shared/src/components/confirmation-modal';
import { StoreOrderLineItem } from '@medusajs/types'; import { StoreOrderLineItem } from '@medusajs/types';
import { formatDate } from 'date-fns'; import { formatDate } from 'date-fns';
@@ -19,6 +22,7 @@ import { Trans } from '@kit/ui/trans';
import type { Order } from '~/lib/types/order'; import type { Order } from '~/lib/types/order';
import { cancelTtoBooking } from '../../_lib/server/actions';
import { logAnalysisResultsNavigateAction } from './actions'; import { logAnalysisResultsNavigateAction } from './actions';
export type OrderItemType = 'analysisOrder' | 'ttoService'; export type OrderItemType = 'analysisOrder' | 'ttoService';
@@ -35,12 +39,14 @@ export default function OrderItemsTable({
type?: OrderItemType; type?: OrderItemType;
}) { }) {
const router = useRouter(); const router = useRouter();
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
if (!items || items.length === 0) { if (!items || items.length === 0) {
return null; return null;
} }
const isAnalysisOrder = type === 'analysisOrder'; const isAnalysisOrder = type === 'analysisOrder';
const isTtoservice = type === 'ttoService';
const openDetailedView = async () => { const openDetailedView = async () => {
if (isAnalysisOrder && order?.medusaOrderId && order?.id) { if (isAnalysisOrder && order?.medusaOrderId && order?.id) {
@@ -50,7 +56,7 @@ export default function OrderItemsTable({
router.push(`${pathsConfig.app.myOrders}/${order.medusaOrderId}`); router.push(`${pathsConfig.app.myOrders}/${order.medusaOrderId}`);
} }
}; };
console.log('order', order);
return ( return (
<Table className="border-separate rounded-lg border"> <Table className="border-separate rounded-lg border">
<TableHeader className="text-ui-fg-subtle txt-medium-plus"> <TableHeader className="text-ui-fg-subtle txt-medium-plus">
@@ -103,10 +109,30 @@ export default function OrderItemsTable({
<Button size="sm" onClick={openDetailedView}> <Button size="sm" onClick={openDetailedView}>
<Trans i18nKey="analysis-results:view" /> <Trans i18nKey="analysis-results:view" />
</Button> </Button>
{isTtoservice && order.bookingCode && (
<Button
size="sm"
className="bg-warning/90 hover:bg-warning mt-2 w-full"
onClick={() => setIsConfirmOpen(true)}
>
<Trans i18nKey="analysis-results:cancel" />
</Button>
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
{order?.bookingCode && order?.clinicId && (
<ConfirmationModal
isOpen={isConfirmOpen}
onClose={() => setIsConfirmOpen(false)}
onConfirm={async () => {
await cancelTtoBooking(order.bookingCode!, order.clinicId!);
}}
titleKey="orders:confirmBookingCancel.title"
descriptionKey="orders:confirmBookingCancel.description"
/>
)}
</Table> </Table>
); );
} }

View File

@@ -3,8 +3,12 @@
import { updateLineItem } from '@lib/data/cart'; import { updateLineItem } from '@lib/data/cart';
import { StoreProductVariant } from '@medusajs/types'; import { StoreProductVariant } from '@medusajs/types';
import logRequestResult from '~/lib/services/audit.service';
import { handleAddToCart } from '~/lib/services/medusaCart.service'; import { handleAddToCart } from '~/lib/services/medusaCart.service';
import { createInitialReservation } from '~/lib/services/reservation.service'; import { createInitialReservation } from '~/lib/services/reservation.service';
import { RequestStatus } from '~/lib/types/audit';
import { ConnectedOnlineMethodName } from '~/lib/types/connected-online';
import { ExternalApi } from '~/lib/types/external';
export async function createInitialReservationAction( export async function createInitialReservationAction(
selectedVariant: Pick<StoreProductVariant, 'id'>, selectedVariant: Pick<StoreProductVariant, 'id'>,
@@ -41,3 +45,32 @@ export async function createInitialReservationAction(
}); });
} }
} }
export async function cancelTtoBooking(bookingCode: string, clinicId: number) {
try {
const response = await fetch(
`${process.env.CONNECTED_ONLINE_URL}/${ConnectedOnlineMethodName.ConfirmedCancel}`,
{
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify({
param: `{'Value':'${bookingCode}|${clinicId}|et'}`,
}),
},
);
return null;
} catch (error) {
await logRequestResult(
ExternalApi.ConnectedOnline,
ConnectedOnlineMethodName.ConfirmedCancel,
RequestStatus.Fail,
JSON.stringify(error),
);
if (error instanceof Error) {
console.error('Error cancelling booking: ' + error.message);
}
console.error('Error cancelling booking: ', error);
}
}

View File

@@ -6,7 +6,6 @@ export const isValidOpenAiEnv = async () => {
await client.models.list(); await client.models.list();
return true; return true;
} catch (e) { } catch (e) {
console.log('AI not enabled');
return false; return false;
} }
}; };

View File

@@ -10,4 +10,6 @@ export type Order = {
id?: number; id?: number;
status?: string; status?: string;
location?: string; location?: string;
bookingCode?: string | null;
clinicId?: number;
}; };

View File

@@ -1,5 +1,3 @@
import { sdk } from '@lib/config';
import { import {
renderAllResultsReceivedEmail, renderAllResultsReceivedEmail,
renderFirstResultsReceivedEmail, renderFirstResultsReceivedEmail,

View File

@@ -80,7 +80,9 @@
"orderStatus": "Order status", "orderStatus": "Order status",
"paymentStatus": "Payment status", "paymentStatus": "Payment status",
"discount": "Discount", "discount": "Discount",
"paymentConfirmationLoading": "Payment confirmation..." "paymentConfirmationLoading": "Payment confirmation...",
"location": "Location",
"serviceProvider": "Service provider"
}, },
"montonioCallback": { "montonioCallback": {
"title": "Montonio checkout", "title": "Montonio checkout",

View File

@@ -31,5 +31,9 @@
"REJECTED": "Rejected", "REJECTED": "Rejected",
"CANCELLED": "Cancelled" "CANCELLED": "Cancelled"
} }
},
"confirmBookingCancel": {
"title": "Are you sure?",
"description": "Are you sure you wish to cancel this booking?"
} }
} }

View File

@@ -80,7 +80,9 @@
"orderStatus": "Tellimuse olek", "orderStatus": "Tellimuse olek",
"paymentStatus": "Makse olek", "paymentStatus": "Makse olek",
"discount": "Soodus", "discount": "Soodus",
"paymentConfirmationLoading": "Makse kinnitamine..." "paymentConfirmationLoading": "Makse kinnitamine...",
"location": "Asukoht",
"serviceProvider": "Teenuse pakkuja"
}, },
"montonioCallback": { "montonioCallback": {
"title": "Montonio makseprotsess", "title": "Montonio makseprotsess",

View File

@@ -33,5 +33,9 @@
"REJECTED": "Tagasi lükatud", "REJECTED": "Tagasi lükatud",
"CANCELLED": "Tühistatud" "CANCELLED": "Tühistatud"
} }
},
"confirmBookingCancel": {
"title": "Oled kindel?",
"description": "Oled kindel, et soovitd broneeringu tühistada?"
} }
} }

View File

@@ -80,7 +80,9 @@
"orderStatus": "Статус заказа", "orderStatus": "Статус заказа",
"paymentStatus": "Статус оплаты", "paymentStatus": "Статус оплаты",
"discount": "Скидка", "discount": "Скидка",
"paymentConfirmationLoading": "Ожидание подтверждения оплаты..." "paymentConfirmationLoading": "Ожидание подтверждения оплаты...",
"location": "Location",
"serviceProvider": "Service provider"
}, },
"montonioCallback": { "montonioCallback": {
"title": "Процесс оплаты Montonio", "title": "Процесс оплаты Montonio",

View File

@@ -31,5 +31,9 @@
"REJECTED": "Отклонено", "REJECTED": "Отклонено",
"CANCELLED": "Отменено" "CANCELLED": "Отменено"
} }
},
"confirmBookingCancel": {
"title": "Are you sure?",
"description": "Are you sure you wish to cancel this booking?"
} }
} }