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
This commit is contained in:
95
admin-spa/src/lib/api/coupons.ts
Normal file
95
admin-spa/src/lib/api/coupons.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
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'}`);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user