'use client'; import React, { useState, useEffect, useMemo } from 'react'; import { PaperType } from '@/types'; import { Plus, Edit2, Trash2, Check, X, RefreshCw, Search, Building2, Filter, RotateCcw } from 'lucide-react'; export const PaperManager: React.FC = () => { const [papers, setPapers] = useState([]); const [loading, setLoading] = useState(true); const [editingPaper, setEditingPaper] = useState | null>(null); const [isModalOpen, setIsModalOpen] = useState(false); // Search & Filter State const [searchTerm, setSearchTerm] = useState(''); const [selectedSupplier, setSelectedSupplier] = useState('all'); const [selectedCategory, setSelectedCategory] = useState('all'); const fetchPapers = async () => { try { setLoading(true); const res = await fetch('/api/papers'); const data = await res.json(); if (Array.isArray(data)) setPapers(data); } catch (err) { console.error(err); } finally { setLoading(false); } }; useEffect(() => { fetchPapers(); }, []); // Unique suppliers const suppliers = useMemo(() => { const list = Array.from(new Set(papers.map((p) => p.supplier || 'Thuận Phát'))).filter(Boolean); return list.sort(); }, [papers]); // Categories helper const getCategory = (p: PaperType): string => { const name = p.name.toLowerCase(); const code = p.code.toLowerCase(); if (name.includes('ivory') || code.startsWith('i')) return 'Ivory'; if (name.includes('couche') || code.startsWith('c')) return 'Couche'; if (name.includes('fort') || name.includes('ford') || code.startsWith('f')) return 'Fort'; if (name.includes('duplex') || code.startsWith('d')) return 'Duplex'; if (name.includes('bristol') || code.startsWith('b')) return 'Bristol'; if (name.includes('decal')) return 'Decal'; if (name.includes('kraft')) return 'Kraft'; return 'Khác'; }; const categories = ['Ivory', 'Couche', 'Fort', 'Duplex', 'Bristol', 'Decal', 'Kraft', 'Khác']; // Filtered papers const filteredPapers = useMemo(() => { return papers.filter((p) => { // Supplier filter if (selectedSupplier !== 'all') { const supp = p.supplier || 'Thuận Phát'; if (supp !== selectedSupplier) return false; } // Category filter if (selectedCategory !== 'all') { if (getCategory(p) !== selectedCategory) return false; } // Text search if (searchTerm.trim()) { const query = searchTerm.toLowerCase(); const matchName = p.name.toLowerCase().includes(query); const matchCode = p.code.toLowerCase().includes(query); const matchDesc = (p.description || '').toLowerCase().includes(query); const matchSupp = (p.supplier || '').toLowerCase().includes(query); const matchGsm = p.gsm.toString().includes(query); if (!matchName && !matchCode && !matchDesc && !matchSupp && !matchGsm) return false; } return true; }); }, [papers, selectedSupplier, selectedCategory, searchTerm]); const handleOpenAdd = () => { setEditingPaper({ supplier: 'Thuận Phát', code: `C_${Date.now().toString().slice(-4)}`, name: 'Couche ', gsm: 150, parentWidthCm: 65, parentHeightCm: 86, priceAbove500: 500000, priceBelow500: 530000, pricePerRam: 500000, unit: 'ram', description: '', isActive: true, }); setIsModalOpen(true); }; const handleOpenEdit = (p: PaperType) => { setEditingPaper({ ...p, supplier: p.supplier || 'Thuận Phát', priceAbove500: p.priceAbove500 || p.pricePerRam || 0, priceBelow500: p.priceBelow500 || p.pricePerRam || 0, }); setIsModalOpen(true); }; const handleSave = async (e: React.FormEvent) => { e.preventDefault(); if (!editingPaper) return; const isUpdate = Boolean(editingPaper.id); const method = isUpdate ? 'PUT' : 'POST'; // Normalize prices: keep pricePerRam synced with priceAbove500 const payload = { ...editingPaper, pricePerRam: editingPaper.priceAbove500 || 0, }; try { const res = await fetch('/api/papers', { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); if (res.ok) { setIsModalOpen(false); setEditingPaper(null); fetchPapers(); } else { const data = await res.json(); alert(data.error || 'Có lỗi xảy ra khi lưu giấy!'); } } catch (err) { console.error(err); alert('Không thể kết nối đến máy chủ.'); } }; const handleDelete = async (id: number) => { if (!confirm('Bạn có chắc chắn muốn xóa loại giấy này?')) return; try { const res = await fetch(`/api/papers?id=${id}`, { method: 'DELETE' }); if (res.ok) fetchPapers(); } catch (err) { console.error(err); } }; const resetFilters = () => { setSearchTerm(''); setSelectedSupplier('all'); setSelectedCategory('all'); }; return (
{/* Header & Quick stats */}

Kho Bảng Giá Giấy In & Nhà Cung Cấp

Quản lý báo giá theo mức ≥ 500 tờ (trên ram) và < 500 tờ (dưới ram). Đã phân chia rõ từng Nhà Cung Cấp (NCC).

{/* Filter Bar */}
{/* Search box */}
setSearchTerm(e.target.value)} placeholder="Tìm theo tên giấy, mã giấy, hiệu (Ningbo, Nevia, Indo...), NCC..." className="w-full pl-9 pr-8 py-2 text-xs border border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 font-medium" /> {searchTerm && ( )}
{/* Supplier Filter */}
NCC:
{/* Category Filter */}
Loại:
{/* Filter Badges & Count */}
Hiển thị {filteredPapers.length} / {papers.length} loại giấy {(searchTerm || selectedSupplier !== 'all' || selectedCategory !== 'all') && ( )}
{/* Quick Category Pills */}
{['all', 'Couche', 'Ivory', 'Fort', 'Duplex', 'Bristol'].map((cat) => ( ))}
{/* Main Table */}
{loading ? ( ) : filteredPapers.length === 0 ? ( ) : ( filteredPapers.map((p) => { const priceAbove = p.priceAbove500 || p.pricePerRam || 0; const priceBelow = p.priceBelow500 || priceAbove; const unitPriceAbove = Math.round(priceAbove / 500); const unitPriceBelow = Math.round(priceBelow / 500); return ( {/* NCC */} {/* Mã giấy */} {/* Tên loại giấy */} {/* GSM */} {/* Khổ mẹ */} {/* Giá >= 500 tờ (trên ram) */} {/* Giá < 500 tờ (dưới ram) */} {/* Trạng thái */} {/* Thao tác */} ); }) )}
Nhà Cung Cấp Mã Giấy Tên Loại Giấy & Hiệu Định Lượng Khổ Mẹ
Giá ≥ 500 tờ
Nguyên ram
Giá < 500 tờ
Bán lẻ
Trạng Thái Thao Tác
Đang tải dữ liệu giấy...
Không tìm thấy loại giấy nào phù hợp với bộ lọc.
{p.supplier || 'Thuận Phát'} {p.code} {p.name} {p.description && {p.description}} {p.gsm} gsm {p.parentWidthCm} × {p.parentHeightCm} cm
{priceAbove > 0 ? `${priceAbove.toLocaleString('vi-VN')} đ` : '-'}
{priceAbove > 0 && (
~{unitPriceAbove.toLocaleString('vi-VN')} đ/tờ
)}
{priceBelow > 0 ? `${priceBelow.toLocaleString('vi-VN')} đ` : '-'}
{priceBelow > 0 && (
~{unitPriceBelow.toLocaleString('vi-VN')} đ/tờ
)}
{p.isActive ? 'Đang dùng' : 'Tạm khóa'}
{/* Modal Thêm/Sửa Giấy */} {isModalOpen && editingPaper && (

{editingPaper.id ? 'Cập Nhật Bảng Giá Giấy' : 'Thêm Mới Loại Giấy'}

{/* Nhà Cung Cấp */}
setEditingPaper({ ...editingPaper, supplier: e.target.value })} placeholder="Ví dụ: Thuận Phát, Mai Lam, Tân Phát Đạt..." className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500 font-medium" />
setEditingPaper({ ...editingPaper, code: e.target.value })} className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500 font-mono" />
setEditingPaper({ ...editingPaper, gsm: Number(e.target.value) })} className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500" />
setEditingPaper({ ...editingPaper, name: e.target.value })} className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500" />
setEditingPaper({ ...editingPaper, parentWidthCm: Number(e.target.value) })} className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500" />
setEditingPaper({ ...editingPaper, parentHeightCm: Number(e.target.value) })} className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500" />
{/* 2 Mức giá: >= 500 tờ và < 500 tờ */}
setEditingPaper({ ...editingPaper, priceAbove500: Number(e.target.value), pricePerRam: Number(e.target.value), }) } className="w-full border border-emerald-300 rounded-lg p-2 focus:ring-1 focus:ring-emerald-500 font-bold text-emerald-900 bg-white" /> Áp dụng khi dùng ≥ 500 tờ mẹ (~{Math.round((editingPaper.priceAbove500 || 0) / 500).toLocaleString('vi-VN')} đ/tờ)
setEditingPaper({ ...editingPaper, priceBelow500: Number(e.target.value) })} className="w-full border border-blue-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500 font-bold text-blue-900 bg-white" /> Bán lẻ khi dùng < 500 tờ mẹ (~{Math.round((editingPaper.priceBelow500 || 0) / 500).toLocaleString('vi-VN')} đ/tờ)
setEditingPaper({ ...editingPaper, description: e.target.value })} placeholder="Ví dụ: AF - Indo, Nevia, Oji..." className="w-full border border-slate-300 rounded-lg p-2 focus:ring-1 focus:ring-blue-500" />
setEditingPaper({ ...editingPaper, isActive: e.target.checked })} className="rounded text-blue-600 focus:ring-blue-500 w-4 h-4" />
)}
); };