Download src/components/CustomerManager.tsx from phucsd/PrintCal: direct link, hf CLI and curl.
- Browser
- Download file 9.32 kB
-
https://huggingface.co/spaces/phucsd/PrintCal/resolve/main/src/components/CustomerManager.tsx
- Command line
-
hf download hf://spaces/phucsd/PrintCal/src/components/CustomerManager.tsx
-
curl -L -o CustomerManager.tsx https://huggingface.co/spaces/phucsd/PrintCal/resolve/main/src/components/CustomerManager.tsx
9.32 kB
| 'use client'; | |
| import React, { useState, useEffect } from 'react'; | |
| import { Customer } from '@/types'; | |
| import { Users, Plus, Phone, Mail, Building, MapPin, X, RefreshCw } from 'lucide-react'; | |
| export const CustomerManager: React.FC = () => { | |
| const [customers, setCustomers] = useState<Customer[]>([]); | |
| const [loading, setLoading] = useState(true); | |
| const [isModalOpen, setIsModalOpen] = useState(false); | |
| const [newCustomer, setNewCustomer] = useState({ | |
| name: '', | |
| phone: '', | |
| email: '', | |
| company: '', | |
| address: '', | |
| notes: '', | |
| }); | |
| const fetchCustomers = async () => { | |
| try { | |
| setLoading(true); | |
| const res = await fetch('/api/customers'); | |
| const data = await res.json(); | |
| if (Array.isArray(data)) setCustomers(data); | |
| } catch (err) { | |
| console.error(err); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| useEffect(() => { | |
| fetchCustomers(); | |
| }, []); | |
| const handleSave = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| try { | |
| const res = await fetch('/api/customers', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(newCustomer), | |
| }); | |
| if (res.ok) { | |
| setIsModalOpen(false); | |
| setNewCustomer({ name: '', phone: '', email: '', company: '', address: '', notes: '' }); | |
| fetchCustomers(); | |
| } | |
| } catch (err) { | |
| console.error(err); | |
| } | |
| }; | |
| return ( | |
| <div className="space-y-4"> | |
| <div className="flex items-center justify-between bg-white p-4 rounded-xl border border-slate-200 shadow-xs"> | |
| <div> | |
| <h2 className="text-base font-bold text-slate-800">Danh Sách Khách Hàng (CRM mini)</h2> | |
| <p className="text-xs text-slate-500">Lưu trữ thông tin liên hệ, công ty và lịch sử đặt in của khách hàng</p> | |
| </div> | |
| <button | |
| onClick={() => setIsModalOpen(true)} | |
| className="flex items-center gap-1.5 px-3.5 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg text-xs font-semibold shadow transition-colors" | |
| > | |
| <Plus className="w-4 h-4" /> | |
| Thêm Khách Hàng | |
| </button> | |
| </div> | |
| <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| {loading ? ( | |
| <div className="col-span-3 py-12 text-center text-slate-400"> | |
| <RefreshCw className="w-6 h-6 animate-spin mx-auto text-blue-500 mb-2" /> | |
| Đang tải danh sách khách hàng... | |
| </div> | |
| ) : customers.length === 0 ? ( | |
| <div className="col-span-3 py-12 text-center text-slate-400 bg-white rounded-xl border border-slate-200"> | |
| <Users className="w-10 h-10 mx-auto text-slate-300 mb-2" /> | |
| Chưa có khách hàng nào trong hệ thống | |
| </div> | |
| ) : ( | |
| customers.map((c) => ( | |
| <div key={c.id} className="bg-white p-4 rounded-xl border border-slate-200 shadow-xs space-y-2.5"> | |
| <div className="flex items-center justify-between"> | |
| <strong className="text-sm font-bold text-slate-900">{c.name}</strong> | |
| <span className="text-[10px] text-slate-400 font-mono">#{c.id}</span> | |
| </div> | |
| {c.company && ( | |
| <div className="flex items-center gap-1.5 text-xs text-slate-600"> | |
| <Building className="w-3.5 h-3.5 text-slate-400 shrink-0" /> | |
| <span className="truncate">{c.company}</span> | |
| </div> | |
| )} | |
| <div className="flex items-center gap-1.5 text-xs text-slate-700 font-medium"> | |
| <Phone className="w-3.5 h-3.5 text-blue-600 shrink-0" /> | |
| <span>{c.phone}</span> | |
| </div> | |
| {c.email && ( | |
| <div className="flex items-center gap-1.5 text-xs text-slate-600"> | |
| <Mail className="w-3.5 h-3.5 text-slate-400 shrink-0" /> | |
| <span className="truncate">{c.email}</span> | |
| </div> | |
| )} | |
| {c.address && ( | |
| <div className="flex items-center gap-1.5 text-xs text-slate-500"> | |
| <MapPin className="w-3.5 h-3.5 text-slate-400 shrink-0" /> | |
| <span className="truncate">{c.address}</span> | |
| </div> | |
| )} | |
| {c.notes && ( | |
| <p className="text-[11px] text-slate-500 bg-slate-50 p-2 rounded border border-slate-100 italic"> | |
| “{c.notes}” | |
| </p> | |
| )} | |
| </div> | |
| )) | |
| )} | |
| </div> | |
| {isModalOpen && ( | |
| <div className="fixed inset-0 z-50 bg-slate-900/50 backdrop-blur-xs flex items-center justify-center p-4"> | |
| <div className="bg-white rounded-xl shadow-xl max-w-md w-full overflow-hidden"> | |
| <div className="p-4 bg-slate-900 text-white flex items-center justify-between"> | |
| <h3 className="font-bold text-sm">Thêm Mới Khách Hàng</h3> | |
| <button onClick={() => setIsModalOpen(false)} className="text-slate-400 hover:text-white"> | |
| <X className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| <form onSubmit={handleSave} className="p-5 space-y-3 text-xs"> | |
| <div> | |
| <label className="block text-slate-600 font-medium mb-1">Tên Khách Hàng *</label> | |
| <input | |
| type="text" | |
| required | |
| value={newCustomer.name} | |
| onChange={(e) => setNewCustomer({ ...newCustomer, name: e.target.value })} | |
| placeholder="Ví dụ: Anh Hoàng / Chị Mai" | |
| className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500" | |
| /> | |
| </div> | |
| <div className="grid grid-cols-2 gap-3"> | |
| <div> | |
| <label className="block text-slate-600 font-medium mb-1">Số Điện Thoại *</label> | |
| <input | |
| type="tel" | |
| required | |
| value={newCustomer.phone} | |
| onChange={(e) => setNewCustomer({ ...newCustomer, phone: e.target.value })} | |
| placeholder="0901234567" | |
| className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500" | |
| /> | |
| </div> | |
| <div> | |
| <label className="block text-slate-600 font-medium mb-1">Email</label> | |
| <input | |
| type="email" | |
| value={newCustomer.email} | |
| onChange={(e) => setNewCustomer({ ...newCustomer, email: e.target.value })} | |
| placeholder="email@example.com" | |
| className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500" | |
| /> | |
| </div> | |
| </div> | |
| <div> | |
| <label className="block text-slate-600 font-medium mb-1">Tên Công Ty / Cửa Hàng</label> | |
| <input | |
| type="text" | |
| value={newCustomer.company} | |
| onChange={(e) => setNewCustomer({ ...newCustomer, company: e.target.value })} | |
| placeholder="Công ty TNHH..." | |
| className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500" | |
| /> | |
| </div> | |
| <div> | |
| <label className="block text-slate-600 font-medium mb-1">Địa Chỉ Giao Hàng</label> | |
| <input | |
| type="text" | |
| value={newCustomer.address} | |
| onChange={(e) => setNewCustomer({ ...newCustomer, address: e.target.value })} | |
| placeholder="Số nhà, tên đường, quận/huyện..." | |
| className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500" | |
| /> | |
| </div> | |
| <div> | |
| <label className="block text-slate-600 font-medium mb-1">Ghi Chú Về Khách Hàng</label> | |
| <textarea | |
| rows={2} | |
| value={newCustomer.notes} | |
| onChange={(e) => setNewCustomer({ ...newCustomer, notes: e.target.value })} | |
| placeholder="Thói quen đặt hàng, lưu ý kỹ thuật..." | |
| className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500" | |
| /> | |
| </div> | |
| <div className="flex justify-end gap-2 pt-3 border-t border-slate-100"> | |
| <button | |
| type="button" | |
| onClick={() => setIsModalOpen(false)} | |
| className="px-4 py-2 border border-slate-300 rounded-lg text-slate-600 hover:bg-slate-50" | |
| > | |
| Hủy | |
| </button> | |
| <button | |
| type="submit" | |
| className="px-4 py-2 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700" | |
| > | |
| Lưu Khách Hàng | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |