|
|
@@ -1,15 +1,23 @@
|
|
|
-import { ConfirmationDialog } from '@/components/shared/confirmation-dialog.js';
|
|
|
-import { CustomerSelector } from '@/components/shared/customer-selector.js';
|
|
|
-import { ErrorPage } from '@/components/shared/error-page.js';
|
|
|
-import { PermissionGuard } from '@/components/shared/permission-guard.js';
|
|
|
-import { Button } from '@/components/ui/button.js';
|
|
|
-import { Form } from '@/components/ui/form.js';
|
|
|
-import { addCustomFields } from '@/framework/document-introspection/add-custom-fields.js';
|
|
|
-import { useGeneratedForm } from '@/framework/form-engine/use-generated-form.js';
|
|
|
-import { CustomFieldsPageBlock, Page, PageActionBar, PageActionBarRight, PageBlock, PageLayout, PageTitle } from '@/framework/layout-engine/page-layout.js';
|
|
|
-import { getDetailQueryOptions, useDetailPage } from '@/framework/page/use-detail-page.js';
|
|
|
-import { api } from '@/graphql/api.js';
|
|
|
-import { Trans, useLingui } from '@/lib/trans.js';
|
|
|
+import { ConfirmationDialog } from '@/vdb/components/shared/confirmation-dialog.js';
|
|
|
+import { CustomFieldsForm } from '@/vdb/components/shared/custom-fields-form.js';
|
|
|
+import { CustomerSelector } from '@/vdb/components/shared/customer-selector.js';
|
|
|
+import { ErrorPage } from '@/vdb/components/shared/error-page.js';
|
|
|
+import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
|
|
|
+import { Button } from '@/vdb/components/ui/button.js';
|
|
|
+import { Form } from '@/vdb/components/ui/form.js';
|
|
|
+import { addCustomFields } from '@/vdb/framework/document-introspection/add-custom-fields.js';
|
|
|
+import { useGeneratedForm } from '@/vdb/framework/form-engine/use-generated-form.js';
|
|
|
+import {
|
|
|
+ Page,
|
|
|
+ PageActionBar,
|
|
|
+ PageActionBarRight,
|
|
|
+ PageBlock,
|
|
|
+ PageLayout,
|
|
|
+ PageTitle,
|
|
|
+} from '@/vdb/framework/layout-engine/page-layout.js';
|
|
|
+import { getDetailQueryOptions, useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
|
|
|
+import { api } from '@/vdb/graphql/api.js';
|
|
|
+import { Trans, useLingui } from '@/vdb/lib/trans.js';
|
|
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
|
|
import { createFileRoute, Link, redirect, useNavigate } from '@tanstack/react-router';
|
|
|
import { ResultOf } from 'gql.tada';
|
|
|
@@ -18,15 +26,28 @@ import { toast } from 'sonner';
|
|
|
import { CustomerAddressSelector } from './components/customer-address-selector.js';
|
|
|
import { EditOrderTable } from './components/edit-order-table.js';
|
|
|
import { OrderAddress } from './components/order-address.js';
|
|
|
-import { addItemToDraftOrderDocument, adjustDraftOrderLineDocument, applyCouponCodeToDraftOrderDocument, deleteDraftOrderDocument, draftOrderEligibleShippingMethodsDocument, orderDetailDocument, removeCouponCodeFromDraftOrderDocument, removeDraftOrderLineDocument, setBillingAddressForDraftOrderDocument, setCustomerForDraftOrderDocument, setDraftOrderCustomFieldsDocument, setDraftOrderShippingMethodDocument, setShippingAddressForDraftOrderDocument, transitionOrderToStateDocument, unsetBillingAddressForDraftOrderDocument, unsetShippingAddressForDraftOrderDocument } from './orders.graphql.js';
|
|
|
-import { CustomFieldsForm } from '@/components/shared/custom-fields-form.js';
|
|
|
+import {
|
|
|
+ addItemToDraftOrderDocument,
|
|
|
+ adjustDraftOrderLineDocument,
|
|
|
+ applyCouponCodeToDraftOrderDocument,
|
|
|
+ deleteDraftOrderDocument,
|
|
|
+ draftOrderEligibleShippingMethodsDocument,
|
|
|
+ orderDetailDocument,
|
|
|
+ removeCouponCodeFromDraftOrderDocument,
|
|
|
+ removeDraftOrderLineDocument,
|
|
|
+ setBillingAddressForDraftOrderDocument,
|
|
|
+ setCustomerForDraftOrderDocument,
|
|
|
+ setDraftOrderCustomFieldsDocument,
|
|
|
+ setDraftOrderShippingMethodDocument,
|
|
|
+ setShippingAddressForDraftOrderDocument,
|
|
|
+ transitionOrderToStateDocument,
|
|
|
+ unsetBillingAddressForDraftOrderDocument,
|
|
|
+ unsetShippingAddressForDraftOrderDocument,
|
|
|
+} from './orders.graphql.js';
|
|
|
|
|
|
export const Route = createFileRoute('/_authenticated/_orders/orders_/draft/$id')({
|
|
|
component: DraftOrderPage,
|
|
|
- loader: async ({
|
|
|
- context,
|
|
|
- params,
|
|
|
- }) => {
|
|
|
+ loader: async ({ context, params }) => {
|
|
|
if (!params.id) {
|
|
|
throw new Error('ID param is required');
|
|
|
}
|
|
|
@@ -80,7 +101,7 @@ function DraftOrderPage() {
|
|
|
quantity: entity.quantity,
|
|
|
orderLineId: entity.id,
|
|
|
customFields: entity.customFields,
|
|
|
- }
|
|
|
+ },
|
|
|
};
|
|
|
},
|
|
|
});
|
|
|
@@ -95,7 +116,7 @@ function DraftOrderPage() {
|
|
|
input: {
|
|
|
id: entity.id,
|
|
|
customFields: entity.customFields,
|
|
|
- }
|
|
|
+ },
|
|
|
};
|
|
|
},
|
|
|
});
|
|
|
@@ -286,12 +307,17 @@ function DraftOrderPage() {
|
|
|
}
|
|
|
|
|
|
const onSaveCustomFields = (values: any) => {
|
|
|
- setDraftOrderCustomFields({ input: { id: entity.id, customFields: values.input?.customFields }, orderId: entity.id });
|
|
|
- }
|
|
|
+ setDraftOrderCustomFields({
|
|
|
+ input: { id: entity.id, customFields: values.input?.customFields },
|
|
|
+ orderId: entity.id,
|
|
|
+ });
|
|
|
+ };
|
|
|
|
|
|
return (
|
|
|
<Page pageId="draft-order-detail" form={form}>
|
|
|
- <PageTitle><Trans>Draft order</Trans>: {entity?.code ?? ''}</PageTitle>
|
|
|
+ <PageTitle>
|
|
|
+ <Trans>Draft order</Trans>: {entity?.code ?? ''}
|
|
|
+ </PageTitle>
|
|
|
<PageActionBar>
|
|
|
<PageActionBarRight>
|
|
|
<PermissionGuard requires={['DeleteOrder']}>
|
|
|
@@ -306,11 +332,16 @@ function DraftOrderPage() {
|
|
|
<Trans>Delete draft</Trans>
|
|
|
</Button>
|
|
|
</ConfirmationDialog>
|
|
|
-
|
|
|
</PermissionGuard>
|
|
|
<PermissionGuard requires={['UpdateOrder']}>
|
|
|
- <Button type="button"
|
|
|
- disabled={!entity.customer || entity.lines.length === 0 || entity.shippingLines.length === 0 || entity.state !== 'Draft'}
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ disabled={
|
|
|
+ !entity.customer ||
|
|
|
+ entity.lines.length === 0 ||
|
|
|
+ entity.shippingLines.length === 0 ||
|
|
|
+ entity.state !== 'Draft'
|
|
|
+ }
|
|
|
onClick={() => completeDraftOrder({ id: entity.id, state: 'ArrangingPayment' })}
|
|
|
>
|
|
|
<Trans>Complete draft</Trans>
|
|
|
@@ -320,88 +351,153 @@ function DraftOrderPage() {
|
|
|
</PageActionBar>
|
|
|
<PageLayout>
|
|
|
<PageBlock column="main" blockId="order-table">
|
|
|
- <EditOrderTable order={entity}
|
|
|
- eligibleShippingMethods={eligibleShippingMethods?.eligibleShippingMethodsForDraftOrder ?? []}
|
|
|
- onSetShippingMethod={(e) => setShippingMethodForDraftOrder({ orderId: entity.id, shippingMethodId: e.shippingMethodId })}
|
|
|
- onAddItem={(e) => addItemToDraftOrder({ orderId: entity.id, input: { productVariantId: e.productVariantId, quantity: 1 } })}
|
|
|
- onAdjustLine={(e) => adjustDraftOrderLine({ orderId: entity.id, input: { orderLineId: e.lineId, quantity: e.quantity, customFields: e.customFields } as any })}
|
|
|
- onRemoveLine={(e) => removeDraftOrderLine({ orderId: entity.id, orderLineId: e.lineId })}
|
|
|
- onApplyCouponCode={(e) => setCouponCodeForDraftOrder({ orderId: entity.id, couponCode: e.couponCode })}
|
|
|
- onRemoveCouponCode={(e) => removeCouponCodeForDraftOrder({ orderId: entity.id, couponCode: e.couponCode })}
|
|
|
+ <EditOrderTable
|
|
|
+ order={entity}
|
|
|
+ eligibleShippingMethods={
|
|
|
+ eligibleShippingMethods?.eligibleShippingMethodsForDraftOrder ?? []
|
|
|
+ }
|
|
|
+ onSetShippingMethod={e =>
|
|
|
+ setShippingMethodForDraftOrder({
|
|
|
+ orderId: entity.id,
|
|
|
+ shippingMethodId: e.shippingMethodId,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ onAddItem={e =>
|
|
|
+ addItemToDraftOrder({
|
|
|
+ orderId: entity.id,
|
|
|
+ input: { productVariantId: e.productVariantId, quantity: 1 },
|
|
|
+ })
|
|
|
+ }
|
|
|
+ onAdjustLine={e =>
|
|
|
+ adjustDraftOrderLine({
|
|
|
+ orderId: entity.id,
|
|
|
+ input: {
|
|
|
+ orderLineId: e.lineId,
|
|
|
+ quantity: e.quantity,
|
|
|
+ customFields: e.customFields,
|
|
|
+ } as any,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ onRemoveLine={e =>
|
|
|
+ removeDraftOrderLine({
|
|
|
+ orderId: entity.id,
|
|
|
+ orderLineId: e.lineId,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ onApplyCouponCode={e =>
|
|
|
+ setCouponCodeForDraftOrder({
|
|
|
+ orderId: entity.id,
|
|
|
+ couponCode: e.couponCode,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ onRemoveCouponCode={e =>
|
|
|
+ removeCouponCodeForDraftOrder({
|
|
|
+ orderId: entity.id,
|
|
|
+ couponCode: e.couponCode,
|
|
|
+ })
|
|
|
+ }
|
|
|
orderLineForm={orderLineForm}
|
|
|
/>
|
|
|
</PageBlock>
|
|
|
<PageBlock column="main" blockId="order-custom-fields" title={<Trans>Custom fields</Trans>}>
|
|
|
<Form {...orderCustomFieldsForm}>
|
|
|
- <CustomFieldsForm entityType="Order" control={orderCustomFieldsForm.control} formPathPrefix='input' />
|
|
|
+ <CustomFieldsForm
|
|
|
+ entityType="Order"
|
|
|
+ control={orderCustomFieldsForm.control}
|
|
|
+ formPathPrefix="input"
|
|
|
+ />
|
|
|
<div className="mt-4">
|
|
|
- <Button type="submit" className=""
|
|
|
- disabled={!orderCustomFieldsForm.formState.isValid || !orderCustomFieldsForm.formState.isDirty}
|
|
|
- onClick={(e) => {
|
|
|
+ <Button
|
|
|
+ type="submit"
|
|
|
+ className=""
|
|
|
+ disabled={
|
|
|
+ !orderCustomFieldsForm.formState.isValid ||
|
|
|
+ !orderCustomFieldsForm.formState.isDirty
|
|
|
+ }
|
|
|
+ onClick={e => {
|
|
|
e.preventDefault();
|
|
|
e.stopPropagation();
|
|
|
orderCustomFieldsForm.handleSubmit(onSaveCustomFields)();
|
|
|
- }}>
|
|
|
+ }}
|
|
|
+ >
|
|
|
<Trans>Set custom fields</Trans>
|
|
|
</Button>
|
|
|
</div>
|
|
|
</Form>
|
|
|
</PageBlock>
|
|
|
<PageBlock column="side" blockId="customer" title={<Trans>Customer</Trans>}>
|
|
|
- {entity?.customer?.id ? <Button variant="ghost" asChild className="mb-4">
|
|
|
- <Link to={`/customers/${entity?.customer?.id}`}>
|
|
|
- <User className="w-4 h-4" />
|
|
|
- {entity?.customer?.firstName} {entity?.customer?.lastName}
|
|
|
- </Link>
|
|
|
- </Button> : null}
|
|
|
- <CustomerSelector onSelect={customer => {
|
|
|
- setCustomerForDraftOrder({ orderId: entity.id, customerId: customer.id });
|
|
|
- }} />
|
|
|
+ {entity?.customer?.id ? (
|
|
|
+ <Button variant="ghost" asChild className="mb-4">
|
|
|
+ <Link to={`/customers/${entity?.customer?.id}`}>
|
|
|
+ <User className="w-4 h-4" />
|
|
|
+ {entity?.customer?.firstName} {entity?.customer?.lastName}
|
|
|
+ </Link>
|
|
|
+ </Button>
|
|
|
+ ) : null}
|
|
|
+ <CustomerSelector
|
|
|
+ onSelect={customer => {
|
|
|
+ setCustomerForDraftOrder({ orderId: entity.id, customerId: customer.id });
|
|
|
+ }}
|
|
|
+ />
|
|
|
</PageBlock>
|
|
|
<PageBlock column="side" blockId="shipping-address" title={<Trans>Shipping address</Trans>}>
|
|
|
<div className="flex flex-col">
|
|
|
<OrderAddress address={entity.shippingAddress ?? undefined} />
|
|
|
- {entity.shippingAddress?.streetLine1
|
|
|
- ? <RemoveAddressButton onClick={() => unsetShippingAddressForDraftOrder({ orderId: entity.id })} />
|
|
|
- : <CustomerAddressSelector customerId={entity.customer?.id} onSelect={address => {
|
|
|
- setShippingAddressForDraftOrder({
|
|
|
- orderId: entity.id, input: {
|
|
|
- fullName: address.fullName,
|
|
|
- company: address.company,
|
|
|
- streetLine1: address.streetLine1,
|
|
|
- streetLine2: address.streetLine2,
|
|
|
- city: address.city,
|
|
|
- province: address.province,
|
|
|
- postalCode: address.postalCode,
|
|
|
- countryCode: address.country.code,
|
|
|
- phoneNumber: address.phoneNumber,
|
|
|
- }
|
|
|
- });
|
|
|
- }} />
|
|
|
- }
|
|
|
+ {entity.shippingAddress?.streetLine1 ? (
|
|
|
+ <RemoveAddressButton
|
|
|
+ onClick={() => unsetShippingAddressForDraftOrder({ orderId: entity.id })}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <CustomerAddressSelector
|
|
|
+ customerId={entity.customer?.id}
|
|
|
+ onSelect={address => {
|
|
|
+ setShippingAddressForDraftOrder({
|
|
|
+ orderId: entity.id,
|
|
|
+ input: {
|
|
|
+ fullName: address.fullName,
|
|
|
+ company: address.company,
|
|
|
+ streetLine1: address.streetLine1,
|
|
|
+ streetLine2: address.streetLine2,
|
|
|
+ city: address.city,
|
|
|
+ province: address.province,
|
|
|
+ postalCode: address.postalCode,
|
|
|
+ countryCode: address.country.code,
|
|
|
+ phoneNumber: address.phoneNumber,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
</div>
|
|
|
</PageBlock>
|
|
|
<PageBlock column="side" blockId="billing-address" title={<Trans>Billing address</Trans>}>
|
|
|
<div className="flex flex-col">
|
|
|
<OrderAddress address={entity.billingAddress ?? undefined} />
|
|
|
- {entity.billingAddress?.streetLine1
|
|
|
- ? <RemoveAddressButton onClick={() => unsetBillingAddressForDraftOrder({ orderId: entity.id })} />
|
|
|
- : <CustomerAddressSelector customerId={entity.customer?.id} onSelect={address => {
|
|
|
- setBillingAddressForDraftOrder({
|
|
|
- orderId: entity.id, input: {
|
|
|
- fullName: address.fullName,
|
|
|
- company: address.company,
|
|
|
- streetLine1: address.streetLine1,
|
|
|
- streetLine2: address.streetLine2,
|
|
|
- city: address.city,
|
|
|
- province: address.province,
|
|
|
- postalCode: address.postalCode,
|
|
|
- countryCode: address.country.code,
|
|
|
- phoneNumber: address.phoneNumber,
|
|
|
- }
|
|
|
- });
|
|
|
- }} />
|
|
|
- }
|
|
|
+ {entity.billingAddress?.streetLine1 ? (
|
|
|
+ <RemoveAddressButton
|
|
|
+ onClick={() => unsetBillingAddressForDraftOrder({ orderId: entity.id })}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <CustomerAddressSelector
|
|
|
+ customerId={entity.customer?.id}
|
|
|
+ onSelect={address => {
|
|
|
+ setBillingAddressForDraftOrder({
|
|
|
+ orderId: entity.id,
|
|
|
+ input: {
|
|
|
+ fullName: address.fullName,
|
|
|
+ company: address.company,
|
|
|
+ streetLine1: address.streetLine1,
|
|
|
+ streetLine2: address.streetLine2,
|
|
|
+ city: address.city,
|
|
|
+ province: address.province,
|
|
|
+ postalCode: address.postalCode,
|
|
|
+ countryCode: address.country.code,
|
|
|
+ phoneNumber: address.phoneNumber,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
</div>
|
|
|
</PageBlock>
|
|
|
</PageLayout>
|
|
|
@@ -410,9 +506,11 @@ function DraftOrderPage() {
|
|
|
}
|
|
|
|
|
|
function RemoveAddressButton(props: { onClick: () => void }) {
|
|
|
- return (<div className="">
|
|
|
- <Button variant="outline" className="mt-4" size="sm" onClick={props.onClick}>
|
|
|
- <Trans>Remove</Trans>
|
|
|
- </Button>
|
|
|
- </div>)
|
|
|
-}
|
|
|
+ return (
|
|
|
+ <div className="">
|
|
|
+ <Button variant="outline" className="mt-4" size="sm" onClick={props.onClick}>
|
|
|
+ <Trans>Remove</Trans>
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|