mirror of
https://github.com/duongcamcute/tech-gadget-manager.git
synced 2026-06-28 14:55:47 +00:00
feat: chế độ xem mặc định + custom spec badges trên card
This commit is contained in:
@@ -712,6 +712,38 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Default View Mode */}
|
||||
<div className="space-y-4 pt-6 border-t border-gray-100">
|
||||
<Label className="text-base font-semibold">Chế độ xem mặc định (Kho đồ)</Label>
|
||||
<p className="text-xs text-muted-foreground -mt-2">Chọn cách hiển thị thiết bị mặc định khi mở trang Kho đồ</p>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{[
|
||||
{ value: 'grid', label: 'Lưới', icon: '🔲', desc: 'Icon thiết bị' },
|
||||
{ value: 'grid-thumb', label: 'Thumbnail', icon: '🖼️', desc: 'Ảnh thu nhỏ' },
|
||||
{ value: 'list', label: 'Danh sách', icon: '📋', desc: 'Chi tiết hàng' },
|
||||
].map(mode => {
|
||||
const savedMode = typeof window !== 'undefined' ? localStorage.getItem('defaultViewMode') : 'grid';
|
||||
const isActive = savedMode === mode.value || (!savedMode && mode.value === 'grid');
|
||||
return (
|
||||
<button
|
||||
key={mode.value}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
localStorage.setItem('defaultViewMode', mode.value);
|
||||
toast(`Đã đặt chế độ xem mặc định: ${mode.label}`, 'success');
|
||||
}}
|
||||
className={`p-4 rounded-xl border-2 transition-all text-center hover:shadow-md ${isActive ? 'border-primary-500 bg-primary-50 shadow-sm' : 'border-gray-200 bg-white hover:border-gray-300'}`}
|
||||
>
|
||||
<span className="text-2xl block mb-1">{mode.icon}</span>
|
||||
<span className={`font-semibold text-sm ${isActive ? 'text-primary-700' : 'text-gray-700'}`}>{mode.label}</span>
|
||||
<span className="text-[10px] text-gray-400 block">{mode.desc}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<p className="text-[10px] text-gray-400">💡 Tip: Khi F5 trang Kho đồ sẽ tự động hiển thị theo chế độ bạn chọn ở đây.</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end pt-4 border-t border-gray-100">
|
||||
<Button onClick={handleSaveTheme} disabled={loading} className="bg-gray-900 hover:bg-black text-white px-8 rounded-xl">
|
||||
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
|
||||
@@ -74,6 +74,23 @@ export default function InventoryManager({ initialItems, locations }: { initialI
|
||||
const searchParams = useSearchParams(); // Added useSearchParams
|
||||
const { toast } = useToast();
|
||||
|
||||
// Load default view mode from localStorage
|
||||
useEffect(() => {
|
||||
const savedMode = localStorage.getItem('defaultViewMode');
|
||||
if (savedMode) {
|
||||
if (savedMode === 'grid') {
|
||||
setViewMode('grid');
|
||||
setShowThumbnails(false);
|
||||
} else if (savedMode === 'grid-thumb') {
|
||||
setViewMode('grid');
|
||||
setShowThumbnails(true);
|
||||
} else if (savedMode === 'list') {
|
||||
setViewMode('list');
|
||||
setShowThumbnails(false);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Auto-open Item Detail from URL
|
||||
useEffect(() => {
|
||||
const itemId = searchParams.get("item");
|
||||
@@ -553,9 +570,19 @@ export default function InventoryManager({ initialItems, locations }: { initialI
|
||||
<h3 className="font-bold text-sm text-gray-900 dark:text-gray-100 line-clamp-2 leading-tight mb-2" title={item.name}>{item.name}</h3>
|
||||
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{specs.power && <span className="bg-orange-50 px-1.5 py-0.5 rounded text-[10px] text-orange-700 border border-orange-100 font-medium whitespace-nowrap" title="Công suất">⚡ {specs.power}</span>}
|
||||
{specs.length && <span className="bg-emerald-50 px-1.5 py-0.5 rounded text-[10px] text-emerald-700 border border-emerald-100 font-medium whitespace-nowrap" title="Độ dài">📏 {specs.length}</span>}
|
||||
{specs.capacity && <span className="bg-purple-50 px-1.5 py-0.5 rounded text-[10px] text-purple-700 border border-purple-100 font-medium whitespace-nowrap" title="Dung lượng">🔋 {specs.capacity}</span>}
|
||||
{/* Check displayBadges array - if exists only show selected, else show all */}
|
||||
{(() => {
|
||||
const badges = specs.displayBadges || ['power', 'length', 'capacity']; // default show all
|
||||
return (
|
||||
<>
|
||||
{badges.includes('power') && specs.power && <span className="bg-orange-50 px-1.5 py-0.5 rounded text-[10px] text-orange-700 border border-orange-100 font-medium whitespace-nowrap" title="Công suất">⚡ {specs.power}</span>}
|
||||
{badges.includes('length') && specs.length && <span className="bg-emerald-50 px-1.5 py-0.5 rounded text-[10px] text-emerald-700 border border-emerald-100 font-medium whitespace-nowrap" title="Độ dài">📏 {specs.length}</span>}
|
||||
{badges.includes('capacity') && specs.capacity && <span className="bg-purple-50 px-1.5 py-0.5 rounded text-[10px] text-purple-700 border border-purple-100 font-medium whitespace-nowrap" title="Dung lượng">🔋 {specs.capacity}</span>}
|
||||
{badges.includes('interface') && specs.interface && <span className="bg-blue-50 px-1.5 py-0.5 rounded text-[10px] text-blue-700 border border-blue-100 font-medium whitespace-nowrap" title="Kết nối">🔌 {specs.interface}</span>}
|
||||
{badges.includes('bandwidth') && specs.bandwidth && <span className="bg-cyan-50 px-1.5 py-0.5 rounded text-[10px] text-cyan-700 border border-cyan-100 font-medium whitespace-nowrap" title="Tốc độ">⚡ {specs.bandwidth}</span>}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -713,7 +713,7 @@ function EditMode({ item, locations, onCancel, onClose }: { item: any, locations
|
||||
{(() => {
|
||||
const currentSpecs = form.watch("specs") || {};
|
||||
// Filter out null/undefined values to avoid display issues
|
||||
const entries = Object.entries(currentSpecs).filter(([_, v]) => v !== null && v !== undefined);
|
||||
const entries = Object.entries(currentSpecs).filter(([k, v]) => v !== null && v !== undefined && k !== 'displayBadges');
|
||||
|
||||
const updateSpec = (key: string, val: string) => {
|
||||
const newSpecs = { ...currentSpecs, [key]: val };
|
||||
@@ -753,6 +753,60 @@ function EditMode({ item, locations, onCancel, onClose }: { item: any, locations
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Display Badges Selection */}
|
||||
<div className="col-span-2 space-y-2">
|
||||
<Label className="flex items-center gap-2">
|
||||
Hiển thị trên Card
|
||||
<span className="text-[10px] bg-orange-100 text-orange-700 px-1.5 py-0.5 rounded-full font-medium">Badge</span>
|
||||
</Label>
|
||||
<p className="text-[10px] text-gray-500 -mt-1">Chọn thông số sẽ hiển thị dưới dạng badge trên card của thiết bị</p>
|
||||
<div className="bg-gradient-to-r from-orange-50 to-emerald-50 rounded-lg p-3 border border-orange-200/50">
|
||||
{(() => {
|
||||
const currentSpecs = form.watch("specs") || {};
|
||||
const displayBadges: string[] = currentSpecs.displayBadges || ['power', 'length', 'capacity'];
|
||||
const allBadgeOptions = [
|
||||
{ key: 'power', label: 'Công suất', icon: '⚡', color: 'bg-orange-100 border-orange-200 text-orange-700' },
|
||||
{ key: 'length', label: 'Độ dài', icon: '📏', color: 'bg-emerald-100 border-emerald-200 text-emerald-700' },
|
||||
{ key: 'capacity', label: 'Dung lượng', icon: '🔋', color: 'bg-purple-100 border-purple-200 text-purple-700' },
|
||||
{ key: 'interface', label: 'Kết nối', icon: '🔌', color: 'bg-blue-100 border-blue-200 text-blue-700' },
|
||||
{ key: 'bandwidth', label: 'Tốc độ', icon: '⚡', color: 'bg-cyan-100 border-cyan-200 text-cyan-700' },
|
||||
];
|
||||
|
||||
const toggleBadge = (key: string) => {
|
||||
let newBadges = [...displayBadges];
|
||||
if (newBadges.includes(key)) {
|
||||
newBadges = newBadges.filter(b => b !== key);
|
||||
} else {
|
||||
newBadges.push(key);
|
||||
}
|
||||
form.setValue("specs", { ...currentSpecs, displayBadges: newBadges }, { shouldDirty: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{allBadgeOptions.map(opt => {
|
||||
const isChecked = displayBadges.includes(opt.key);
|
||||
const hasValue = currentSpecs[opt.key];
|
||||
return (
|
||||
<button
|
||||
key={opt.key}
|
||||
type="button"
|
||||
onClick={() => toggleBadge(opt.key)}
|
||||
className={`flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg border transition-all text-xs font-medium ${isChecked ? opt.color + ' shadow-sm' : 'bg-white border-gray-200 text-gray-400 hover:border-gray-300'} ${!hasValue ? 'opacity-50' : ''}`}
|
||||
title={hasValue ? `${opt.label}: ${currentSpecs[opt.key]}` : `Chưa có ${opt.label}`}
|
||||
>
|
||||
<span>{opt.icon}</span>
|
||||
<span>{opt.label}</span>
|
||||
{isChecked && <Check size={12} className="ml-1" />}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user