feat(dashboard,types): add credit lines + loyalty changes (#11885)
* feat(dashboard,types): add credit lines + loyalty changes * chore: fix types * chore: fix specs * chore: use correct plugin name * chore: use new currency input --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,7 @@ export * from "./notification"
|
||||
export * from "./orders"
|
||||
export * from "./payment-collections"
|
||||
export * from "./payments"
|
||||
export * from "./plugins"
|
||||
export * from "./price-lists"
|
||||
export * from "./product-types"
|
||||
export * from "./product-variants"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { CreateOrderCreditLineDTO, HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
useMutation,
|
||||
@@ -354,3 +354,28 @@ export const useCancelOrderTransfer = (
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useCreateOrderCreditLine = (
|
||||
orderId: string,
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminOrderResponse,
|
||||
FetchError,
|
||||
Omit<CreateOrderCreditLineDTO, "order_id">
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload) => sdk.admin.order.createCreditLine(orderId, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ordersQueryKeys.details(),
|
||||
})
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ordersQueryKeys.preview(orderId),
|
||||
})
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
28
packages/admin/dashboard/src/hooks/api/plugins.tsx
Normal file
28
packages/admin/dashboard/src/hooks/api/plugins.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
|
||||
const PLUGINS_QUERY_KEY = "plugins" as const
|
||||
export const pluginsQueryKeys = queryKeysFactory(PLUGINS_QUERY_KEY)
|
||||
|
||||
export const usePlugins = (
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
any,
|
||||
FetchError,
|
||||
HttpTypes.AdminPluginsListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () => sdk.admin.plugin.list(),
|
||||
queryKey: pluginsQueryKeys.list(),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
@@ -3783,6 +3783,78 @@
|
||||
"orders": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"giftCardsStoreCreditLines": {
|
||||
"type": "string"
|
||||
},
|
||||
"creditLines": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"total": {
|
||||
"type": "string"
|
||||
},
|
||||
"creditOrDebit": {
|
||||
"type": "string"
|
||||
},
|
||||
"createCreditLine": {
|
||||
"type": "string"
|
||||
},
|
||||
"createCreditLineSuccess": {
|
||||
"type": "string"
|
||||
},
|
||||
"createCreditLineError": {
|
||||
"type": "string"
|
||||
},
|
||||
"createCreditLineDescription": {
|
||||
"type": "string"
|
||||
},
|
||||
"operation": {
|
||||
"type": "string"
|
||||
},
|
||||
"credit": {
|
||||
"type": "string"
|
||||
},
|
||||
"debit": {
|
||||
"type": "string"
|
||||
},
|
||||
"debitDescription": {
|
||||
"type": "string"
|
||||
},
|
||||
"creditDescription": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"balanceSettlement": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"settlementType": {
|
||||
"type": "string"
|
||||
},
|
||||
"settlementTypes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"paymentMethod": {
|
||||
"type": "string"
|
||||
},
|
||||
"paymentMethodDescription": {
|
||||
"type": "string"
|
||||
},
|
||||
"creditLine": {
|
||||
"type": "string"
|
||||
},
|
||||
"creditLineDescription": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3954,6 +4026,9 @@
|
||||
"totalPaidByCustomer": {
|
||||
"type": "string"
|
||||
},
|
||||
"totalStoreCreditRefunds": {
|
||||
"type": "string"
|
||||
},
|
||||
"capture": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -4055,6 +4130,7 @@
|
||||
"title",
|
||||
"isReadyToBeCaptured",
|
||||
"totalPaidByCustomer",
|
||||
"totalStoreCreditRefunds",
|
||||
"capture",
|
||||
"capture_short",
|
||||
"refund",
|
||||
@@ -10601,6 +10677,12 @@
|
||||
"amount": {
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"type": "string"
|
||||
},
|
||||
"reference_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"refundAmount": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -10859,6 +10941,9 @@
|
||||
"paidTotal": {
|
||||
"type": "string"
|
||||
},
|
||||
"creditTotal": {
|
||||
"type": "string"
|
||||
},
|
||||
"totalExclTax": {
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1010,6 +1010,31 @@
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"giftCardsStoreCreditLines": "Gift cards & credit lines",
|
||||
"creditLines": {
|
||||
"title": "Credit lines",
|
||||
"total": "Sum of all credit lines",
|
||||
"creditOrDebit": "Credit / Debit",
|
||||
"createCreditLine": "Create credit line",
|
||||
"createCreditLineSuccess": "Credit line created successfully",
|
||||
"createCreditLineError": "Error creating credit line",
|
||||
"createCreditLineDescription": "Create a credit line for amount {{amount}}",
|
||||
"operation": "Operation",
|
||||
"credit": "Credit",
|
||||
"creditDescription": "Adds a positive sum to the order",
|
||||
"debit": "Debit",
|
||||
"debitDescription": "Subtracts a negative sum from the order"
|
||||
},
|
||||
"balanceSettlement": {
|
||||
"title": "Balance settlement",
|
||||
"settlementType": "Settlement type",
|
||||
"settlementTypes": {
|
||||
"paymentMethod": "Payment method",
|
||||
"paymentMethodDescription": "Refund amount to the payment method",
|
||||
"creditLine": "Store credit",
|
||||
"creditLineDescription": "Refund amount as store credit"
|
||||
}
|
||||
},
|
||||
"domain": "Orders",
|
||||
"claim": "Claim",
|
||||
"exchange": "Exchange",
|
||||
@@ -1056,6 +1081,7 @@
|
||||
"title": "Payments",
|
||||
"isReadyToBeCaptured": "Payment <0/> is ready to be captured.",
|
||||
"totalPaidByCustomer": "Total paid by customer",
|
||||
"totalStoreCreditRefunds": "Total store credit refunds",
|
||||
"capture": "Capture payment",
|
||||
"capture_short": "Capture",
|
||||
"refund": "Refund",
|
||||
@@ -2847,6 +2873,8 @@
|
||||
},
|
||||
"fields": {
|
||||
"amount": "Amount",
|
||||
"reference": "Reference",
|
||||
"reference_id": "Reference ID",
|
||||
"refundAmount": "Refund amount",
|
||||
"name": "Name",
|
||||
"default": "Default",
|
||||
@@ -2932,7 +2960,8 @@
|
||||
"orders": "Orders",
|
||||
"account": "Account",
|
||||
"total": "Order Total",
|
||||
"paidTotal": "Total captured",
|
||||
"paidTotal": "Paid Total",
|
||||
"creditTotal": "Credit Lines Total",
|
||||
"totalExclTax": "Total excl. tax",
|
||||
"subtotal": "Subtotal",
|
||||
"shipping": "Shipping",
|
||||
|
||||
8
packages/admin/dashboard/src/lib/credit-line.ts
Normal file
8
packages/admin/dashboard/src/lib/credit-line.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { OrderCreditLineDTO } from "@medusajs/types"
|
||||
|
||||
export const getTotalCreditLines = (creditLines: OrderCreditLineDTO[]) =>
|
||||
creditLines.reduce((acc, creditLine) => {
|
||||
acc = acc + (creditLine.amount as number)
|
||||
|
||||
return acc
|
||||
}, 0)
|
||||
8
packages/admin/dashboard/src/lib/orders.ts
Normal file
8
packages/admin/dashboard/src/lib/orders.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
export const getPaymentsFromOrder = (order: HttpTypes.AdminOrder) => {
|
||||
return order.payment_collections
|
||||
.map((collection: HttpTypes.AdminPaymentCollection) => collection.payments)
|
||||
.flat(1)
|
||||
.filter(Boolean) as HttpTypes.AdminPayment[]
|
||||
}
|
||||
7
packages/admin/dashboard/src/lib/plugins.ts
Normal file
7
packages/admin/dashboard/src/lib/plugins.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
export const LOYALTY_PLUGIN_NAME = "@medusajs/loyalty-plugin"
|
||||
|
||||
export const getLoyaltyPlugin = (plugins: HttpTypes.AdminPlugin[]) => {
|
||||
return plugins?.find((plugin) => plugin.name === LOYALTY_PLUGIN_NAME)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./order-balance-settlement-form"
|
||||
@@ -0,0 +1,398 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { AdminOrder, AdminPayment } from "@medusajs/types"
|
||||
import {
|
||||
Button,
|
||||
clx,
|
||||
CurrencyInput,
|
||||
Divider,
|
||||
Input,
|
||||
Label,
|
||||
RadioGroup,
|
||||
Select,
|
||||
Textarea,
|
||||
toast,
|
||||
} from "@medusajs/ui"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { formatValue } from "react-currency-input-field"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
|
||||
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||
import {
|
||||
useCreateOrderCreditLine,
|
||||
useRefundPayment,
|
||||
} from "../../../../../hooks/api"
|
||||
import { currencies } from "../../../../../lib/data/currencies"
|
||||
import { formatCurrency } from "../../../../../lib/format-currency"
|
||||
import { getLocaleAmount } from "../../../../../lib/money-amount-helpers"
|
||||
import { getPaymentsFromOrder } from "../../../../../lib/orders"
|
||||
|
||||
const OrderBalanceSettlementSchema = zod.object({
|
||||
settlement_type: zod.enum(["credit_line", "refund"]),
|
||||
refund: zod
|
||||
.object({
|
||||
amount: zod.string().or(zod.number()).optional(),
|
||||
note: zod.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
credit_line: zod
|
||||
.object({
|
||||
amount: zod.string().or(zod.number()).optional(),
|
||||
reference: zod.string().optional(),
|
||||
reference_id: zod.string().optional(),
|
||||
note: zod.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export const OrderBalanceSettlementForm = ({
|
||||
order,
|
||||
}: {
|
||||
order: AdminOrder
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
const [activePayment, setActivePayment] = useState<AdminPayment | null>(null)
|
||||
const payments = getPaymentsFromOrder(order)
|
||||
const pendingDifference = order.summary.pending_difference * -1
|
||||
|
||||
const form = useForm<zod.infer<typeof OrderBalanceSettlementSchema>>({
|
||||
defaultValues: {
|
||||
settlement_type: "refund",
|
||||
refund: {
|
||||
amount: 0,
|
||||
},
|
||||
credit_line: {
|
||||
amount: 0,
|
||||
},
|
||||
},
|
||||
resolver: zodResolver(OrderBalanceSettlementSchema),
|
||||
})
|
||||
|
||||
const { mutateAsync: createCreditLine, isPending: isCreditLinePending } =
|
||||
useCreateOrderCreditLine(order.id)
|
||||
|
||||
const { mutateAsync: createRefund, isPending: isRefundPending } =
|
||||
useRefundPayment(order.id, activePayment?.id!)
|
||||
|
||||
const settlementType = form.watch("settlement_type")
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
if (data.settlement_type === "credit_line") {
|
||||
await createCreditLine(
|
||||
{
|
||||
amount: parseFloat(data.credit_line!.amount! as string) * -1,
|
||||
reference: data.credit_line!.reference ?? "order",
|
||||
reference_id: data.credit_line!.reference_id ?? order.id,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(t("orders.creditLines.createCreditLineSuccess"))
|
||||
|
||||
handleSuccess()
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (data.settlement_type === "refund") {
|
||||
await createRefund(
|
||||
{
|
||||
amount: parseFloat(data.refund!.amount! as string),
|
||||
note: data.refund!.note,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(
|
||||
t("orders.payment.refundPaymentSuccess", {
|
||||
amount: formatCurrency(
|
||||
parseFloat(data.refund!.amount! as string),
|
||||
order.currency_code!
|
||||
),
|
||||
})
|
||||
)
|
||||
|
||||
handleSuccess()
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(error.message)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const currency = useMemo(
|
||||
() => currencies[order.currency_code.toUpperCase()],
|
||||
[order.currency_code]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
form.clearErrors()
|
||||
|
||||
const minimum = activePayment?.amount
|
||||
? Math.min(pendingDifference, activePayment.amount)
|
||||
: pendingDifference
|
||||
|
||||
if (settlementType === "refund") {
|
||||
form.setValue("refund.amount", activePayment ? minimum : 0)
|
||||
}
|
||||
|
||||
if (settlementType === "credit_line") {
|
||||
form.setValue("credit_line.amount", minimum)
|
||||
}
|
||||
}, [settlementType, activePayment, pendingDifference, form])
|
||||
|
||||
return (
|
||||
<RouteDrawer.Form form={form}>
|
||||
<KeyboundForm
|
||||
onSubmit={handleSubmit}
|
||||
className="flex size-full flex-col overflow-hidden"
|
||||
>
|
||||
<RouteDrawer.Body className="flex-1 overflow-auto">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<Label className="txt-compact-small font-sans font-medium">
|
||||
{t("orders.balanceSettlement.settlementType")}
|
||||
</Label>
|
||||
|
||||
<RadioGroup
|
||||
className="flex flex-col gap-y-3"
|
||||
value={settlementType}
|
||||
onValueChange={(value: "credit_line" | "refund") =>
|
||||
form.setValue("settlement_type", value)
|
||||
}
|
||||
>
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"refund"}
|
||||
description={t(
|
||||
"orders.balanceSettlement.settlementTypes.paymentMethodDescription"
|
||||
)}
|
||||
label={t(
|
||||
"orders.balanceSettlement.settlementTypes.paymentMethod"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
|
||||
<RadioGroup.ChoiceBox
|
||||
value={"credit_line"}
|
||||
description={t(
|
||||
"orders.balanceSettlement.settlementTypes.creditLineDescription"
|
||||
)}
|
||||
label={t(
|
||||
"orders.balanceSettlement.settlementTypes.creditLine"
|
||||
)}
|
||||
className={clx("basis-1/2")}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{settlementType === "refund" && (
|
||||
<>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
setActivePayment(payments.find((p) => p.id === value)!)
|
||||
}}
|
||||
>
|
||||
<Label className="txt-compact-small mb-[-6px] font-sans font-medium">
|
||||
{t("orders.payment.selectPaymentToRefund")}
|
||||
</Label>
|
||||
|
||||
<Select.Trigger>
|
||||
<Select.Value
|
||||
placeholder={t("orders.payment.selectPaymentToRefund")}
|
||||
/>
|
||||
</Select.Trigger>
|
||||
|
||||
<Select.Content>
|
||||
{payments.map((payment) => {
|
||||
const totalRefunded =
|
||||
payment.refunds?.reduce(
|
||||
(acc, next) => next.amount + acc,
|
||||
0
|
||||
) ?? 0
|
||||
|
||||
return (
|
||||
<Select.Item
|
||||
value={payment!.id}
|
||||
key={payment.id}
|
||||
disabled={
|
||||
!!payment.canceled_at ||
|
||||
totalRefunded >= payment.amount
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{getLocaleAmount(
|
||||
payment.amount as number,
|
||||
payment.currency_code
|
||||
)}
|
||||
{" - "}
|
||||
</span>
|
||||
<span>{payment.provider_id}</span>
|
||||
<span> - ({payment.id.replace("pay_", "")})</span>
|
||||
</Select.Item>
|
||||
)
|
||||
})}
|
||||
</Select.Content>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="refund.amount"
|
||||
render={({ field: { onChange, ...field } }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("fields.amount")}</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<CurrencyInput
|
||||
{...field}
|
||||
min={0}
|
||||
placeholder={formatValue({
|
||||
value: "0",
|
||||
decimalScale: currency.decimal_digits,
|
||||
})}
|
||||
decimalScale={currency.decimal_digits}
|
||||
symbol={currency.symbol_native}
|
||||
code={currency.code}
|
||||
value={field.value}
|
||||
onValueChange={(_value, _name, values) =>
|
||||
onChange(values?.value ? values?.value : "")
|
||||
}
|
||||
autoFocus
|
||||
/>
|
||||
</Form.Control>
|
||||
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name={`refund.note`}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("fields.note")}</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<Textarea {...field} />
|
||||
</Form.Control>
|
||||
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{settlementType === "credit_line" && (
|
||||
<>
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="credit_line.amount"
|
||||
render={({ field: { onChange, ...field } }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("fields.amount")}</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<CurrencyInput
|
||||
{...field}
|
||||
min={0}
|
||||
placeholder={formatValue({
|
||||
value: "0",
|
||||
decimalScale: currency.decimal_digits,
|
||||
})}
|
||||
decimalScale={currency.decimal_digits}
|
||||
symbol={currency.symbol_native}
|
||||
code={currency.code}
|
||||
value={field.value}
|
||||
onValueChange={(_value, _name, values) =>
|
||||
onChange(values?.value ? values?.value : "")
|
||||
}
|
||||
autoFocus
|
||||
/>
|
||||
</Form.Control>
|
||||
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="credit_line.reference"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("fields.reference")}</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<Input {...field} />
|
||||
</Form.Control>
|
||||
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="credit_line.reference_id"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("fields.reference_id")}</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<Input {...field} />
|
||||
</Form.Control>
|
||||
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</RouteDrawer.Body>
|
||||
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button variant="secondary" size="small">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteDrawer.Close>
|
||||
|
||||
<Button
|
||||
isLoading={isCreditLinePending || isRefundPending}
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="small"
|
||||
disabled={!!Object.keys(form.formState.errors || {}).length}
|
||||
>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</RouteDrawer.Footer>
|
||||
</KeyboundForm>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./components/order-balance-settlement-form"
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
toast,
|
||||
} from "@medusajs/ui"
|
||||
import { useEffect, useMemo } from "react"
|
||||
import { formatValue } from "react-currency-input-field"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate, useSearchParams } from "react-router-dom"
|
||||
@@ -19,26 +20,20 @@ import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
|
||||
import { useRefundPayment } from "../../../../../hooks/api"
|
||||
import { currencies } from "../../../../../lib/data/currencies"
|
||||
import { formatCurrency } from "../../../../../lib/format-currency"
|
||||
import { getLocaleAmount } from "../../../../../lib/money-amount-helpers"
|
||||
import { getPaymentsFromOrder } from "../../../order-detail/components/order-payment-section"
|
||||
import { formatValue } from "react-currency-input-field"
|
||||
import { formatProvider } from "../../../../../lib/format-provider"
|
||||
import { getLocaleAmount } from "../../../../../lib/money-amount-helpers"
|
||||
import { getPaymentsFromOrder } from "../../../../../lib/orders"
|
||||
|
||||
type CreateRefundFormProps = {
|
||||
order: HttpTypes.AdminOrder
|
||||
refundReasons: HttpTypes.AdminRefundReason[]
|
||||
}
|
||||
|
||||
const CreateRefundSchema = zod.object({
|
||||
amount: zod.string().or(zod.number()),
|
||||
refund_reason_id: zod.string().nullish(),
|
||||
note: zod.string().optional(),
|
||||
})
|
||||
|
||||
export const CreateRefundForm = ({
|
||||
order,
|
||||
refundReasons,
|
||||
}: CreateRefundFormProps) => {
|
||||
export const CreateRefundForm = ({ order }: CreateRefundFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
const navigate = useNavigate()
|
||||
@@ -81,14 +76,16 @@ export const CreateRefundForm = ({
|
||||
await mutateAsync(
|
||||
{
|
||||
amount: parseFloat(data.amount as string),
|
||||
refund_reason_id: data.refund_reason_id,
|
||||
note: data.note,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(
|
||||
t("orders.payment.refundPaymentSuccess", {
|
||||
amount: formatCurrency(data.amount, payment?.currency_code!),
|
||||
amount: formatCurrency(
|
||||
data.amount as number,
|
||||
payment?.currency_code!
|
||||
),
|
||||
})
|
||||
)
|
||||
|
||||
@@ -196,30 +193,6 @@ export const CreateRefundForm = ({
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* TODO: Bring this back when we have a refund reason management UI */}
|
||||
{/* <Form.Field
|
||||
control={form.control}
|
||||
name="refund_reason_id"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("fields.refundReason")}</Form.Label>
|
||||
|
||||
<Form.Control>
|
||||
<Combobox
|
||||
{...field}
|
||||
options={refundReasons.map((pp) => ({
|
||||
label: upperCaseFirst(pp.label),
|
||||
value: pp.id,
|
||||
}))}
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/> */}
|
||||
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name={`note`}
|
||||
|
||||
@@ -2,7 +2,9 @@ import { Heading } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { RouteDrawer } from "../../../components/modals"
|
||||
import { useOrder, useRefundReasons } from "../../../hooks/api"
|
||||
import { useOrder, usePlugins } from "../../../hooks/api"
|
||||
import { getLoyaltyPlugin } from "../../../lib/plugins"
|
||||
import { OrderBalanceSettlementForm } from "../order-balance-settlement"
|
||||
import { DEFAULT_FIELDS } from "../order-detail/constants"
|
||||
import { CreateRefundForm } from "./components/create-refund-form"
|
||||
|
||||
@@ -13,16 +15,8 @@ export const OrderCreateRefund = () => {
|
||||
fields: DEFAULT_FIELDS,
|
||||
})
|
||||
|
||||
const {
|
||||
refund_reasons: refundReasons,
|
||||
isLoading: isRefundReasonsLoading,
|
||||
isError: isRefundReasonsError,
|
||||
error: refundReasonsError,
|
||||
} = useRefundReasons()
|
||||
|
||||
if (isRefundReasonsError) {
|
||||
throw refundReasonsError
|
||||
}
|
||||
const { plugins = [] } = usePlugins()
|
||||
const loyaltyPlugin = getLoyaltyPlugin(plugins)
|
||||
|
||||
return (
|
||||
<RouteDrawer>
|
||||
@@ -30,9 +24,8 @@ export const OrderCreateRefund = () => {
|
||||
<Heading>{t("orders.payment.createRefund")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
|
||||
{order && !isRefundReasonsLoading && refundReasons && (
|
||||
<CreateRefundForm order={order} refundReasons={refundReasons} />
|
||||
)}
|
||||
{order && !loyaltyPlugin && <CreateRefundForm order={order} />}
|
||||
{order && loyaltyPlugin && <OrderBalanceSettlementForm order={order} />}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { useCancelReturn, useReturns } from "../../../../../hooks/api/returns"
|
||||
import { useDate } from "../../../../../hooks/use-date"
|
||||
import { getFormattedAddress } from "../../../../../lib/addresses"
|
||||
import { getStylizedAmount } from "../../../../../lib/money-amount-helpers"
|
||||
import { getPaymentsFromOrder } from "../order-payment-section"
|
||||
import { getPaymentsFromOrder } from "../../../../../lib/orders"
|
||||
import ActivityItems from "./activity-items"
|
||||
import ChangeDetailsTooltip from "./change-details-tooltip"
|
||||
|
||||
@@ -392,12 +392,12 @@ const useActivityItems = (order: AdminOrder): Activity[] => {
|
||||
edit.status === "requested"
|
||||
? edit.requested_at
|
||||
: edit.status === "confirmed"
|
||||
? edit.confirmed_at
|
||||
: edit.status === "declined"
|
||||
? edit.declined_at
|
||||
: edit.status === "canceled"
|
||||
? edit.canceled_at
|
||||
: edit.created_at,
|
||||
? edit.confirmed_at
|
||||
: edit.status === "declined"
|
||||
? edit.declined_at
|
||||
: edit.status === "canceled"
|
||||
? edit.canceled_at
|
||||
: edit.created_at,
|
||||
children: isConfirmed ? <OrderEditBody edit={edit} /> : null,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { OrderCreditLineDTO } from "@medusajs/framework/types"
|
||||
import { ArrowDownRightMini, DocumentText, XCircle } from "@medusajs/icons"
|
||||
import { AdminOrder, AdminPayment, HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
@@ -22,20 +23,19 @@ import {
|
||||
getStylizedAmount,
|
||||
} from "../../../../../lib/money-amount-helpers"
|
||||
import { getOrderPaymentStatus } from "../../../../../lib/order-helpers"
|
||||
import { getPaymentsFromOrder } from "../../../../../lib/orders"
|
||||
import { getTotalCaptured, getTotalPending } from "../../../../../lib/payment"
|
||||
import { getLoyaltyPlugin } from "../../../../../lib/plugins"
|
||||
|
||||
type OrderPaymentSectionProps = {
|
||||
order: HttpTypes.AdminOrder
|
||||
plugins: HttpTypes.AdminPlugin[]
|
||||
}
|
||||
|
||||
export const getPaymentsFromOrder = (order: HttpTypes.AdminOrder) => {
|
||||
return order.payment_collections
|
||||
.map((collection: HttpTypes.AdminPaymentCollection) => collection.payments)
|
||||
.flat(1)
|
||||
.filter(Boolean) as HttpTypes.AdminPayment[]
|
||||
}
|
||||
|
||||
export const OrderPaymentSection = ({ order }: OrderPaymentSectionProps) => {
|
||||
export const OrderPaymentSection = ({
|
||||
order,
|
||||
plugins,
|
||||
}: OrderPaymentSectionProps) => {
|
||||
const payments = getPaymentsFromOrder(order)
|
||||
|
||||
const refunds = payments
|
||||
@@ -49,6 +49,7 @@ export const OrderPaymentSection = ({ order }: OrderPaymentSectionProps) => {
|
||||
|
||||
<PaymentBreakdown
|
||||
order={order}
|
||||
plugins={plugins}
|
||||
payments={payments}
|
||||
refunds={refunds}
|
||||
currencyCode={order.currency_code}
|
||||
@@ -59,7 +60,7 @@ export const OrderPaymentSection = ({ order }: OrderPaymentSectionProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
const Header = ({ order }) => {
|
||||
const Header = ({ order }: { order: HttpTypes.AdminOrder }) => {
|
||||
const { t } = useTranslation()
|
||||
const { label, color } = getOrderPaymentStatus(t, order.payment_status)
|
||||
|
||||
@@ -279,23 +280,94 @@ const Payment = ({
|
||||
)
|
||||
}
|
||||
|
||||
const CreditLine = ({
|
||||
creditLine,
|
||||
currencyCode,
|
||||
plugins,
|
||||
}: {
|
||||
creditLine: OrderCreditLineDTO
|
||||
currencyCode: string
|
||||
plugins: HttpTypes.AdminPlugin[]
|
||||
}) => {
|
||||
const loyaltyPlugin = getLoyaltyPlugin(plugins)
|
||||
|
||||
if (!loyaltyPlugin) {
|
||||
return null
|
||||
}
|
||||
|
||||
const prettyReference = creditLine.reference
|
||||
?.split("_")
|
||||
.join(" ")
|
||||
.split("-")
|
||||
.join(" ")
|
||||
|
||||
const prettyReferenceId = creditLine.reference_id ? (
|
||||
<DisplayId id={creditLine.reference_id} />
|
||||
) : null
|
||||
|
||||
return (
|
||||
<div className="divide-y divide-dashed">
|
||||
<div className="text-ui-fg-subtle grid grid-cols-[1fr_1fr_20px] items-center gap-x-4 px-6 py-4 sm:grid-cols-[1fr_1fr_1fr_20px]">
|
||||
<div className="w-full min-w-[60px] overflow-hidden">
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
weight="plus"
|
||||
className="truncate"
|
||||
>
|
||||
{loyaltyPlugin ? (
|
||||
<Text size="small" leading="compact" weight="plus">
|
||||
Store credit refund
|
||||
</Text>
|
||||
) : (
|
||||
<DisplayId id={creditLine.id} />
|
||||
)}
|
||||
</Text>
|
||||
<Text size="small" leading="compact">
|
||||
{format(
|
||||
new Date(creditLine.created_at as string),
|
||||
"dd MMM, yyyy, HH:mm:ss"
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="hidden items-center justify-end sm:flex">
|
||||
<Text size="small" leading="compact" className="capitalize">
|
||||
{prettyReference} ({prettyReferenceId})
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex items-center justify-end">
|
||||
<Text size="small" leading="compact">
|
||||
{getLocaleAmount(creditLine.amount as number, currencyCode)}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const PaymentBreakdown = ({
|
||||
order,
|
||||
payments,
|
||||
refunds,
|
||||
currencyCode,
|
||||
plugins,
|
||||
}: {
|
||||
order: HttpTypes.AdminOrder
|
||||
payments: HttpTypes.AdminPayment[]
|
||||
refunds: HttpTypes.AdminRefund[]
|
||||
currencyCode: string
|
||||
plugins: HttpTypes.AdminPlugin[]
|
||||
}) => {
|
||||
/**
|
||||
* Refunds that are not associated with a payment.
|
||||
*/
|
||||
const orderRefunds = refunds.filter((refund) => refund.payment_id === null)
|
||||
const creditLines = order.credit_lines ?? []
|
||||
const creditLineRefunds = creditLines.filter(
|
||||
(creditLine) => (creditLine.amount as number) < 0
|
||||
)
|
||||
|
||||
const entries = [...orderRefunds, ...payments]
|
||||
const entries = [...orderRefunds, ...payments, ...creditLineRefunds]
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
new Date(a.created_at as string).getTime() -
|
||||
@@ -303,13 +375,20 @@ const PaymentBreakdown = ({
|
||||
)
|
||||
})
|
||||
.map((entry) => {
|
||||
return {
|
||||
event: entry,
|
||||
type: entry.id.startsWith("pay_") ? "payment" : "refund",
|
||||
let type = entry.id.startsWith("pay_") ? "payment" : "refund"
|
||||
|
||||
if (entry.id.startsWith("ordcl_")) {
|
||||
type = "credit_line_refund"
|
||||
}
|
||||
|
||||
return { event: entry, type }
|
||||
}) as (
|
||||
| { type: "payment"; event: HttpTypes.AdminPayment }
|
||||
| { type: "refund"; event: HttpTypes.AdminRefund }
|
||||
| {
|
||||
type: "credit_line_refund"
|
||||
event: OrderCreditLineDTO
|
||||
}
|
||||
)[]
|
||||
|
||||
return (
|
||||
@@ -336,6 +415,15 @@ const PaymentBreakdown = ({
|
||||
currencyCode={currencyCode}
|
||||
/>
|
||||
)
|
||||
case "credit_line_refund":
|
||||
return (
|
||||
<CreditLine
|
||||
key={event.id}
|
||||
creditLine={event}
|
||||
currencyCode={currencyCode}
|
||||
plugins={plugins}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
@@ -347,8 +435,8 @@ const Total = ({ order }: { order: AdminOrder }) => {
|
||||
const totalPending = getTotalPending(order.payment_collections)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<div className="flex flex-col gap-y-4 px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Text size="small" weight="plus" leading="compact">
|
||||
{t("orders.payment.totalPaidByCustomer")}
|
||||
</Text>
|
||||
@@ -362,7 +450,7 @@ const Total = ({ order }: { order: AdminOrder }) => {
|
||||
</div>
|
||||
|
||||
{order.status !== "canceled" && totalPending > 0 && (
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Text size="small" weight="plus" leading="compact">
|
||||
Total pending
|
||||
</Text>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactNode, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, useNavigate } from "react-router-dom"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
import {
|
||||
ArrowDownRightMini,
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
AdminOrderLineItem,
|
||||
AdminOrderPreview,
|
||||
AdminPaymentCollection,
|
||||
AdminPlugin,
|
||||
AdminRegion,
|
||||
AdminReturn,
|
||||
} from "@medusajs/types"
|
||||
@@ -37,7 +38,9 @@ import {
|
||||
} from "@medusajs/ui"
|
||||
|
||||
import { AdminReservation } from "@medusajs/types/src/http"
|
||||
import { format } from "date-fns"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import DisplayId from "../../../../../components/common/display-id/display-id"
|
||||
import { Thumbnail } from "../../../../../components/common/thumbnail"
|
||||
import { useClaims } from "../../../../../hooks/api/claims"
|
||||
import { useExchanges } from "../../../../../hooks/api/exchanges"
|
||||
@@ -46,6 +49,7 @@ import { useMarkPaymentCollectionAsPaid } from "../../../../../hooks/api/payment
|
||||
import { useReservationItems } from "../../../../../hooks/api/reservations"
|
||||
import { useReturns } from "../../../../../hooks/api/returns"
|
||||
import { useDate } from "../../../../../hooks/use-date"
|
||||
import { getTotalCreditLines } from "../../../../../lib/credit-line"
|
||||
import { formatCurrency } from "../../../../../lib/format-currency"
|
||||
import {
|
||||
getLocaleAmount,
|
||||
@@ -53,6 +57,7 @@ import {
|
||||
isAmountLessThenRoundingError,
|
||||
} from "../../../../../lib/money-amount-helpers"
|
||||
import { getTotalCaptured } from "../../../../../lib/payment"
|
||||
import { getLoyaltyPlugin } from "../../../../../lib/plugins"
|
||||
import { getReturnableQuantity } from "../../../../../lib/rma"
|
||||
import { CopyPaymentLink } from "../copy-payment-link/copy-payment-link"
|
||||
import ReturnInfoPopover from "./return-info-popover"
|
||||
@@ -60,9 +65,13 @@ import ShippingInfoPopover from "./shipping-info-popover"
|
||||
|
||||
type OrderSummarySectionProps = {
|
||||
order: AdminOrder
|
||||
plugins: AdminPlugin[]
|
||||
}
|
||||
|
||||
export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => {
|
||||
export const OrderSummarySection = ({
|
||||
order,
|
||||
plugins,
|
||||
}: OrderSummarySectionProps) => {
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
|
||||
@@ -133,8 +142,7 @@ export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => {
|
||||
|
||||
const showPayment =
|
||||
unpaidPaymentCollection && pendingDifference > 0 && isAmountSignificant
|
||||
const showRefund =
|
||||
unpaidPaymentCollection && pendingDifference < 0 && isAmountSignificant
|
||||
const showRefund = pendingDifference < 0 && isAmountSignificant
|
||||
|
||||
const handleMarkAsPaid = async (
|
||||
paymentCollection: AdminPaymentCollection
|
||||
@@ -181,6 +189,7 @@ export const OrderSummarySection = ({ order }: OrderSummarySectionProps) => {
|
||||
<Header order={order} orderPreview={orderPreview} />
|
||||
<ItemBreakdown order={order} reservations={reservations!} />
|
||||
<CostBreakdown order={order} />
|
||||
<CreditLinesBreakdown order={order} plugins={plugins} />
|
||||
<Total order={order} />
|
||||
|
||||
{(showAllocateButton || showReturns || showPayment || showRefund) && (
|
||||
@@ -398,12 +407,7 @@ const Item = ({
|
||||
<div className="flex items-start gap-x-4">
|
||||
<Thumbnail src={item.thumbnail} />
|
||||
<div>
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
weight="plus"
|
||||
className="text-ui-fg-base"
|
||||
>
|
||||
<Text size="small" leading="compact" className="text-ui-fg-base">
|
||||
{item.title}
|
||||
</Text>
|
||||
|
||||
@@ -741,6 +745,109 @@ const CostBreakdown = ({
|
||||
)
|
||||
}
|
||||
|
||||
const CreditLinesBreakdown = ({
|
||||
order,
|
||||
plugins,
|
||||
}: {
|
||||
order: AdminOrder & { region?: AdminRegion | null }
|
||||
plugins: AdminPlugin[]
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isCreditLinesOpen, setIsCreditLinesOpen] = useState(false)
|
||||
const creditLines = order.credit_lines ?? []
|
||||
const loyaltyPlugin = getLoyaltyPlugin(plugins)
|
||||
|
||||
if (creditLines.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-ui-fg-subtle flex flex-col">
|
||||
<>
|
||||
<div
|
||||
onClick={() => setIsCreditLinesOpen((o) => !o)}
|
||||
className="bg-ui-bg-component flex cursor-pointer items-center justify-between border border-dashed px-6 py-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<TriangleDownMini
|
||||
style={{
|
||||
transform: `rotate(${isCreditLinesOpen ? 0 : -90}deg)`,
|
||||
}}
|
||||
/>
|
||||
<span className="text-ui-fg-muted txt-small select-none">
|
||||
{loyaltyPlugin
|
||||
? t("orders.giftCardsStoreCreditLines")
|
||||
: t("orders.creditLines.title")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text size="small" leading="compact">
|
||||
{getLocaleAmount(order.credit_line_total, order.currency_code)}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isCreditLinesOpen && (
|
||||
<div className="flex flex-col">
|
||||
{creditLines.map((creditLine) => {
|
||||
const prettyReference = creditLine.reference
|
||||
?.split("_")
|
||||
.join(" ")
|
||||
.split("-")
|
||||
.join(" ")
|
||||
|
||||
const prettyReferenceId = creditLine.reference_id ? (
|
||||
<DisplayId id={creditLine.reference_id} />
|
||||
) : null
|
||||
|
||||
return (
|
||||
<div
|
||||
className="text-ui-fg-subtle grid grid-cols-[1fr_1fr_1fr] items-center px-6 py-4 py-4 sm:grid-cols-[1fr_1fr_1fr]"
|
||||
key={creditLine.id}
|
||||
>
|
||||
<div className="w-full min-w-[60px] overflow-hidden">
|
||||
<Text
|
||||
size="small"
|
||||
leading="compact"
|
||||
weight="plus"
|
||||
className="truncate"
|
||||
>
|
||||
<DisplayId id={creditLine.id} />
|
||||
</Text>
|
||||
|
||||
<Text size="small" leading="compact">
|
||||
{format(
|
||||
new Date(creditLine.created_at),
|
||||
"dd MMM, yyyy, HH:mm:ss"
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="hidden items-center justify-end gap-x-2 sm:flex">
|
||||
<Text size="small" leading="compact" className="capitalize">
|
||||
{prettyReference} ({prettyReferenceId})
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end">
|
||||
<Text size="small" leading="compact">
|
||||
{getLocaleAmount(
|
||||
creditLine.amount as number,
|
||||
order.currency_code
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const InventoryKitBreakdown = ({ item }: { item: AdminOrderLineItem }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -1028,39 +1135,19 @@ const Total = ({ order }: { order: AdminOrder }) => {
|
||||
return (
|
||||
<div className=" flex flex-col gap-y-2 px-6 py-4">
|
||||
<div className="text-ui-fg-base flex items-center justify-between">
|
||||
<Text
|
||||
weight="plus"
|
||||
className="text-ui-fg-subtle"
|
||||
size="small"
|
||||
leading="compact"
|
||||
>
|
||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
||||
{t("fields.total")}
|
||||
</Text>
|
||||
<Text
|
||||
weight="plus"
|
||||
className="text-ui-fg-subtle"
|
||||
size="small"
|
||||
leading="compact"
|
||||
>
|
||||
{getStylizedAmount(order.total, order.currency_code)}
|
||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
||||
{getStylizedAmount(order.original_total, order.currency_code)}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="text-ui-fg-base flex items-center justify-between">
|
||||
<Text
|
||||
weight="plus"
|
||||
className="text-ui-fg-subtle"
|
||||
size="small"
|
||||
leading="compact"
|
||||
>
|
||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
||||
{t("fields.paidTotal")}
|
||||
</Text>
|
||||
<Text
|
||||
weight="plus"
|
||||
className="text-ui-fg-subtle"
|
||||
size="small"
|
||||
leading="compact"
|
||||
>
|
||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
||||
{getStylizedAmount(
|
||||
getTotalCaptured(order.payment_collections || []),
|
||||
order.currency_code
|
||||
@@ -1068,12 +1155,24 @@ const Total = ({ order }: { order: AdminOrder }) => {
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="text-ui-fg-base flex items-center justify-between">
|
||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
||||
{t("fields.creditTotal")}
|
||||
</Text>
|
||||
|
||||
<Text className="text-ui-fg-subtle" size="small" leading="compact">
|
||||
{getStylizedAmount(
|
||||
getTotalCreditLines(order.credit_lines ?? []),
|
||||
order.currency_code
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="text-ui-fg-base flex items-center justify-between">
|
||||
<Text
|
||||
className="text-ui-fg-subtle text-semibold"
|
||||
size="small"
|
||||
leading="compact"
|
||||
weight="plus"
|
||||
>
|
||||
{t("orders.returns.outstandingAmount")}
|
||||
</Text>
|
||||
@@ -1081,7 +1180,6 @@ const Total = ({ order }: { order: AdminOrder }) => {
|
||||
className="text-ui-fg-subtle text-bold"
|
||||
size="small"
|
||||
leading="compact"
|
||||
weight="plus"
|
||||
>
|
||||
{getStylizedAmount(
|
||||
order.summary.pending_difference || 0,
|
||||
|
||||
@@ -9,8 +9,10 @@ const DEFAULT_PROPERTIES = [
|
||||
"metadata",
|
||||
// --- TOTALS ---
|
||||
"total",
|
||||
"credit_line_total",
|
||||
"item_total",
|
||||
"shipping_subtotal",
|
||||
"original_total",
|
||||
"subtotal",
|
||||
"discount_total",
|
||||
"discount_subtotal",
|
||||
@@ -36,6 +38,7 @@ const DEFAULT_RELATIONS = [
|
||||
"*sales_channel",
|
||||
"*promotion",
|
||||
"*shipping_methods",
|
||||
"*credit_lines",
|
||||
"*fulfillments",
|
||||
"+fulfillments.shipping_option.service_zone.fulfillment_set.type",
|
||||
"*fulfillments.items",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useLoaderData, useParams } from "react-router-dom"
|
||||
import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
|
||||
import { TwoColumnPage } from "../../../components/layout/pages"
|
||||
import { useOrder, useOrderPreview } from "../../../hooks/api/orders"
|
||||
import { usePlugins } from "../../../hooks/api/plugins"
|
||||
import { useExtension } from "../../../providers/extension-provider"
|
||||
import { ActiveOrderClaimSection } from "./components/active-order-claim-section"
|
||||
import { ActiveOrderExchangeSection } from "./components/active-order-exchange-section"
|
||||
@@ -22,6 +23,7 @@ export const OrderDetail = () => {
|
||||
|
||||
const { id } = useParams()
|
||||
const { getWidgets } = useExtension()
|
||||
const { plugins = [] } = usePlugins()
|
||||
|
||||
const { order, isLoading, isError, error } = useOrder(
|
||||
id!,
|
||||
@@ -81,8 +83,8 @@ export const OrderDetail = () => {
|
||||
<ActiveOrderExchangeSection orderPreview={orderPreview!} />
|
||||
<ActiveOrderReturnSection orderPreview={orderPreview!} />
|
||||
<OrderGeneralSection order={order} />
|
||||
<OrderSummarySection order={order} />
|
||||
<OrderPaymentSection order={order} />
|
||||
<OrderSummarySection order={order} plugins={plugins} />
|
||||
<OrderPaymentSection order={order} plugins={plugins} />
|
||||
<OrderFulfillmentSection order={order} />
|
||||
</TwoColumnPage.Main>
|
||||
<TwoColumnPage.Sidebar>
|
||||
|
||||
Reference in New Issue
Block a user