Skip to content

Commit 4036943

Browse files
authored
Add Net column to orders list alongside gross Total (#6704)
Show post-discount product value (subtotal.net) as a new optional column defaulting before Total, with right-aligned money headers and column-order normalization when toggled via the picker.
1 parent f26158a commit 4036943

18 files changed

Lines changed: 392 additions & 9 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"saleor-dashboard": patch
3+
---
4+
5+
Added a **Net** column to the orders list showing post-discount product value (excluding tax and shipping), placed before the existing **Total** column. The Net column is shown by default for new layouts and can be enabled from the column picker for customized views.

locale/defaultMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10715,6 +10715,10 @@
1071510715
"context": "expand refunded line sub-rows",
1071610716
"string": "Show all {count} lines"
1071710717
},
10718+
"rU2b3o": {
10719+
"context": "orders list column: net product value (excludes tax and shipping)",
10720+
"string": "Net"
10721+
},
1071810722
"rVIlBs": {
1071910723
"context": "page header with order number",
1072010724
"string": "Order #{orderNumber}"

src/components/Datagrid/ColumnPicker/useColumns.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,39 @@ describe("useColumns", () => {
198198
},
199199
]);
200200
});
201+
it("should apply mapColumnsOnSave before calling onSave", () => {
202+
// Arrange
203+
const mapColumnsOnSave = (columns: string[]) => [...columns].reverse();
204+
const { result } = renderHook(() =>
205+
useColumns({
206+
staticColumns: mockedColumns,
207+
selectedColumns: ["name"],
208+
onSave,
209+
mapColumnsOnSave,
210+
}),
211+
);
212+
213+
// Act
214+
act(() => result.current.handlers.onToggle("description"));
215+
216+
// Assert
217+
expect(onSave).toHaveBeenCalledWith(["name", "description"]);
218+
});
219+
it("should apply mapColumnsOnSave to selected columns", () => {
220+
// Arrange
221+
const mapColumnsOnSave = (columns: string[]) => [...columns].reverse();
222+
const { result } = renderHook(() =>
223+
useColumns({
224+
staticColumns: mockedColumns,
225+
selectedColumns: ["name", "description"],
226+
onSave,
227+
mapColumnsOnSave,
228+
}),
229+
);
230+
231+
// Assert
232+
expect(result.current.selectedColumns).toEqual(["description", "name"]);
233+
});
201234
it("should call onSave when column is toggled", () => {
202235
// Arrange
203236
const { result } = renderHook(() =>

src/components/Datagrid/ColumnPicker/useColumns.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ interface UseColumnsProps {
4141
columnCategories?: ColumnCategory[];
4242
selectedColumns: string[];
4343
onSave: (columns: string[]) => void;
44+
mapColumnsOnSave?: (columns: string[]) => string[];
4445
}
4546

4647
export const useColumns = ({
@@ -49,11 +50,16 @@ export const useColumns = ({
4950
selectedColumns: _selectedColumns,
5051
columnCategories,
5152
onSave: handleSave,
53+
mapColumnsOnSave,
5254
}: UseColumnsProps) => {
5355
const [dynamicColumns, updateDynamicColumns] = useState<AvailableColumn[] | null>(null);
5456

5557
const { columns: persistedColumns, update } = usePersistance(gridName);
56-
const selectedColumns = selectedWithPersistance(_selectedColumns, persistedColumns);
58+
const selectedColumns = useMemo(() => {
59+
const columns = selectedWithPersistance(_selectedColumns, persistedColumns);
60+
61+
return mapColumnsOnSave ? mapColumnsOnSave(columns) : columns;
62+
}, [_selectedColumns, persistedColumns, mapColumnsOnSave]);
5763

5864
// Dynamic columns are loaded from the API, thus they need to be updated
5965
// after query resolves with data. Then we also sort them by order of addition
@@ -74,7 +80,8 @@ export const useColumns = ({
7480
const [visibleColumns, setVisibleColumns] = useStateFromProps(initialColumnsState);
7581

7682
const onSave = (columnsIds: string[]) => {
77-
const columns = columnsIds.map(columnId => {
83+
const normalizedColumnIds = mapColumnsOnSave ? mapColumnsOnSave(columnsIds) : columnsIds;
84+
const columns = normalizedColumnIds.map(columnId => {
7885
const persistentEquivalent = persistedColumns.find(
7986
persistedColumn => persistedColumn.identifier() === columnId,
8087
);
@@ -87,7 +94,7 @@ export const useColumns = ({
8794
});
8895

8996
update(columns);
90-
handleSave(columnsIds);
97+
handleSave(normalizedColumnIds);
9198
};
9299

93100
const handleVisibleColumnsChange = (currentColumns: (AvailableColumn | undefined)[]) => {

src/components/Datagrid/Datagrid.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import DataEditor, {
99
type DataEditorRef,
1010
type DrawHeaderCallback,
1111
type EditableGridCell,
12+
getMiddleCenterBias,
1213
type GridCell,
1314
type GridColumn,
1415
type GridSelection,
@@ -388,6 +389,8 @@ export const Datagrid = ({
388389
const drawHeader: DrawHeaderCallback = useCallback(
389390
args => {
390391
const { ctx, rect, isSelected, spriteManager, theme, column } = args;
392+
const columnMeta = availableColumns.find(col => col.id === column.id);
393+
const isRightAligned = columnMeta?.headerAlign === "right";
391394

392395
if (isSelected) {
393396
ctx.fillStyle = themeValues.colors.background.default1;
@@ -403,9 +406,41 @@ export const Datagrid = ({
403406
spriteManager.drawSprite("gripVertical", "normal", ctx, x, y, iconSize, theme);
404407
}
405408

409+
if (isRightAligned && column.id !== "empty") {
410+
const xPad = theme.cellHorizontalPadding;
411+
const gripReserved = isSelected ? 24 : 0;
412+
const drawX = rect.x + rect.width - xPad - gripReserved;
413+
const font = `${theme.headerFontStyle} ${theme.fontFamily}`;
414+
415+
ctx.font = font;
416+
ctx.fillStyle = isSelected ? theme.textHeaderSelected : theme.textHeader;
417+
418+
const textY = rect.y + rect.height / 2 + getMiddleCenterBias(ctx, font);
419+
420+
ctx.textAlign = "right";
421+
ctx.fillText(column.title, drawX, textY);
422+
ctx.textAlign = "left";
423+
424+
if (column.icon !== undefined) {
425+
const headerSize = theme.headerIconSize;
426+
427+
spriteManager.drawSprite(
428+
column.icon,
429+
isSelected ? "selected" : "normal",
430+
ctx,
431+
rect.x + xPad,
432+
rect.y + (rect.height - headerSize) / 2,
433+
headerSize,
434+
theme,
435+
);
436+
}
437+
438+
return true;
439+
}
440+
406441
return false;
407442
},
408-
[themeValues],
443+
[themeValues, availableColumns],
409444
);
410445
const handleRemoveRows = useCallback(
411446
(rows: number[]) => {

src/components/Datagrid/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export interface AvailableColumn {
1111
icon?: string;
1212
themeOverride?: Partial<Theme>;
1313
action?: (id: string) => boolean;
14+
headerAlign?: "left" | "right";
1415
}

src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export const defaultListSettings: AppListViewSettings = {
129129
},
130130
[ListViews.ORDER_LIST]: {
131131
rowNumber: PAGINATE_BY,
132-
columns: ["number", "date", "customer", "payment", "status", "total", "channel"],
132+
columns: ["number", "date", "customer", "payment", "status", "net", "total", "channel"],
133133
},
134134
[ListViews.PAGES_LIST]: {
135135
rowNumber: PAGINATE_BY,

src/graphql/hooks.generated.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13467,6 +13467,14 @@ export const OrderListDocument = gql`
1346713467
number
1346813468
paymentStatus
1346913469
status
13470+
subtotal {
13471+
__typename
13472+
net {
13473+
__typename
13474+
amount
13475+
currency
13476+
}
13477+
}
1347013478
total {
1347113479
__typename
1347213480
gross {

src/graphql/types.generated.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12591,7 +12591,7 @@ export type OrderListQueryVariables = Exact<{
1259112591
}>;
1259212592

1259312593

12594-
export type OrderListQuery = { __typename: 'Query', orders: { __typename: 'OrderCountableConnection', edges: Array<{ __typename: 'OrderCountableEdge', node: { __typename: 'Order', created: any, id: string, number: string, paymentStatus: PaymentChargeStatusEnum, status: OrderStatus, userEmail: string | null, chargeStatus: OrderChargeStatusEnum, billingAddress: { __typename: 'Address', city: string, cityArea: string, companyName: string, countryArea: string, firstName: string, id: string, lastName: string, phone: string | null, postalCode: string, streetAddress1: string, streetAddress2: string, country: { __typename: 'CountryDisplay', code: string, country: string } } | null, channel: { __typename: 'Channel', name: string, id: string }, total: { __typename: 'TaxedMoney', gross: { __typename: 'Money', amount: number, currency: string } } } }>, pageInfo: { __typename: 'PageInfo', hasPreviousPage: boolean, hasNextPage: boolean, startCursor: string | null, endCursor: string | null } } | null };
12594+
export type OrderListQuery = { __typename: 'Query', orders: { __typename: 'OrderCountableConnection', edges: Array<{ __typename: 'OrderCountableEdge', node: { __typename: 'Order', created: any, id: string, number: string, paymentStatus: PaymentChargeStatusEnum, status: OrderStatus, userEmail: string | null, chargeStatus: OrderChargeStatusEnum, billingAddress: { __typename: 'Address', city: string, cityArea: string, companyName: string, countryArea: string, firstName: string, id: string, lastName: string, phone: string | null, postalCode: string, streetAddress1: string, streetAddress2: string, country: { __typename: 'CountryDisplay', code: string, country: string } } | null, channel: { __typename: 'Channel', name: string, id: string }, subtotal: { __typename: 'TaxedMoney', net: { __typename: 'Money', amount: number, currency: string } }, total: { __typename: 'TaxedMoney', gross: { __typename: 'Money', amount: number, currency: string } } } }>, pageInfo: { __typename: 'PageInfo', hasPreviousPage: boolean, hasNextPage: boolean, startCursor: string | null, endCursor: string | null } } | null };
1259512595

1259612596
export type OrderDraftListQueryVariables = Exact<{
1259712597
first?: InputMaybe<Scalars['Int']['input']>;

src/orders/components/OrderListDatagrid/OrderListDatagrid.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const meta: Meta<typeof OrderListDatagrid> = {
1616
sort: { sort: "number" as any, asc: true },
1717
onSort: fn(),
1818
settings: {
19-
columns: ["number", "date", "customer", "payment", "status", "total", "channel"],
19+
columns: ["number", "date", "customer", "payment", "status", "net", "total", "channel"],
2020
rowsPerPage: 20,
2121
},
2222
onUpdateListSettings: fn(),

0 commit comments

Comments
 (0)