Files
WooNooW/admin-spa/src/lib/api/coupons.ts
dwindown b77f63fcaf feat: Coupons CRUD - Frontend list page (Phase 2)
Implemented complete Coupons list page following PROJECT_SOP.md

Created: CouponsApi helper (lib/api/coupons.ts)
- TypeScript interfaces for Coupon and CouponFormData
- Full CRUD methods: list, get, create, update, delete
- Pagination and filtering support

Updated: Coupons/index.tsx (Complete rewrite)
- Full CRUD list page with SOP-compliant UI
- Toolbar with bulk actions and filters
- Desktop table + Mobile cards (responsive)
- Pagination support
- Search and filter by discount type

Following PROJECT_SOP.md Standards:
 Toolbar pattern: Bulk delete, Refresh (REQUIRED), Filters
 Table UI: p-3 padding, hover:bg-muted/30, bg-muted/50 header
 Button styling: bg-red-600 for delete, inline-flex gap-2
 Reset filters: Text link style (NOT button)
 Empty state: Icon + message + helper text
 Mobile responsive: Cards with md:hidden
 Error handling: ErrorCard for page loads
 Loading state: LoadingState component
 FAB configuration: Navigate to /coupons/new

Features:
- Bulk selection with checkbox
- Bulk delete with confirmation
- Search by coupon code
- Filter by discount type
- Pagination (prev/next)
- Formatted discount amounts
- Usage tracking display
- Expiry date display
- Edit navigation

UI Components Used:
- Card, Input, Select, Checkbox, Badge
- Lucide icons: Trash2, RefreshCw, Edit, Tag
- Consistent spacing and typography

Next Steps:
- Create New.tsx (create coupon form)
- Create Edit.tsx (edit coupon form)
- Update NavigationRegistry.php
- Update API_ROUTES.md
2025-11-20 13:57:35 +07:00

96 lines
2.3 KiB
TypeScript

import { api } from '../api';
export interface Coupon {
id: number;
code: string;
amount: number;
discount_type: 'percent' | 'fixed_cart' | 'fixed_product';
description: string;
usage_count: number;
usage_limit: number | null;
date_expires: string | null;
individual_use?: boolean;
product_ids?: number[];
excluded_product_ids?: number[];
usage_limit_per_user?: number | null;
limit_usage_to_x_items?: number | null;
free_shipping?: boolean;
product_categories?: number[];
excluded_product_categories?: number[];
exclude_sale_items?: boolean;
minimum_amount?: number | null;
maximum_amount?: number | null;
email_restrictions?: string[];
}
export interface CouponListResponse {
coupons: Coupon[];
total: number;
page: number;
per_page: number;
total_pages: number;
}
export interface CouponFormData {
code: string;
amount: number;
discount_type: 'percent' | 'fixed_cart' | 'fixed_product';
description?: string;
date_expires?: string | null;
individual_use?: boolean;
product_ids?: number[];
excluded_product_ids?: number[];
usage_limit?: number | null;
usage_limit_per_user?: number | null;
limit_usage_to_x_items?: number | null;
free_shipping?: boolean;
product_categories?: number[];
excluded_product_categories?: number[];
exclude_sale_items?: boolean;
minimum_amount?: number | null;
maximum_amount?: number | null;
email_restrictions?: string[];
}
export const CouponsApi = {
/**
* List coupons with pagination and filtering
*/
list: async (params?: {
page?: number;
per_page?: number;
search?: string;
discount_type?: string;
}): Promise<CouponListResponse> => {
return api.get('/coupons', { params });
},
/**
* Get single coupon
*/
get: async (id: number): Promise<Coupon> => {
return api.get(`/coupons/${id}`);
},
/**
* Create new coupon
*/
create: async (data: CouponFormData): Promise<Coupon> => {
return api.post('/coupons', data);
},
/**
* Update coupon
*/
update: async (id: number, data: Partial<CouponFormData>): Promise<Coupon> => {
return api.put(`/coupons/${id}`, data);
},
/**
* Delete coupon
*/
delete: async (id: number, force: boolean = false): Promise<{ success: boolean; id: number }> => {
return api.del(`/coupons/${id}?force=${force ? 'true' : 'false'}`);
},
};