diff --git a/.changeset/sixty-kangaroos-cheer.md b/.changeset/sixty-kangaroos-cheer.md
new file mode 100644
index 00000000000..dcc83bdc3c1
--- /dev/null
+++ b/.changeset/sixty-kangaroos-cheer.md
@@ -0,0 +1,5 @@
+---
+"saleor-dashboard": minor
+---
+
+Implement refunds datagrid on order details
diff --git a/.featureFlags/generated.tsx b/.featureFlags/generated.tsx
index 4089ebdef61..1d72dcddd74 100644
--- a/.featureFlags/generated.tsx
+++ b/.featureFlags/generated.tsx
@@ -1,13 +1,16 @@
// @ts-nocheck
-import L10838 from "./images/discounts-list.png"
-import W33914 from "./images/filters.png"
+import E60240 from "./images/discounts-list.png"
+import T97919 from "./images/filters.png"
-const discounts_rules = () => (<>

+const discounts_rules = () => (<>
Apply the new discounts rules to narrow your promotions audience.
Set up conditions and channels that must be fulfilled to apply defined reward.
>)
-const product_filters = () => (<>
+const improved_refunds = () => (<>Experience new refund flow supporting multiple transactions.
+
+>)
+const product_filters = () => (<>
Experience the new look and enhanced abilities of new fitering mechanism.
Easily combine any criteria you want, and quickly browse their values.
>)
@@ -21,6 +24,15 @@ export const AVAILABLE_FLAGS = [{
enabled: false,
payload: "default",
}
+},{
+ name: "improved_refunds",
+ displayName: "Improved refunds",
+ component: improved_refunds,
+ visible: false,
+ content: {
+ enabled: false,
+ payload: "default",
+ }
},{
name: "product_filters",
displayName: "Products filtering",
diff --git a/.featureFlags/improved-refunds.md b/.featureFlags/improved-refunds.md
new file mode 100644
index 00000000000..c27b8610149
--- /dev/null
+++ b/.featureFlags/improved-refunds.md
@@ -0,0 +1,11 @@
+---
+name: improved_refunds
+displayName: Improved refunds
+enabled: false
+payload: "default"
+visible: false
+---
+
+Experience new refund flow supporting multiple transactions.
+
+
diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json
index 19ee5b30adf..0082f778f61 100644
--- a/locale/defaultMessages.json
+++ b/locale/defaultMessages.json
@@ -741,6 +741,10 @@
"context": "delete app",
"string": "Deleting this app, you will remove installation of the app. If you are paying for app subscription, remember to unsubscribe from the app in Saleor Marketplace."
},
+ "2v3tZ2": {
+ "context": "refund datagrid reason column",
+ "string": "Reason"
+ },
"2x9ZgK": {
"context": "card title",
"string": "Refund balance"
@@ -983,6 +987,10 @@
"context": "modal button",
"string": "Upload URL"
},
+ "4XRIV5": {
+ "context": "add new refund button",
+ "string": "Add new refund"
+ },
"4XhJY+": {
"context": "dialog header",
"string": "Add product from {channelName}"
@@ -1473,6 +1481,10 @@
"context": "default tax class name for new tax classes",
"string": "New tax class"
},
+ "8DyXiQ": {
+ "context": "refund datagrid status column",
+ "string": "Status"
+ },
"8EGagh": {
"context": "search box label",
"string": "Filter Countries"
@@ -3258,6 +3270,10 @@
"context": "select product informations to be exported",
"string": "Information exported:"
},
+ "JxWKvr": {
+ "context": "refund datagrid account column",
+ "string": "Account"
+ },
"JyQoES": {
"context": "delete attribute value",
"string": "Are you sure you want to delete \"{name}\" value?"
@@ -4977,6 +4993,10 @@
"context": "warehouse input",
"string": "Warehouse"
},
+ "W6VS7F": {
+ "context": "refund datagrid amount column",
+ "string": "Amount"
+ },
"W6nwjo": {
"string": "Draft"
},
@@ -7778,6 +7798,10 @@
"context": "order status",
"string": "Fulfilled"
},
+ "pnPj0b": {
+ "context": "refund datagrid date column",
+ "string": "Date"
+ },
"ppLwx3": {
"context": "number of categories",
"string": "Categories ({quantity})"
@@ -8292,6 +8316,10 @@
"context": "balance curreny missing error message",
"string": "Balance currency is missing"
},
+ "tZmVfa": {
+ "context": "refund section header",
+ "string": "Refund"
+ },
"tZnV8L": {
"context": "Header row stock label",
"string": "Stock"
diff --git a/src/config.ts b/src/config.ts
index cef1d9e944d..f180c96464c 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -68,7 +68,10 @@ export interface AppListViewSettings {
[ListViews.ORDER_DRAFT_DETAILS_LIST]: ListSettings;
[ListViews.PRODUCT_DETAILS]: ListSettings;
[ListViews.VOUCHER_CODES]: ListSettings;
+ [ListViews.ORDER_REFUNDS]: ListSettings;
}
+// TODO: replace with
+// type AppListViewSettings = Record;
export const defaultListSettings: AppListViewSettings = {
[ListViews.APPS_LIST]: {
@@ -194,6 +197,10 @@ export const defaultListSettings: AppListViewSettings = {
[ListViews.VOUCHER_CODES]: {
rowNumber: PAGINATE_BY,
},
+ [ListViews.ORDER_REFUNDS]: {
+ rowNumber: PAGINATE_BY,
+ columns: ["status", "amount", "reason", "date", "account"],
+ },
};
export const APP_VERSION =
diff --git a/src/orders/components/OrderPaymentOrTransaction/OrderPaymentOrTransaction.test.tsx b/src/orders/components/OrderPaymentOrTransaction/OrderPaymentOrTransaction.test.tsx
index b15d6bd0222..3d7fd514df7 100644
--- a/src/orders/components/OrderPaymentOrTransaction/OrderPaymentOrTransaction.test.tsx
+++ b/src/orders/components/OrderPaymentOrTransaction/OrderPaymentOrTransaction.test.tsx
@@ -52,6 +52,10 @@ jest.mock("react-router-dom", () => ({
Link: jest.fn(({ to, ...props }) => ),
}));
+jest.mock("@dashboard/featureFlags", () => ({
+ useFlag: jest.fn(() => ({ enabled: false })),
+}));
+
describe("OrderPaymentOrTransaction", () => {
const order = orderFixture(undefined);
const sharedProps = {
diff --git a/src/orders/components/OrderPaymentOrTransaction/OrderTransactionsWrapper.tsx b/src/orders/components/OrderPaymentOrTransaction/OrderTransactionsWrapper.tsx
index 02da93e21f3..f9da7fff908 100644
--- a/src/orders/components/OrderPaymentOrTransaction/OrderTransactionsWrapper.tsx
+++ b/src/orders/components/OrderPaymentOrTransaction/OrderTransactionsWrapper.tsx
@@ -1,5 +1,6 @@
// @ts-strict-ignore
import CardSpacer from "@dashboard/components/CardSpacer";
+import { useFlag } from "@dashboard/featureFlags";
import {
OrderDetailsFragment,
OrderDetailsQuery,
@@ -11,6 +12,7 @@ import OrderAddTransaction from "../OrderAddTransaction";
import { useStyles } from "../OrderDetailsPage/styles";
import OrderGrantedRefunds from "../OrderGrantedRefunds";
import OrderPaymentSummaryCard from "../OrderPaymentSummaryCard";
+import { OrderRefundDatagrid } from "../OrderRefundDatagrid";
import OrderSummaryCard from "../OrderSummaryCard";
import OrderTransaction from "../OrderTransaction";
import OrderTransactionGiftCard from "../OrderTransactionGiftCard";
@@ -45,6 +47,9 @@ export const OrderTransactionsWrapper: React.FC = ({
() => getFilteredPayments(order),
[order],
);
+
+ const { enabled } = useFlag("improved_refunds");
+
return (
<>
@@ -52,12 +57,23 @@ export const OrderTransactionsWrapper: React.FC = ({
- {order?.grantedRefunds?.length !== 0 ? (
- <>
-
-
- >
- ) : null}
+ <>
+ {enabled && (
+ <>
+
+
+ >
+ )}
+ {order?.grantedRefunds?.length !== 0 && !enabled && (
+ <>
+
+
+ >
+ )}
+ >
{order?.transactions?.map(transaction => (
= ({
+ grantedRefunds,
+ orderId,
+}) => {
+ const { datagrid, currentTheme, settings, handleColumnChange } =
+ useDatagridOpts(ListViews.ORDER_REFUNDS);
+
+ const orderDraftDetailsStaticColumns = useOrderRefundStaticColumns();
+
+ const {
+ handlers,
+ visibleColumns,
+ staticColumns,
+ selectedColumns,
+ recentlyAddedColumn,
+ } = useColumns({
+ staticColumns: orderDraftDetailsStaticColumns,
+ selectedColumns: settings?.columns ?? [],
+ onSave: handleColumnChange,
+ });
+
+ const getCellContent = createGetCellContent({
+ columns: visibleColumns,
+ refunds: grantedRefunds,
+ currentTheme,
+ });
+
+ const getMenuItems = React.useCallback(
+ index => [
+ {
+ label: "",
+ Icon: (
+
+
+
+ ),
+ onSelect: () => false,
+ },
+ ],
+ [],
+ );
+
+ return (
+
+
+
+
+
+ {/** TODO: Add modal */}
+
+
+
+ col > 1}
+ availableColumns={visibleColumns}
+ emptyText={""}
+ getCellContent={getCellContent}
+ getCellError={() => false}
+ rows={grantedRefunds.length}
+ selectionActions={() => null}
+ onColumnResize={handlers.onResize}
+ onColumnMoved={handlers.onMove}
+ recentlyAddedColumn={recentlyAddedColumn}
+ renderColumnPicker={() => (
+
+ )}
+ />
+
+
+ );
+};
diff --git a/src/orders/components/OrderRefundDatagrid/datagrid.test.ts b/src/orders/components/OrderRefundDatagrid/datagrid.test.ts
new file mode 100644
index 00000000000..27194e4f3f9
--- /dev/null
+++ b/src/orders/components/OrderRefundDatagrid/datagrid.test.ts
@@ -0,0 +1,149 @@
+import { grantedRefunds } from "@dashboard/orders/fixtures";
+import { GridCellKind } from "@glideapps/glide-data-grid";
+
+import { createGetCellContent, useOrderRefundStaticColumns } from "./datagrid";
+
+const currentTheme = "defaultLight";
+
+jest.mock("react-intl", () => ({
+ useIntl: () => ({
+ formatMessage: ({ defaultMessage }: { defaultMessage: string }) =>
+ defaultMessage,
+ }),
+ defineMessages: jest.fn(x => x),
+}));
+
+jest.mock("@dashboard/components/Datagrid/hooks/useEmptyColumn", () => ({
+ useEmptyColumn: () => ({
+ id: "empty",
+ title: "",
+ width: 20,
+ }),
+}));
+
+describe("Order refund datagrid", () => {
+ it("presents grant refund with status draft created by user", () => {
+ // Arrange
+ const refunds = grantedRefunds;
+ const columns = useOrderRefundStaticColumns();
+
+ // Act
+ const getCellContent = createGetCellContent({
+ refunds,
+ columns,
+ currentTheme,
+ });
+
+ // Assert
+
+ // Status column
+ expect(getCellContent([1, 1])).toEqual(
+ expect.objectContaining({
+ data: {
+ kind: "tags-cell",
+ possibleTags: [{ tag: "DRAFT", color: "#ffe6c8" }],
+ tags: ["DRAFT"],
+ },
+ }),
+ );
+
+ // Amount column
+ expect(getCellContent([2, 1])).toEqual(
+ expect.objectContaining({
+ data: {
+ kind: "money-cell",
+ value: 234.93,
+ currency: "USD",
+ },
+ }),
+ );
+
+ // Reason column
+ expect(getCellContent([3, 1])).toEqual(
+ expect.objectContaining({
+ data: "Products arrived damaged",
+ kind: GridCellKind.Text,
+ }),
+ );
+
+ // Date column
+ expect(getCellContent([4, 1])).toEqual(
+ expect.objectContaining({
+ data: {
+ kind: "date-cell",
+ value: "2022-08-22T10:40:22.226875+00:00",
+ },
+ }),
+ );
+
+ // Account column
+ expect(getCellContent([5, 1])).toEqual(
+ expect.objectContaining({
+ data: "john.doe@example.com",
+ kind: GridCellKind.Text,
+ }),
+ );
+ });
+ it("presents grant refund with status draft created by app", () => {
+ // Arrange
+ const refunds = grantedRefunds;
+ const columns = useOrderRefundStaticColumns();
+
+ // Act
+ const getCellContent = createGetCellContent({
+ refunds,
+ columns,
+ currentTheme,
+ });
+
+ // Assert
+
+ // Status column
+ expect(getCellContent([1, 0])).toEqual(
+ expect.objectContaining({
+ data: {
+ kind: "tags-cell",
+ possibleTags: [{ tag: "DRAFT", color: "#ffe6c8" }],
+ tags: ["DRAFT"],
+ },
+ }),
+ );
+
+ // Amount column
+ expect(getCellContent([2, 0])).toEqual(
+ expect.objectContaining({
+ data: {
+ kind: "money-cell",
+ value: 234.93,
+ currency: "USD",
+ },
+ }),
+ );
+
+ // Reason column
+ expect(getCellContent([3, 0])).toEqual(
+ expect.objectContaining({
+ data: "Products returned",
+ kind: GridCellKind.Text,
+ }),
+ );
+
+ // Date column
+ expect(getCellContent([4, 0])).toEqual(
+ expect.objectContaining({
+ data: {
+ kind: "date-cell",
+ value: "2022-08-22T10:40:22.226875+00:00",
+ },
+ }),
+ );
+
+ // Account column
+ expect(getCellContent([5, 0])).toEqual(
+ expect.objectContaining({
+ data: "",
+ kind: GridCellKind.Text,
+ }),
+ );
+ });
+});
diff --git a/src/orders/components/OrderRefundDatagrid/datagrid.ts b/src/orders/components/OrderRefundDatagrid/datagrid.ts
new file mode 100644
index 00000000000..0cccbccd7f1
--- /dev/null
+++ b/src/orders/components/OrderRefundDatagrid/datagrid.ts
@@ -0,0 +1,137 @@
+import {
+ dateCell,
+ moneyCell,
+ readonlyTextCell,
+ tagsCell,
+} from "@dashboard/components/Datagrid/customCells/cells";
+import {
+ UseDatagridChangeState,
+ useDatagridChangeState,
+} from "@dashboard/components/Datagrid/hooks/useDatagridChange";
+import { useEmptyColumn } from "@dashboard/components/Datagrid/hooks/useEmptyColumn";
+import { AvailableColumn } from "@dashboard/components/Datagrid/types";
+import { OrderDetailsFragment } from "@dashboard/graphql";
+import useListSettings from "@dashboard/hooks/useListSettings";
+import { getStatusColor } from "@dashboard/misc";
+import { ListSettings, ListViews } from "@dashboard/types";
+import { GridCell, Item } from "@glideapps/glide-data-grid";
+import { DefaultTheme, useTheme } from "@saleor/macaw-ui-next";
+import React from "react";
+import { useIntl } from "react-intl";
+
+import { refundGridMessages } from "./messages";
+
+const useOrderRefundConstantColumns = () => {
+ const intl = useIntl();
+ return [
+ {
+ id: "status",
+ title: intl.formatMessage(refundGridMessages.statusCell),
+ width: 80,
+ },
+ {
+ id: "amount",
+ title: intl.formatMessage(refundGridMessages.amountCell),
+ width: 150,
+ },
+ {
+ id: "reason",
+ title: intl.formatMessage(refundGridMessages.reasonCell),
+ width: 300,
+ },
+ {
+ id: "date",
+ title: intl.formatMessage(refundGridMessages.dateCell),
+ width: 300,
+ },
+ {
+ id: "account",
+ title: intl.formatMessage(refundGridMessages.accountCell),
+ width: 300,
+ },
+ ];
+};
+
+export const useOrderRefundStaticColumns = () => {
+ const emptyColumn = useEmptyColumn();
+ const constantColumns = useOrderRefundConstantColumns();
+ return [emptyColumn, ...constantColumns];
+};
+
+export const createGetCellContent =
+ ({
+ refunds,
+ columns,
+ currentTheme,
+ }: {
+ refunds: OrderDetailsFragment["grantedRefunds"] | undefined;
+ columns: AvailableColumn[];
+ currentTheme: DefaultTheme;
+ }) =>
+ ([column, row]: Item): GridCell => {
+ const rowData = refunds?.[row];
+ const columnId = columns[column]?.id;
+
+ if (!columnId || !rowData) {
+ return readonlyTextCell("");
+ }
+
+ // TODO: replace with actual status when API available
+ const color = getStatusColor({
+ status: "generic",
+ currentTheme,
+ });
+
+ switch (columnId) {
+ case "status":
+ return tagsCell(
+ [
+ {
+ tag: "DRAFT",
+ color: color.base,
+ },
+ ],
+ ["DRAFT"],
+ );
+ case "amount":
+ return moneyCell(rowData.amount.amount, rowData.amount.currency ?? "", {
+ readonly: true,
+ });
+ case "reason":
+ return readonlyTextCell(rowData.reason ?? "", false);
+ case "date":
+ return dateCell(rowData.createdAt);
+ case "account":
+ return readonlyTextCell(rowData.user?.email ?? "", false);
+ default:
+ return readonlyTextCell("");
+ }
+ };
+
+export const useDatagridOpts = (
+ view: ListViews,
+): {
+ datagrid: UseDatagridChangeState;
+ currentTheme: DefaultTheme;
+ settings: ListSettings;
+ handleColumnChange: (picked: string[]) => void;
+} => {
+ const datagrid = useDatagridChangeState();
+ const { theme: currentTheme } = useTheme();
+ const { updateListSettings, settings } = useListSettings(view);
+
+ const handleColumnChange = React.useCallback(
+ picked => {
+ if (updateListSettings) {
+ updateListSettings("columns", picked.filter(Boolean));
+ }
+ },
+ [updateListSettings],
+ );
+ return {
+ datagrid,
+ currentTheme,
+ settings,
+ handleColumnChange,
+ };
+};
diff --git a/src/orders/components/OrderRefundDatagrid/index.ts b/src/orders/components/OrderRefundDatagrid/index.ts
new file mode 100644
index 00000000000..d9b51044520
--- /dev/null
+++ b/src/orders/components/OrderRefundDatagrid/index.ts
@@ -0,0 +1 @@
+export * from "./OrderRefundDatagrid";
diff --git a/src/orders/components/OrderRefundDatagrid/messages.ts b/src/orders/components/OrderRefundDatagrid/messages.ts
new file mode 100644
index 00000000000..6b70b0787eb
--- /dev/null
+++ b/src/orders/components/OrderRefundDatagrid/messages.ts
@@ -0,0 +1,39 @@
+import { defineMessages } from "react-intl";
+
+export const refundGridMessages = defineMessages({
+ statusCell: {
+ defaultMessage: "Status",
+ id: "8DyXiQ",
+ description: "refund datagrid status column",
+ },
+ amountCell: {
+ defaultMessage: "Amount",
+ id: "W6VS7F",
+ description: "refund datagrid amount column",
+ },
+ reasonCell: {
+ defaultMessage: "Reason",
+ id: "2v3tZ2",
+ description: "refund datagrid reason column",
+ },
+ dateCell: {
+ defaultMessage: "Date",
+ id: "pnPj0b",
+ description: "refund datagrid date column",
+ },
+ accountCell: {
+ defaultMessage: "Account",
+ id: "JxWKvr",
+ description: "refund datagrid account column",
+ },
+ addNewRefund: {
+ defaultMessage: "Add new refund",
+ id: "4XRIV5",
+ description: "add new refund button",
+ },
+ refundSection: {
+ defaultMessage: "Refund",
+ id: "tZmVfa",
+ description: "refund section header",
+ },
+});
diff --git a/src/types.ts b/src/types.ts
index 891c5086121..48e326f0382 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -50,6 +50,7 @@ export enum ListViews {
// Not strictly a list view, but there's a list of variants
PRODUCT_DETAILS = "PRODUCT_DETAILS",
VOUCHER_CODES = "VOUCHER_CODES",
+ ORDER_REFUNDS = "ORDER_REFUNDS",
}
export interface ListProps {