diff --git a/app/home/(user)/(dashboard)/order/[orderId]/page.tsx b/app/home/(user)/(dashboard)/order/[orderId]/page.tsx index f28b5ae..4f5c49d 100644 --- a/app/home/(user)/(dashboard)/order/[orderId]/page.tsx +++ b/app/home/(user)/(dashboard)/order/[orderId]/page.tsx @@ -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 OrderItems from '@/app/home/(user)/_components/order/order-items'; import { createI18nServerInstance } from '@/lib/i18n/i18n.server'; +import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client'; import { retrieveOrder } from '@lib/data/orders'; 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 { withI18n } from '~/lib/i18n/with-i18n'; -import { getAnalysisOrder } from '~/lib/services/order.service'; export async function generateMetadata() { const { t } = await createI18nServerInstance(); @@ -25,11 +25,42 @@ export async function generateMetadata() { async function OrderConfirmedPage(props: { params: Promise<{ orderId: string }>; }) { + const supabaseClient = getSupabaseServerClient(); const params = await props.params; const medusaOrder = await retrieveOrder(params.orderId).catch(() => null); if (!medusaOrder) { 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 ( @@ -40,6 +71,8 @@ async function OrderConfirmedPage(props: { order={{ id: medusaOrder.id, created_at: medusaOrder.created_at, + location, + serviceProvider, }} /> diff --git a/app/home/(user)/(dashboard)/order/page.tsx b/app/home/(user)/(dashboard)/order/page.tsx index beb0634..4365b73 100644 --- a/app/home/(user)/(dashboard)/order/page.tsx +++ b/app/home/(user)/(dashboard)/order/page.tsx @@ -58,13 +58,22 @@ async function OrdersPage() { /> {medusaOrders.map((medusaOrder) => { + if (!medusaOrder) { + return null; + } + const analysisOrder = analysisOrders.find( ({ medusa_order_id }) => medusa_order_id === medusaOrder.id, ); - if (!medusaOrder) { - return null; - } + 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 medusaOrderItemsAnalysisPackages = medusaOrderItems.filter( @@ -87,6 +96,7 @@ async function OrdersPage() { | null; + serviceProvider: Pick< + Database['medreport']['Tables']['connected_online_providers']['Row'], + 'email' | 'phone_number' | 'name' + > | null; + }; }) { + const { id, created_at, location, serviceProvider } = order; + return (
:{' '} - {order.id} + {id}
:{' '} - {formatDate(order.created_at, 'dd.MM.yyyy HH:mm')} + {formatDate(created_at, 'dd.MM.yyyy HH:mm')}
+ {(location?.name || location?.address) && ( +
+ + :{' '} + + + {location.name || location.address}{' '} + {location?.name ? location.address : ''} + +
+ )} + + {serviceProvider && ( +
+ + :{' '} + + {serviceProvider.name} + {serviceProvider.phone_number} + {serviceProvider.email} +
+ )}
); } diff --git a/app/home/(user)/_components/orders/order-block.tsx b/app/home/(user)/_components/orders/order-block.tsx index 08daf05..b43b3a2 100644 --- a/app/home/(user)/_components/orders/order-block.tsx +++ b/app/home/(user)/_components/orders/order-block.tsx @@ -5,12 +5,14 @@ import { Eye } from 'lucide-react'; 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'; export default function OrderBlock({ analysisOrder, + ttoLocation, + ttoReservation, medusaOrderStatus, itemsAnalysisPackage, itemsTtoService, @@ -18,6 +20,8 @@ export default function OrderBlock({ medusaOrderId, }: { analysisOrder?: AnalysisOrder; + ttoLocation?: { name: string }; + ttoReservation?: TTOOrder; medusaOrderStatus: string; itemsAnalysisPackage: StoreOrderLineItem[]; itemsTtoService: StoreOrderLineItem[]; @@ -67,6 +71,9 @@ export default function OrderBlock({ order={{ status: medusaOrderStatus.toUpperCase(), medusaOrderId, + location: ttoLocation?.name, + bookingCode: ttoReservation?.booking_code, + clinicId: ttoReservation?.clinic_id, }} /> )} diff --git a/app/home/(user)/_components/orders/order-items-table.tsx b/app/home/(user)/_components/orders/order-items-table.tsx index 09d50a9..18ba3a1 100644 --- a/app/home/(user)/_components/orders/order-items-table.tsx +++ b/app/home/(user)/_components/orders/order-items-table.tsx @@ -1,7 +1,10 @@ 'use client'; +import { useState } from 'react'; + import { useRouter } from 'next/navigation'; +import ConfirmationModal from '@/packages/shared/src/components/confirmation-modal'; import { StoreOrderLineItem } from '@medusajs/types'; import { formatDate } from 'date-fns'; @@ -19,6 +22,7 @@ import { Trans } from '@kit/ui/trans'; import type { Order } from '~/lib/types/order'; +import { cancelTtoBooking } from '../../_lib/server/actions'; import { logAnalysisResultsNavigateAction } from './actions'; export type OrderItemType = 'analysisOrder' | 'ttoService'; @@ -35,12 +39,14 @@ export default function OrderItemsTable({ type?: OrderItemType; }) { const router = useRouter(); + const [isConfirmOpen, setIsConfirmOpen] = useState(false); if (!items || items.length === 0) { return null; } const isAnalysisOrder = type === 'analysisOrder'; + const isTtoservice = type === 'ttoService'; const openDetailedView = async () => { if (isAnalysisOrder && order?.medusaOrderId && order?.id) { @@ -61,6 +67,11 @@ export default function OrderItemsTable({ + {order.location && ( + + + + )} @@ -83,7 +94,11 @@ export default function OrderItemsTable({ {formatDate(orderItem.created_at, 'dd.MM.yyyy HH:mm')} - + {order.location && ( + + {order.location} + + )} + {isTtoservice && order.bookingCode && ( + + )} ))} + {order?.bookingCode && order?.clinicId && ( + setIsConfirmOpen(false)} + onConfirm={async () => { + await cancelTtoBooking(order.bookingCode!, order.clinicId!); + }} + titleKey="orders:confirmBookingCancel.title" + descriptionKey="orders:confirmBookingCancel.description" + /> + )} ); } diff --git a/app/home/(user)/_lib/server/actions.ts b/app/home/(user)/_lib/server/actions.ts index f3998ca..aae04d5 100644 --- a/app/home/(user)/_lib/server/actions.ts +++ b/app/home/(user)/_lib/server/actions.ts @@ -3,8 +3,12 @@ import { updateLineItem } from '@lib/data/cart'; import { StoreProductVariant } from '@medusajs/types'; +import logRequestResult from '~/lib/services/audit.service'; import { handleAddToCart } from '~/lib/services/medusaCart.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( selectedVariant: Pick, @@ -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); + } +} diff --git a/app/home/(user)/_lib/server/is-valid-open-ai-env.ts b/app/home/(user)/_lib/server/is-valid-open-ai-env.ts index 12c8ba9..6384a1e 100644 --- a/app/home/(user)/_lib/server/is-valid-open-ai-env.ts +++ b/app/home/(user)/_lib/server/is-valid-open-ai-env.ts @@ -6,7 +6,6 @@ export const isValidOpenAiEnv = async () => { await client.models.list(); return true; } catch (e) { - console.log('AI not enabled'); return false; } }; diff --git a/lib/services/order.service.ts b/lib/services/order.service.ts index 086f4cd..66ca175 100644 --- a/lib/services/order.service.ts +++ b/lib/services/order.service.ts @@ -163,5 +163,42 @@ export async function getTtoOrders({ const orders = await query .order('created_at', { ascending: false }) .throwOnError(); - return orders.data; + + const ordersWithLocation = await Promise.all( + orders.data.map(async (order) => ({ + ...order, + location: await getTtoLocation(order.location_sync_id), + })), + ); + + return ordersWithLocation; +} + +export async function getTtoLocation(syncId?: number | null) { + if (!syncId) { + return null; + } + + const client = getSupabaseServerClient(); + + const { + data: { user }, + } = await client.auth.getUser(); + + if (!user) { + throw new Error('Unauthorized'); + } + + const { data, error } = await client + .schema('medreport') + .from('connected_online_locations') + .select('name') + .eq('sync_id', syncId) + .single(); + + if (error) { + throw new Error('Could not receive online locations: ', error); + } + + return data; } diff --git a/lib/types/order.ts b/lib/types/order.ts index 92ce3f7..9c6b891 100644 --- a/lib/types/order.ts +++ b/lib/types/order.ts @@ -9,4 +9,7 @@ export type Order = { medusaOrderId?: string; id?: number; status?: string; + location?: string; + bookingCode?: string | null; + clinicId?: number; }; diff --git a/packages/features/notifications/src/server/services/webhooks/order-notifications.service.ts b/packages/features/notifications/src/server/services/webhooks/order-notifications.service.ts index 3adc4bb..393eb9c 100644 --- a/packages/features/notifications/src/server/services/webhooks/order-notifications.service.ts +++ b/packages/features/notifications/src/server/services/webhooks/order-notifications.service.ts @@ -1,5 +1,3 @@ -import { sdk } from '@lib/config'; - import { renderAllResultsReceivedEmail, renderFirstResultsReceivedEmail, diff --git a/public/locales/en/cart.json b/public/locales/en/cart.json index a23077e..3603003 100644 --- a/public/locales/en/cart.json +++ b/public/locales/en/cart.json @@ -80,7 +80,9 @@ "orderStatus": "Order status", "paymentStatus": "Payment status", "discount": "Discount", - "paymentConfirmationLoading": "Payment confirmation..." + "paymentConfirmationLoading": "Payment confirmation...", + "location": "Location", + "serviceProvider": "Service provider" }, "montonioCallback": { "title": "Montonio checkout", diff --git a/public/locales/en/orders.json b/public/locales/en/orders.json index ea5f029..3b9b0ec 100644 --- a/public/locales/en/orders.json +++ b/public/locales/en/orders.json @@ -31,5 +31,9 @@ "REJECTED": "Rejected", "CANCELLED": "Cancelled" } + }, + "confirmBookingCancel": { + "title": "Are you sure?", + "description": "Are you sure you wish to cancel this booking?" } } diff --git a/public/locales/et/cart.json b/public/locales/et/cart.json index 5790c31..ab43940 100644 --- a/public/locales/et/cart.json +++ b/public/locales/et/cart.json @@ -80,7 +80,9 @@ "orderStatus": "Tellimuse olek", "paymentStatus": "Makse olek", "discount": "Soodus", - "paymentConfirmationLoading": "Makse kinnitamine..." + "paymentConfirmationLoading": "Makse kinnitamine...", + "location": "Asukoht", + "serviceProvider": "Teenuse pakkuja" }, "montonioCallback": { "title": "Montonio makseprotsess", diff --git a/public/locales/et/orders.json b/public/locales/et/orders.json index a57ff1c..834983b 100644 --- a/public/locales/et/orders.json +++ b/public/locales/et/orders.json @@ -7,7 +7,8 @@ "ttoService": "Broneering", "otherOrders": "Tellimus", "createdAt": "Tellitud", - "status": "Olek" + "status": "Olek", + "location": "Asukoht" }, "status": { "QUEUED": "Esitatud", @@ -32,5 +33,9 @@ "REJECTED": "Tagasi lükatud", "CANCELLED": "Tühistatud" } + }, + "confirmBookingCancel": { + "title": "Oled kindel?", + "description": "Oled kindel, et soovitd broneeringu tühistada?" } } diff --git a/public/locales/ru/cart.json b/public/locales/ru/cart.json index e900175..77c2b1b 100644 --- a/public/locales/ru/cart.json +++ b/public/locales/ru/cart.json @@ -80,7 +80,9 @@ "orderStatus": "Статус заказа", "paymentStatus": "Статус оплаты", "discount": "Скидка", - "paymentConfirmationLoading": "Ожидание подтверждения оплаты..." + "paymentConfirmationLoading": "Ожидание подтверждения оплаты...", + "location": "Location", + "serviceProvider": "Service provider" }, "montonioCallback": { "title": "Процесс оплаты Montonio", diff --git a/public/locales/ru/orders.json b/public/locales/ru/orders.json index d265f87..32ff4f6 100644 --- a/public/locales/ru/orders.json +++ b/public/locales/ru/orders.json @@ -31,5 +31,9 @@ "REJECTED": "Отклонено", "CANCELLED": "Отменено" } + }, + "confirmBookingCancel": { + "title": "Are you sure?", + "description": "Are you sure you wish to cancel this booking?" } } diff --git a/supabase/migrations/20250930175100_update_tto_tables.sql b/supabase/migrations/20250930175100_update_tto_tables.sql new file mode 100644 index 0000000..6b86c0e --- /dev/null +++ b/supabase/migrations/20250930175100_update_tto_tables.sql @@ -0,0 +1,4 @@ +ALTER TABLE medreport.connected_online_reservation +ADD CONSTRAINT fk_reservation_location_sync_id +FOREIGN KEY (location_sync_id) +REFERENCES medreport.connected_online_locations(sync_id); \ No newline at end of file