TN
Thiết Nguyễn

Voucher Management - Copy this React, Tailwind Component to your project

SỬA LỖI RENDER LẠI KHI NHẬP 1 KÍ TỰ VÀ GIỮ NGUYÊN TẤT CẢ CÒN LẠI import React, { useState, useEffect } from "react"; import { FiTrash2, FiEdit2, FiEye, FiPlusCircle, FiRotateCcw } from "react icons/fi"; import { BsGraphUp } from "react icons/bs"; import axios from "axios"; const VoucherManagement = () => { const [vouchers, setVouchers] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [isEditModalOpen, setIsEditModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isViewModalOpen, setIsViewModalOpen] = useState(false); const [isStatsModalOpen, setIsStatsModalOpen] = useState(false); const [selectedVoucher, setSelectedVoucher] = useState(null); const [loading, setLoading] = useState(false); const [stats, setStats] = useState(null); const [formData, setFormData] = useState({ code: "", discountPercent: "", expirationDate: "", quantity: "" }); const API_BASE_URL = "https://domstore.azurewebsites.net/api/v1"; axios.defaults.headers.common["Authorization"] = `Bearer ${localStorage.getItem("authToken")}`; axios.interceptors.response.use( (response) => response, (error) => { if (error.response?.status === 401) { console.log("Unauthorized access redirecting to login"); } return Promise.reject(error); } ); useEffect(() => { fetchVouchers(); }, []); const fetchVouchers = async () => { try { const response = await axios.get(`${API_BASE_URL}/vouchers`, { headers: { Authorization: `Bearer ${localStorage.getItem("authToken")}` } }); setVouchers(response.data.data.vouchers); } catch (error) { console.error("Error fetching vouchers:", error); if (error.response?.status === 401) { setVouchers([]); } } }; const handleCreateVoucher = async (e) => { e.preventDefault(); setLoading(true); try { await axios.post(`${API_BASE_URL}/vouchers`, formData, { headers: { Authorization: `Bearer ${localStorage.getItem("authToken")}` } }); await fetchVouchers(); setIsCreateModalOpen(false); setFormData({ code: "", discountPercent: "", expirationDate: "", quantity: "" }); } catch (error) { console.error("Error creating voucher:", error); } finally { setLoading(false); } }; const handleEditVoucher = async (e) => { e.preventDefault(); setLoading(true); try { await axios.put(`${API_BASE_URL}/vouchers/${selectedVoucher._id}`, { discountPercent: formData.discountPercent, expirationDate: formData.expirationDate, quantity: formData.quantity }, { headers: { Authorization: `Bearer ${localStorage.getItem("authToken")}` } }); await fetchVouchers(); setIsEditModalOpen(false); setSelectedVoucher(null); } catch (error) { console.error("Error updating voucher:", error); } finally { setLoading(false); } }; const handleDeleteVoucher = async () => { setLoading(true); try { await axios.delete(`${API_BASE_URL}/vouchers/${selectedVoucher._id}`, { headers: { Authorization: `Bearer ${localStorage.getItem("authToken")}` } }); await fetchVouchers(); setIsDeleteModalOpen(false); setSelectedVoucher(null); } catch (error) { console.error("Error deleting voucher:", error); } finally { setLoading(false); } }; const handleDeactivateExpired = async () => { setLoading(true); try { await axios.post(`${API_BASE_URL}/vouchers/deactivate expired`, {}, { headers: { Authorization: `Bearer ${localStorage.getItem("authToken")}` } }); await fetchVouchers(); } catch (error) { console.error("Error deactivating expired vouchers:", error); } finally { setLoading(false); } }; const fetchVoucherStats = async (voucherId) => { try { const response = await axios.get(`${API_BASE_URL}/vouchers/${voucherId}/stats`, { headers: { Authorization: `Bearer ${localStorage.getItem("authToken")}` } }); setStats(response.data.data); } catch (error) { console.error("Error fetching voucher stats:", error); } }; const Modal = ({ isOpen, onClose, title, children }) => { if (!isOpen) return null; return ( <div className="fixed inset 0 bg black bg opacity 50 flex items center justify center z 50"> <div className="bg white rounded lg p 6 w full max w md"> <div className="flex justify between items center mb 4"> <h2 className="text xl font semibold">{title}</h2> <button onClick={onClose} className="text gray 500 hover:text gray 700">×</button> </div> {children} </div> </div> ); }; return ( <div className="container mx auto px 4 py 8"> <div className="mb 8 flex justify between items center"> <h1 className="text 3xl font bold">Quản lý Voucher </h1> <div className="flex gap 4"> <button onClick={() => setIsCreateModalOpen(true)} className="bg blue 500 text white px 4 py 2 rounded lg flex items center gap 2 hover:bg blue 600"> <FiPlusCircle /> Tạo Voucher </button> <button onClick={handleDeactivateExpired} className="bg orange 500 text white px 4 py 2 rounded lg flex items center gap 2 hover:bg orange 600"> <FiRotateCcw /> Vô hiệu hóa hết hạn </button> </div> </div> <div className="bg white rounded lg shadow overflow hidden"> <table className="min w full divide y divide gray 200"> <thead className="bg gray 50"> <tr> <th className="px 6 py 3 text left text xs font medium text gray 500 uppercase tracking wider">Mã Voucher</th> <th className="px 6 py 3 text left text xs font medium text gray 500 uppercase tracking wider">Giảm giá (%)</th> <th className="px 6 py 3 text left text xs font medium text gray 500 uppercase tracking wider">Ngày hết hạn</th> <th className="px 6 py 3 text left text xs font medium text gray 500 uppercase tracking wider">Số lượng</th> <th className="px 6 py 3 text left text xs font medium text gray 500 uppercase tracking wider">Hành động</th> </tr> </thead> <tbody className="bg white divide y divide gray 200"> {vouchers.map((voucher) => ( <tr key={voucher._id}> <td className="px 6 py 4 whitespace nowrap">{voucher.code}</td> <td className="px 6 py 4 whitespace nowrap">{voucher.discountPercent}%</td> <td className="px 6 py 4 whitespace nowrap">{new Date(voucher.expirationDate).toLocaleDateString()}</td> <td className="px 6 py 4 whitespace nowrap">{voucher.quantity}</td> <td className="px 6 py 4 whitespace nowrap"> <div className="flex gap 2"> <button onClick={() => { setSelectedVoucher(voucher); setIsViewModalOpen(true); }} className="text blue 500 hover:text blue 700"> <FiEye /> </button> <button onClick={() => { setSelectedVoucher(voucher); setFormData({ ...voucher, expirationDate: new Date(voucher.expirationDate).toISOString().split("T")[0] }); setIsEditModalOpen(true); }} className="text green 500 hover:text green 700"> <FiEdit2 /> </button> <button onClick={() => { setSelectedVoucher(voucher); setIsDeleteModalOpen(true); }} className="text red 500 hover:text red 700"> <FiTrash2 /> </button> <button onClick={() => { setSelectedVoucher(voucher); fetchVoucherStats(voucher._id); setIsStatsModalOpen(true); }} className="text purple 500 hover:text purple 700"> <BsGraphUp /> </button> </div> </td> </tr> ))} </tbody> </table> </div> <Modal isOpen={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} title="Create New Voucher"> <form onSubmit={handleCreateVoucher} className="space y 4"> <div> <label className="block text sm font medium text gray 700">Voucher Code</label> <input type="text" required value={formData.code} onChange={(e) => setFormData({ ...formData, code: e.target.value })} className="mt 1 block w full rounded md border gray 300 shadow sm focus:border blue 500 focus:ring blue 500" /> </div> <div> <label className="block text sm font medium text gray 700">Discount (%)</label> <input type="number" required min="0" max="100" value={formData.discountPercent} onChange={(e) => setFormData({ ...formData, discountPercent: e.target.value })} className="mt 1 block w full rounded md border gray 300 shadow sm focus:border blue 500 focus:ring blue 500" /> </div> <div> <label className="block text sm font medium text gray 700">Expiration Date</label> <input type="date" required value={formData.expirationDate} onChange={(e) => setFormData({ ...formData, expirationDate: e.target.value })} className="mt 1 block w full rounded md border gray 300 shadow sm focus:border blue 500 focus:ring blue 500" /> </div> <div> <label className="block text sm font medium text gray 700">Quantity</label> <input type="number" required min="1" value={formData.quantity} onChange={(e) => setFormData({ ...formData, quantity: e.target.value })} className="mt 1 block w full rounded md border gray 300 shadow sm focus:border blue 500 focus:ring blue 500" /> </div> <button type="submit" disabled={loading} className="w full bg blue 500 text white px 4 py 2 rounded lg hover:bg blue 600 disabled:bg blue 300"> {loading ? "Creating..." : "Create Voucher"} </button> </form> </Modal> <Modal isOpen={isEditModalOpen} onClose={() => setIsEditModalOpen(false)} title="Sửa Voucher"> <form onSubmit={handleEditVoucher} className="space y 4"> <div> <label className="block text sm font medium text gray 700">Giảm giá (%)</label> <input type="number" required min="0" max="100" value={formData.discountPercent} onChange={(e) => setFormData({ ...formData, discountPercent: e.target.value })} className="mt 1 block w full rounded md border gray 300 shadow sm focus:border blue 500 focus:ring blue 500" /> </div> <div> <label className="block text sm font medium text gray 700">Ngày hết hạn</label> <input type="date" required value={formData.expirationDate} onChange={(e) => setFormData({ ...formData, expirationDate: e.target.value })} className="mt 1 block w full rounded md border gray 300 shadow sm focus:border blue 500 focus:ring blue 500" /> </div> <div> <label className="block text sm font medium text gray 700">Số lượng</label> <input type="number" required min="1" value={formData.quantity} onChange={(e) => setFormData({ ...formData, quantity: e.target.value })} className="mt 1 block w full rounded md border gray 300 shadow sm focus:border blue 500 focus:ring blue 500" /> </div> <button type="submit" disabled={loading} className="w full bg green 500 text white px 4 py 2 rounded lg hover:bg green 600 disabled:bg green 300"> {loading ? "Updating..." : "Update Voucher"} </button> </form> </Modal> <Modal isOpen={isDeleteModalOpen} onClose={() => setIsDeleteModalOpen(false)} title="Delete Voucher"> <div className="space y 4"> <p>Bạn muốn xóa voucher?</p> <div className="flex gap 4"> <button onClick={handleDeleteVoucher} disabled={loading} className="flex 1 bg red 500 text white px 4 py 2 rounded lg hover:bg red 600 disabled:bg red 300"> {loading ? "Deleting..." : "Xóa"} </button> <button onClick={() => setIsDeleteModalOpen(false)} className="flex 1 bg gray 500 text white px 4 py 2 rounded lg hover:bg gray 600"> Hủy </button> </div> </div> </Modal> <Modal isOpen={isViewModalOpen} onClose={() => setIsViewModalOpen(false)} title="Chi tiết"> {selectedVoucher && ( <div className="space y 4"> <div> <h3 className="text sm font medium text gray 700">Mã Voucher</h3> <p className="mt 1">{selectedVoucher.code}</p> </div> <div> <h3 className="text sm font medium text gray 700">Giảm giá</h3> <p className="mt 1">{selectedVoucher.discountPercent}%</p> </div> <div> <h3 className="text sm font medium text gray 700">Ngày hết hạn</h3> <p className="mt 1">{new Date(selectedVoucher.expirationDate).toLocaleDateString()}</p> </div> <div> <h3 className="text sm font medium text gray 700">Số lượng</h3> <p className="mt 1">{selectedVoucher.quantity}</p> </div> <div> <h3 className="text sm font medium text gray 700">Ngày tạo</h3> <p className="mt 1">{new Date(selectedVoucher.createdAt).toLocaleDateString()}</p> </div> </div> )} </Modal> <Modal isOpen={isStatsModalOpen} onClose={() => setIsStatsModalOpen(false)} title="Voucher Statistics"> {stats && ( <div className="space y 4"> <div className="grid grid cols 2 gap 4"> <div className="bg blue 100 p 4 rounded lg"> <h3 className="text sm font medium text gray 700">Tổng số lượng </h3> <p className="text 2xl font bold text blue 700">{stats.totalQuantity}</p> </div> <div className="bg green 100 p 4 rounded lg"> <h3 className="text sm font medium text gray 700">Đã sử dụng </h3> <p className="text 2xl font bold text green 700">{stats.usedCount}</p> </div> <div className="bg purple 100 p 4 rounded lg"> <h3 className="text sm font medium text gray 700">Số lượng còn lại</h3> <p className="text 2xl font bold text purple 700">{stats.remainingCount}</p> </div> <div className="bg orange 100 p 4 rounded lg"> <h3 className="text sm font medium text gray 700">Tỷ lệ sử dụng</h3> <p className="text 2xl font bold text orange 700">{Math.round((stats.usedCount / stats.totalQuantity) * 100)}%</p> </div> </div> </div> )} </Modal> </div> ); }; export default VoucherManagement;

Prompt
Component Preview

About

VoucherManagement - Manage vouchers effortlessly with create, edit, delete, and stats features. Built with React and Tailwind. Copy component code!

Share

Last updated 1 month ago