diff --git a/backend/app/Http/Actions/Orders/Public/CompleteOrderActionPublic.php b/backend/app/Http/Actions/Orders/Public/CompleteOrderActionPublic.php index 68c46439c1..a1be5515d9 100644 --- a/backend/app/Http/Actions/Orders/Public/CompleteOrderActionPublic.php +++ b/backend/app/Http/Actions/Orders/Public/CompleteOrderActionPublic.php @@ -34,6 +34,7 @@ public function __invoke(CompleteOrderRequest $request, int $eventId, string $or : null, ]), 'products' => $request->input('products'), + 'event_id' => $eventId, ])); } catch (ResourceConflictException $e) { return $this->errorResponse($e->getMessage(), Response::HTTP_CONFLICT); diff --git a/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php b/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php index b588cffb1d..468ba5df90 100644 --- a/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php +++ b/backend/app/Services/Application/Handlers/Order/CompleteOrderHandler.php @@ -8,6 +8,7 @@ use Exception; use HiEvents\DomainObjects\AttendeeDomainObject; use HiEvents\DomainObjects\Enums\ProductType; +use HiEvents\DomainObjects\EventSettingDomainObject; use HiEvents\DomainObjects\Generated\AttendeeDomainObjectAbstract; use HiEvents\DomainObjects\Generated\OrderDomainObjectAbstract; use HiEvents\DomainObjects\Generated\ProductPriceDomainObjectAbstract; @@ -24,6 +25,7 @@ use HiEvents\Repository\Eloquent\Value\Relationship; use HiEvents\Repository\Interfaces\AffiliateRepositoryInterface; use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; +use HiEvents\Repository\Interfaces\EventSettingsRepositoryInterface; use HiEvents\Repository\Interfaces\OrderRepositoryInterface; use HiEvents\Repository\Interfaces\ProductPriceRepositoryInterface; use HiEvents\Repository\Interfaces\QuestionAnswerRepositoryInterface; @@ -55,6 +57,7 @@ public function __construct( private readonly ProductQuantityUpdateService $productQuantityUpdateService, private readonly ProductPriceRepositoryInterface $productPriceRepository, private readonly DomainEventDispatcherService $domainEventDispatcherService, + private readonly EventSettingsRepositoryInterface $eventSettingsRepository, ) { } @@ -90,7 +93,16 @@ public function handle(string $orderShortId, CompleteOrderDTO $orderData): Order return $updatedOrder; }); - OrderStatusChangedEvent::dispatch($updatedOrder); + /** @var EventSettingDomainObject $eventSettings */ + $eventSettings = $this->eventSettingsRepository->findFirstWhere([ + 'event_id' => $orderData->event_id, + ]); + + event(new OrderStatusChangedEvent( + order: $updatedOrder, + sendEmails: true, + createInvoice: $eventSettings->getEnableInvoicing(), + )); if ($updatedOrder->isOrderCompleted()) { $this->domainEventDispatcherService->dispatch( diff --git a/backend/app/Services/Application/Handlers/Order/DTO/CompleteOrderDTO.php b/backend/app/Services/Application/Handlers/Order/DTO/CompleteOrderDTO.php index 44247d2455..0532294c2a 100644 --- a/backend/app/Services/Application/Handlers/Order/DTO/CompleteOrderDTO.php +++ b/backend/app/Services/Application/Handlers/Order/DTO/CompleteOrderDTO.php @@ -11,11 +11,13 @@ class CompleteOrderDTO extends BaseDTO /** * @param CompleteOrderOrderDTO $order * @param Collection $products + * @param int $event_id */ public function __construct( public CompleteOrderOrderDTO $order, #[CollectionOf(CompleteOrderProductDataDTO::class)] - public Collection $products + public Collection $products, + public int $event_id, ) { } diff --git a/backend/tests/Unit/Services/Application/Handlers/Order/CompleteOrderHandlerTest.php b/backend/tests/Unit/Services/Application/Handlers/Order/CompleteOrderHandlerTest.php index 3f0a56d5c1..b331f4c747 100644 --- a/backend/tests/Unit/Services/Application/Handlers/Order/CompleteOrderHandlerTest.php +++ b/backend/tests/Unit/Services/Application/Handlers/Order/CompleteOrderHandlerTest.php @@ -5,6 +5,7 @@ use Carbon\Carbon; use Exception; use HiEvents\DomainObjects\AttendeeDomainObject; +use HiEvents\DomainObjects\EventSettingDomainObject; use HiEvents\DomainObjects\OrderDomainObject; use HiEvents\DomainObjects\OrderItemDomainObject; use HiEvents\DomainObjects\ProductPriceDomainObject; @@ -12,6 +13,7 @@ use HiEvents\Exceptions\ResourceConflictException; use HiEvents\Repository\Interfaces\AffiliateRepositoryInterface; use HiEvents\Repository\Interfaces\AttendeeRepositoryInterface; +use HiEvents\Repository\Interfaces\EventSettingsRepositoryInterface; use HiEvents\Repository\Interfaces\OrderRepositoryInterface; use HiEvents\Repository\Interfaces\ProductPriceRepositoryInterface; use HiEvents\Repository\Interfaces\QuestionAnswerRepositoryInterface; @@ -44,6 +46,7 @@ class CompleteOrderHandlerTest extends TestCase private CompleteOrderHandler $completeOrderHandler; private DomainEventDispatcherService $domainEventDispatcherService; private AffiliateRepositoryInterface|MockInterface $affiliateRepository; + private EventSettingsRepositoryInterface $eventSettingsRepository; protected function setUp(): void { @@ -60,6 +63,7 @@ protected function setUp(): void $this->productPriceRepository = Mockery::mock(ProductPriceRepositoryInterface::class); $this->domainEventDispatcherService = Mockery::mock(DomainEventDispatcherService::class); $this->affiliateRepository = Mockery::mock(AffiliateRepositoryInterface::class); + $this->eventSettingsRepository = Mockery::mock(EventSettingsRepositoryInterface::class); $this->completeOrderHandler = new CompleteOrderHandler( $this->orderRepository, @@ -68,7 +72,8 @@ protected function setUp(): void $this->questionAnswersRepository, $this->productQuantityUpdateService, $this->productPriceRepository, - $this->domainEventDispatcherService + $this->domainEventDispatcherService, + $this->eventSettingsRepository, ); } @@ -97,6 +102,8 @@ public function testHandleSuccessfullyCompletesOrder(): void $this->productQuantityUpdateService->shouldReceive('updateQuantitiesFromOrder'); + $this->eventSettingsRepository->shouldReceive('findFirstWhere')->andReturn($this->createMockEventSetting()); + $this->completeOrderHandler->handle($orderShortId, $orderData); $this->assertTrue(true); @@ -169,6 +176,8 @@ public function testHandleUpdatesProductQuantitiesForFreeOrder(): void $this->productQuantityUpdateService->shouldReceive('updateQuantitiesFromOrder')->once(); + $this->eventSettingsRepository->shouldReceive('findFirstWhere')->andReturn($this->createMockEventSetting()); + $this->domainEventDispatcherService->shouldReceive('dispatch') ->withArgs(function (OrderEvent $event) use ($order) { return $event->type === DomainEventType::ORDER_CREATED @@ -201,6 +210,8 @@ public function testHandleDoesNotUpdateProductQuantitiesForPaidOrder(): void $this->productQuantityUpdateService->shouldNotReceive('updateQuantitiesFromOrder'); + $this->eventSettingsRepository->shouldReceive('findFirstWhere')->andReturn($this->createMockEventSetting()); + $this->completeOrderHandler->handle($orderShortId, $orderData); $this->expectNotToPerformAssertions(); @@ -269,6 +280,7 @@ private function createMockCompleteOrderDTO(): CompleteOrderDTO return new CompleteOrderDTO( order: $orderDTO, products: new Collection([$attendeeDTO]) + ,event_id: 1 ); } @@ -313,4 +325,11 @@ private function createMockAttendee(): AttendeeDomainObject|MockInterface $attendee->shouldReceive('getProductId')->andReturn(1); return $attendee; } + + private function createMockEventSetting(): EventSettingDomainObject + { + return (new EventSettingDomainObject()) + ->setId(1) + ->setEventId(1); + } } diff --git a/frontend/src/components/common/OrdersTable/OrdersTable.module.scss b/frontend/src/components/common/OrdersTable/OrdersTable.module.scss index e4dc7e9da8..086a360411 100644 --- a/frontend/src/components/common/OrdersTable/OrdersTable.module.scss +++ b/frontend/src/components/common/OrdersTable/OrdersTable.module.scss @@ -1,10 +1,228 @@ @use "../../../styles/mixins.scss"; +// Customer Section (Sticky Left) +.customerDetails { + display: flex; + flex-direction: column; + gap: 6px; + min-width: 0; + flex: 1; +} + +.nameRow { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.customerName { + font-size: 15px; + font-weight: 600; + line-height: 1.3; + text-decoration: none; + color: var(--mantine-color-text); + + &:hover { + text-decoration: underline; + } +} + +.companyName { + font-size: 12px; + color: var(--mantine-color-dimmed); + line-height: 1.3; +} + +.customerEmail { + font-size: 13px; + line-height: 1.3; + color: var(--mantine-color-dimmed); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 280px; +} + +// Order Details Section +.orderDetails { + display: flex; + flex-direction: column; + gap: 6px; +} + +.orderId { + font-size: 14px; + font-weight: 500; + color: var(--mantine-color-text); + font-family: 'Courier New', monospace; + line-height: 1.3; + text-decoration: none; + + &:hover { + text-decoration: underline; + } +} + +.orderMeta { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 12px; +} + +.createdDate { + font-size: 12px; + color: var(--mantine-color-dimmed); + line-height: 1.3; +} + +.invoiceLink { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--mantine-color-primary-filled); + text-decoration: none; + font-weight: 500; + line-height: 1.3; + + &:hover { + text-decoration: underline; + } +} + +.noInvoice { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--mantine-color-dimmed); + line-height: 1.3; +} + +.reservedUntil { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--mantine-color-yellow-9); + line-height: 1.3; +} + +// Items Section +.itemsBadge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 8px; + background: var(--mantine-color-gray-1); + border-radius: 4px; + font-size: 12px; + color: var(--mantine-color-gray-7); + font-weight: 500; +} + +// Amount Section +.amountDetails { + display: flex; + flex-direction: column; + gap: 4px; +} + +.amountGross { + font-size: 16px; + font-weight: 600; + color: var(--mantine-color-text); + line-height: 1.3; +} + +.amountBreakdown { + font-size: 11px; + color: var(--mantine-color-dimmed); + line-height: 1.3; +} + +.refundedAmount { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 11px; + color: var(--mantine-color-red-9); + line-height: 1.3; +} + +// Payment Section .paymentStatus { - text-transform: capitalize; - color: #777777; + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 13px; + color: var(--mantine-color-dimmed); +} + +// Status Section +.statusBadge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 6px 12px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + white-space: nowrap; + + &[data-status="COMPLETED"] { + background: var(--mantine-color-green-1); + color: var(--mantine-color-green-9); + } + + &[data-status="RESERVED"] { + background: var(--mantine-color-blue-1); + color: var(--mantine-color-blue-9); + } + + &[data-status="AWAITING_OFFLINE_PAYMENT"] { + background: var(--mantine-color-yellow-1); + color: var(--mantine-color-yellow-9); + } + + &[data-status="CANCELLED"] { + background: var(--mantine-color-red-1); + color: var(--mantine-color-red-9); + } + + &[data-status="ABANDONED"] { + background: var(--mantine-color-gray-1); + color: var(--mantine-color-gray-9); + } +} + +// Actions Section +.actionsMenu { + display: flex; + align-items: center; + justify-content: flex-end; +} + +// Action menu helpers (for mobile/desktop) +.action { + .desktopAction { + display: block; + @include mixins.respond-below(md) { + display: none; + } + } + + .mobileAction { + display: none; + @include mixins.respond-below(md) { + display: block; + } + } } +// Mobile Card Layout (preserve existing mobile view) .orderCard { display: flex; flex-direction: row; @@ -53,6 +271,13 @@ .amount { margin-bottom: 10px; + font-weight: 600; + font-size: 1.2em; + } + + .name { + font-weight: 600; + margin-bottom: 5px; } } @@ -70,20 +295,6 @@ .actionButton { display: flex; - .desktopAction { - display: none; - @include mixins.respond-below(md) { - display: none; - } - } - - .mobileAction { - display: none; - @include mixins.respond-below(md) { - display: block; - } - } - @include mixins.respond-above(md) { place-content: flex-end; } diff --git a/frontend/src/components/common/OrdersTable/index.tsx b/frontend/src/components/common/OrdersTable/index.tsx index 837994060c..640b6ae201 100644 --- a/frontend/src/components/common/OrdersTable/index.tsx +++ b/frontend/src/components/common/OrdersTable/index.tsx @@ -1,34 +1,39 @@ import {t} from "@lingui/macro"; -import {Anchor, Badge, Button, Group, Menu, Table as MantineTable, Tooltip} from '@mantine/core'; +import {Anchor, Button, Group, Menu, Popover, Text} from '@mantine/core'; import {Event, IdParam, Invoice, MessageType, Order} from "../../../types.ts"; import { + IconAlertCircle, IconBasketCog, + IconCash, IconCheck, + IconClock, + IconClockPause, + IconCopy, + IconCreditCard, + IconCurrencyDollar, IconDotsVertical, - IconInfoCircle, + IconFileInvoice, + IconFileOff, + IconHelp, IconReceipt2, IconReceiptDollar, IconReceiptRefund, IconRepeat, IconSend, - IconTrash + IconTicket, + IconTrash, + IconX } from "@tabler/icons-react"; -import {prettyDate, relativeDate} from "../../../utilites/dates.ts"; +import {relativeDate} from "../../../utilites/dates.ts"; import {ManageOrderModal} from "../../modals/ManageOrderModal"; -import {useDisclosure} from "@mantine/hooks"; -import {useState} from "react"; +import {useClipboard, useDisclosure} from "@mantine/hooks"; +import {useMemo, useState} from "react"; import {CancelOrderModal} from "../../modals/CancelOrderModal"; import {SendMessageModal} from "../../modals/SendMessageModal"; -import {notifications} from "@mantine/notifications"; import {NoResultsSplash} from "../NoResultsSplash"; -import {OrderAmountPopover} from "../OrderAmountPopover"; import {RefundOrderModal} from "../../modals/RefundOrderModal"; import classes from "./OrdersTable.module.scss"; -import {Card} from "../Card"; -import {Table, TableHead} from "../Table"; -import {ShowForDesktop, ShowForMobile} from "../Responsive/ShowHideComponents.tsx"; import {useResendOrderConfirmation} from "../../../mutations/useResendOrderConfirmation.ts"; -import {OrderStatusBadge} from "../OrderStatusBadge"; import {formatNumber} from "../../../utilites/helpers.ts"; import {useUrlHash} from "../../../hooks/useUrlHash.ts"; import {useMarkOrderAsPaid} from "../../../mutations/useMarkOrderAsPaid.ts"; @@ -36,6 +41,10 @@ import {orderClient} from "../../../api/order.client.ts"; import {downloadBinary} from "../../../utilites/download.ts"; import {withLoadingNotification} from "../../../utilites/withLoadingNotification.tsx"; import {showError, showSuccess} from "../../../utilites/notifications.tsx"; +import {TanStackTable, TanStackTableColumn} from "../TanStackTable"; +import {ColumnVisibilityToggle} from "../ColumnVisibilityToggle"; +import {CellContext} from "@tanstack/react-table"; +import {formatCurrency} from "../../../utilites/currency.ts"; interface OrdersTableProps { event: Event, @@ -48,8 +57,10 @@ export const OrdersTable = ({orders, event}: OrdersTableProps) => { const [isMessageModalOpen, messageModal] = useDisclosure(false); const [isRefundModalOpen, refundModal] = useDisclosure(false); const [orderId, setOrderId] = useState(); + const [emailPopoverId, setEmailPopoverId] = useState(null); const resendConfirmationMutation = useResendOrderConfirmation(); const markAsPaidMutation = useMarkOrderAsPaid(); + const clipboard = useClipboard({timeout: 2000}); useUrlHash(/^#order-(\d+)$/, (matches => { const orderId = matches![1]; @@ -83,20 +94,8 @@ export const OrdersTable = ({orders, event}: OrdersTableProps) => { const handleResendConfirmation = (eventId: IdParam, orderId: IdParam) => { resendConfirmationMutation.mutate({eventId, orderId}, { - onSuccess: () => { - notifications.show({ - message: t`Your message has been sent`, - icon: , - position: 'top-center', - }) - }, - onError: () => { - notifications.show({ - message: t`There was an error sending your message`, - icon: , - position: 'top-center', - }) - } + onSuccess: () => showSuccess(t`Your message has been sent`), + onError: () => showError(t`There was an error sending your message`) }); } @@ -123,184 +122,346 @@ export const OrdersTable = ({orders, event}: OrdersTableProps) => { ); }; + const handleCopyEmail = (email: string) => { + clipboard.copy(email); + showSuccess(t`Email address copied to clipboard`); + setEmailPopoverId(null); + }; + + const handleMessageFromEmail = (order: Order) => { + setEmailPopoverId(null); + handleModalClick(order.id, messageModal); + }; + + const formatTime = (dateString: string): string => { + const date = new Date(dateString); + return date.toLocaleTimeString('en-US', { + hour: 'numeric', + minute: '2-digit', + hour12: true, + }); + }; + const ActionMenu = ({order}: { order: Order }) => { const isRefundable = !order.is_free_order && order.status !== 'AWAITING_OFFLINE_PAYMENT' && order.payment_provider === 'STRIPE' && order.refund_status !== 'REFUNDED'; - return - - -
-
- - - -
-
- - - + return ( + + + +
+
-
- + - - {t`Manage`} - handleModalClick(order.id, viewModal)} - leftSection={}>{t`Manage order`} - handleModalClick(order.id, messageModal)} - leftSection={}>{t`Message buyer`} + + {t`Manage`} + handleModalClick(order.id, viewModal)} + leftSection={}>{t`Manage order`} + handleModalClick(order.id, messageModal)} + leftSection={}>{t`Message buyer`} - {order.latest_invoice && ( - handleInvoiceDownload(order.latest_invoice as Invoice)} - leftSection={}>{t`Download invoice`} - )} + {order.latest_invoice && ( + handleInvoiceDownload(order.latest_invoice as Invoice)} + leftSection={}>{t`Download invoice`} + )} - {order.status === 'AWAITING_OFFLINE_PAYMENT' && ( - handleMarkAsPaid(event.id, order.id)} - leftSection={}>{t`Mark as paid`} - )} + {order.status === 'AWAITING_OFFLINE_PAYMENT' && ( + handleMarkAsPaid(event.id, order.id)} + leftSection={}>{t`Mark as paid`} + )} - {isRefundable && ( - handleModalClick(order.id, refundModal)} - leftSection={}>{t`Refund order`} - )} + {isRefundable && ( + handleModalClick(order.id, refundModal)} + leftSection={}>{t`Refund order`} + )} - {order.status === 'COMPLETED' && ( - handleResendConfirmation(event.id, order.id)} - leftSection={}> - {t`Resend order email`} - - )} - - {order.status !== 'CANCELLED' && ( - <> - - {t`Danger zone`} - handleModalClick(order.id, cancelModal)} - leftSection={}> - {t`Cancel order`} + {order.status === 'COMPLETED' && ( + handleResendConfirmation(event.id, order.id)} + leftSection={}> + {t`Resend order email`} - - )} - -
-
; + )} + + {order.status !== 'CANCELLED' && ( + <> + + {t`Danger zone`} + handleModalClick(order.id, cancelModal)} + leftSection={}> + {t`Cancel order`} + + + )} + + + + ); } - const OrderTableDesktop = () => ( - - - - - {t`Reference`} - {t`Customer`} - {t`Attendees`} - {t`Amount`} - {t`Created`} - {t`Status`} - - - - - {orders.map((order) => { - return ( - - - handleModalClick(order.id, viewModal)}> - {order.public_id} + const columns = useMemo[]>( + () => [ + { + id: 'customer', + header: t`Customer`, + enableHiding: false, + cell: (info: CellContext) => { + const order = info.row.original; + return ( +
+
+ handleModalClick(order.id, viewModal)} + className={classes.customerName} + style={{cursor: 'pointer'}} + > + {order.first_name} {order.last_name} + + {order.company_name && ( + + {order.company_name} + + )} +
+ { + if (!opened) setEmailPopoverId(null); + }} + width={200} + position="bottom" + withArrow + shadow="md" + > + + setEmailPopoverId(order.id)} + className={classes.customerEmail} + style={{cursor: 'pointer'}} + > + {order.email} - - -
- {order.first_name + ' ' + order.last_name} -
- {order.email} -
- - handleModalClick(order.id, viewModal)}> - {formatNumber(order.attendees?.length as number)} +
+ + + + + + +
+
+ ); + }, + meta: { + headerStyle: {minWidth: 280}, + }, + }, + { + id: 'orderDetails', + header: t`Order Details`, + enableHiding: true, + cell: (info: CellContext) => { + const order = info.row.original; + return ( +
+ handleModalClick(order.id, viewModal)} + className={classes.orderId} + style={{cursor: 'pointer'}} + > + {order.public_id} + +
+ + {relativeDate(order.created_at)} + + {order.latest_invoice ? ( + handleInvoiceDownload(order.latest_invoice as Invoice)} + className={classes.invoiceLink} + style={{cursor: 'pointer'}} + > + + {t`Invoice`} #{order.latest_invoice.invoice_number} - - - - - - - - {relativeDate(order.created_at)} - - - - - - - - - - - ); - })} - -
-
- ); - - const OrderTableMobile = () => ( - - {orders.map((order) => { - return ( - -
- -
- -
-
- {order.first_name + ' ' + order.last_name} + ) : ( + + + {t`No invoice`} + + )} + {order.status === 'RESERVED' && order.reserved_until && ( + + + {t`Reserved until`} {formatTime(order.reserved_until)} + + )}
- {order.email} - - - {t`Reference`}: {order.public_id} - handleModalClick(order.id, viewModal)}> - - - - - - {t`Created`}: {relativeDate(order.created_at)} - - -
-
-
- -
-
- -
+ ); + }, + }, + { + id: 'items', + header: t`Items`, + enableHiding: true, + cell: (info: CellContext) => { + const order = info.row.original; + const itemCount = order.order_items?.length || order.attendees?.length || 0; + return ( +
+ + {formatNumber(itemCount)} {t`item(s)`} +
+ ); + }, + }, + { + id: 'amount', + header: t`Amount`, + enableHiding: true, + cell: (info: CellContext) => { + const order = info.row.original; + return ( +
+ + {formatCurrency(order.total_gross, order.currency)} + + + {t`Tax`}: {formatCurrency(order.total_tax, order.currency)} • + {' '}{t`Fees`}: {formatCurrency(order.total_fee, order.currency)} + + {order.total_refunded > 0 && ( + + + {t`Refunded`}: {formatCurrency(order.total_refunded, order.currency)} + + )} +
+ ); + }, + }, + { + id: 'payment', + header: t`Payment`, + enableHiding: true, + cell: (info: CellContext) => { + const order = info.row.original; + return ( +
+ {order.payment_provider === 'STRIPE' ? ( + <> + + {t`Stripe`} + + ) : order.payment_provider === 'OFFLINE' ? ( + <> + + {t`Offline`} + + ) : ( + <> + + {t`Other`} + + )}
- - ); - })} - + ); + }, + }, + { + id: 'status', + header: t`Status`, + enableHiding: true, + cell: (info: CellContext) => { + const order = info.row.original; + return ( +
+ {order.status === 'COMPLETED' && ( + <> + + {t`Completed`} + + )} + {order.status === 'RESERVED' && ( + <> + + {t`Reserved`} + + )} + {order.status === 'AWAITING_OFFLINE_PAYMENT' && ( + <> + + {t`Awaiting Payment`} + + )} + {order.status === 'CANCELLED' && ( + <> + + {t`Cancelled`} + + )} + {order.status === 'ABANDONED' && ( + <> + + {t`Abandoned`} + + )} +
+ ); + }, + }, + { + id: 'actions', + header: t`Actions`, + enableHiding: false, + cell: (info: CellContext) => { + const order = info.row.original; + return ( +
+ +
+ ); + }, + meta: { + sticky: 'right', + }, + }, + ], + [event.id, emailPopoverId] ); return ( <> - - + } + /> {orderId && ( <> {isRefundModalOpen && } diff --git a/frontend/src/components/common/TanStackTable/TanStackTable.module.scss b/frontend/src/components/common/TanStackTable/TanStackTable.module.scss index 154729a1f9..e8ce8ffdab 100644 --- a/frontend/src/components/common/TanStackTable/TanStackTable.module.scss +++ b/frontend/src/components/common/TanStackTable/TanStackTable.module.scss @@ -19,7 +19,7 @@ th { background-color: #f2f0f5; color: var(--hi-secondary-text) !important; - padding: 0.75rem 0.625rem !important; + padding: 20px !important; } } @@ -30,6 +30,10 @@ :global(.mantine-ScrollArea-viewport) { padding-bottom: 0; } + + :global(.mantine-ScrollArea-scrollbar) { + z-index: 20 !important; + } } .table { @@ -83,7 +87,7 @@ .stickyRight { position: sticky; right: 0; - z-index: 10; + z-index: 9; background: var(--mantine-color-body); &::before { diff --git a/frontend/src/components/common/TanStackTable/index.tsx b/frontend/src/components/common/TanStackTable/index.tsx index 07d3e617ed..74596d9cc7 100644 --- a/frontend/src/components/common/TanStackTable/index.tsx +++ b/frontend/src/components/common/TanStackTable/index.tsx @@ -1,10 +1,4 @@ -import { - ColumnDef, - flexRender, - getCoreRowModel, - useReactTable, - VisibilityState, -} from '@tanstack/react-table'; +import {ColumnDef, flexRender, getCoreRowModel, useReactTable, VisibilityState,} from '@tanstack/react-table'; import {Table as MantineTable} from '@mantine/core'; import React, {useEffect, useState} from 'react'; import {Card} from '../Card'; @@ -29,12 +23,12 @@ interface TanStackTableProps { } export function TanStackTable({ - data, - columns, - storageKey, - enableColumnVisibility = false, - renderColumnVisibilityToggle, -}: TanStackTableProps) { + data, + columns, + storageKey, + enableColumnVisibility = false, + renderColumnVisibilityToggle, + }: TanStackTableProps) { const [columnVisibility, setColumnVisibility] = useState(() => { if (storageKey && enableColumnVisibility) { const stored = localStorage.getItem(`${storageKey}-column-visibility`); @@ -74,7 +68,9 @@ export function TanStackTable({
)} - + {table.getHeaderGroups().map((headerGroup) => ( @@ -84,8 +80,8 @@ export function TanStackTable({ const stickyClass = columnMeta?.sticky === 'left' ? classes.stickyLeft : columnMeta?.sticky === 'right' - ? classes.stickyRight - : ''; + ? classes.stickyRight + : ''; return ( ({ const stickyClass = columnMeta?.sticky === 'left' ? classes.stickyLeft : columnMeta?.sticky === 'right' - ? classes.stickyRight - : ''; + ? classes.stickyRight + : ''; return (