Add status/paymentStatus/year filters to the order overview
Server-side, URL-search-param-driven filtering (?status=&paymentStatus=&year=) so the page stays a Server Component — each filter chip is a plain Link to the same route with one param changed, shareable/bookmarkable/back- button-safe for free. getCustomerOrders() gained an optional filters param translated into additional Payload where[] clauses; a new getCustomerOrderYears() derives the year filter's option list from the customer's actual orders instead of a hardcoded range. paymentStatus's "Offen" filter option mirrors PaymentStatusBadge's own grouping (pending + not_applicable as one option, not two). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+57
-1
@@ -498,15 +498,61 @@ function excludeFailedPaymentAttemptsQuery(): Record<string, string> {
|
||||
};
|
||||
}
|
||||
|
||||
// "Offen" in the account UI's PaymentStatusBadge covers two distinct
|
||||
// backend values (an unconfirmed Stripe payment vs. an Überweisung order
|
||||
// awaiting manual reconciliation) — the filter chip mirrors that same
|
||||
// grouping rather than exposing the internal distinction as two options.
|
||||
const OPEN_PAYMENT_STATUSES = ["pending", "not_applicable"] as const;
|
||||
|
||||
export type CustomerOrderFilters = {
|
||||
status?: string;
|
||||
paymentStatus?: string;
|
||||
/** Calendar year as a string, e.g. "2026" — matches createdAt within
|
||||
* [Jan 1, Jan 1 of next year). */
|
||||
year?: string;
|
||||
};
|
||||
|
||||
function customerOrderFilterQuery(filters: CustomerOrderFilters | undefined, whereIndex: number): Record<string, string> {
|
||||
if (!filters) return {};
|
||||
const params: Record<string, string> = {};
|
||||
let i = whereIndex;
|
||||
if (filters.status) {
|
||||
params[`where[and][${i}][status][equals]`] = filters.status;
|
||||
i += 1;
|
||||
}
|
||||
if (filters.paymentStatus) {
|
||||
if (filters.paymentStatus === "open") {
|
||||
OPEN_PAYMENT_STATUSES.forEach((value, j) => {
|
||||
params[`where[and][${i}][or][${j}][paymentStatus][equals]`] = value;
|
||||
});
|
||||
} else {
|
||||
params[`where[and][${i}][paymentStatus][equals]`] = filters.paymentStatus;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if (filters.year && /^\d{4}$/.test(filters.year)) {
|
||||
const year = Number(filters.year);
|
||||
params[`where[and][${i}][createdAt][greater_than_equal]`] = new Date(Date.UTC(year, 0, 1)).toISOString();
|
||||
params[`where[and][${i}][createdAt][less_than]`] = new Date(Date.UTC(year + 1, 0, 1)).toISOString();
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
// `excludeFailedPaymentAttempts` defaults to true (list views) — the one
|
||||
// exception is /api/account/export/route.ts's GDPR data export, which
|
||||
// passes false: a legal completeness export must include every order
|
||||
// row that exists about this customer, not just the ones normally shown
|
||||
// in "Meine Bestellungen".
|
||||
export async function getCustomerOrders(token: string, customerId: number, excludeFailedPaymentAttempts = true): Promise<CustomerOrder[]> {
|
||||
export async function getCustomerOrders(
|
||||
token: string,
|
||||
customerId: number,
|
||||
excludeFailedPaymentAttempts = true,
|
||||
filters?: CustomerOrderFilters,
|
||||
): Promise<CustomerOrder[]> {
|
||||
const params = new URLSearchParams({
|
||||
"where[and][0][customer][equals]": String(customerId),
|
||||
...(excludeFailedPaymentAttempts ? excludeFailedPaymentAttemptsQuery() : {}),
|
||||
...customerOrderFilterQuery(filters, excludeFailedPaymentAttempts ? 2 : 1),
|
||||
sort: "-createdAt",
|
||||
depth: "0",
|
||||
limit: "50",
|
||||
@@ -537,6 +583,16 @@ export async function getCustomerOrders(token: string, customerId: number, exclu
|
||||
}));
|
||||
}
|
||||
|
||||
// All years that have at least one (non-filtered-out) order for this
|
||||
// customer — powers the year filter's option list without hardcoding a
|
||||
// range. Cheap: reuses the same excludeFailedPaymentAttempts query, no
|
||||
// separate collection/aggregation endpoint needed for this order volume.
|
||||
export async function getCustomerOrderYears(token: string, customerId: number): Promise<string[]> {
|
||||
const orders = await getCustomerOrders(token, customerId, true);
|
||||
const years = new Set(orders.map((o) => new Date(o.createdAt).getUTCFullYear().toString()));
|
||||
return Array.from(years).sort((a, b) => Number(b) - Number(a));
|
||||
}
|
||||
|
||||
export type CustomerOrderDetail = CustomerOrder & {
|
||||
id: number;
|
||||
// 'manual' (Überweisung) vs 'stripe' (Kreditkarte/PayPal) — see
|
||||
|
||||
Reference in New Issue
Block a user