From fa59df4c3d9be95bfedef3590081aca7c9f8a526 Mon Sep 17 00:00:00 2001 From: DaX Date: Fri, 27 Jun 2025 01:40:18 +0200 Subject: [PATCH] Implement quantity-based inventory tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace checkboxes with quantity inputs for refill, vakuum, and otvoreno - Display actual quantities in admin table instead of checkmarks - Auto-parse existing data formats (e.g., "3 vakuum", "Da") to numbers - Maintain backwards compatibility with existing data - Update price auto-fill logic to work with quantity values - Update tests to reflect new quantity inputs šŸ¤– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: DaX --- __tests__/ui-features.test.ts | 13 ++- app/upadaj/dashboard/page.tsx | 178 +++++++++++++++++++++------------- 2 files changed, 118 insertions(+), 73 deletions(-) diff --git a/__tests__/ui-features.test.ts b/__tests__/ui-features.test.ts index 1136681..274d80e 100644 --- a/__tests__/ui-features.test.ts +++ b/__tests__/ui-features.test.ts @@ -21,14 +21,17 @@ describe('UI Features Tests', () => { expect(tableContent).toContain('color.hex'); }); - it('should have checkboxes for boolean fields', () => { + it('should have number inputs for quantity fields', () => { const adminDashboardPath = join(process.cwd(), 'app', 'upadaj', 'dashboard', 'page.tsx'); const adminContent = readFileSync(adminDashboardPath, 'utf-8'); - // Check for checkbox inputs - expect(adminContent).toMatch(/type="checkbox"[\s\S]*?name="refill"/); - expect(adminContent).toMatch(/type="checkbox"[\s\S]*?name="vakum"/); - expect(adminContent).toMatch(/type="checkbox"[\s\S]*?name="otvoreno"/); + // Check for number inputs for quantities + expect(adminContent).toMatch(/type="number"[\s\S]*?name="refill"/); + expect(adminContent).toMatch(/type="number"[\s\S]*?name="vakum"/); + expect(adminContent).toMatch(/type="number"[\s\S]*?name="otvoreno"/); + expect(adminContent).toContain('Refill količina'); + expect(adminContent).toContain('Vakuum količina'); + expect(adminContent).toContain('Otvoreno količina'); }); it('should have number input for quantity', () => { diff --git a/app/upadaj/dashboard/page.tsx b/app/upadaj/dashboard/page.tsx index ed4e553..12059a4 100644 --- a/app/upadaj/dashboard/page.tsx +++ b/app/upadaj/dashboard/page.tsx @@ -265,25 +265,33 @@ export default function AdminDashboard() { - {filament.refill?.toLowerCase() === 'da' ? ( - āœ“ - ) : ( - āœ— - )} + {(() => { + const refillCount = parseInt(filament.refill) || 0; + if (refillCount > 0) { + return {refillCount}; + } + return 0; + })()} - {filament.vakum?.toLowerCase().includes('vakuum') ? ( - āœ“ - ) : ( - āœ— - )} + {(() => { + const match = filament.vakum?.match(/^(\d+)\s*vakuum/); + const vakuumCount = match ? parseInt(match[1]) : 0; + if (vakuumCount > 0) { + return {vakuumCount}; + } + return 0; + })()} - {filament.otvoreno?.toLowerCase().includes('otvorena') ? ( - āœ“ - ) : ( - āœ— - )} + {(() => { + const match = filament.otvoreno?.match(/^(\d+)\s*otvorena/); + const otvorenCount = match ? parseInt(match[1]) : 0; + if (otvorenCount > 0) { + return {otvorenCount}; + } + return 0; + })()} {filament.kolicina} {filament.cena} @@ -392,28 +400,17 @@ function FilamentForm({ }, [filament]); const handleChange = (e: React.ChangeEvent) => { - const { name, value, type } = e.target; + const { name, value } = e.target; - if (type === 'checkbox') { - const checked = (e.target as HTMLInputElement).checked; - if (name === 'vakum') { - setFormData({ - ...formData, - [name]: checked ? 'vakuum' : '' - }); - } else if (name === 'otvoreno') { - setFormData({ - ...formData, - [name]: checked ? 'otvorena' : '' - }); - } else if (name === 'refill') { - setFormData({ - ...formData, - [name]: checked ? 'Da' : '', - // Auto-fill price based on refill status if this is a new filament - ...(filament.id ? {} : { cena: checked ? '3499' : '3999' }) - }); - } + if (name === 'refill') { + // Auto-set price based on refill quantity + const refillCount = parseInt(value) || 0; + setFormData({ + ...formData, + [name]: value, + // Auto-fill price based on refill status if this is a new filament + ...(filament.id ? {} : { cena: refillCount > 0 ? '3499' : '3999' }) + }); } else { setFormData({ ...formData, @@ -593,40 +590,85 @@ function FilamentForm({ /> - {/* Checkboxes grouped together horizontally */} -
- + {/* Quantity inputs for refill, vakuum, and otvoreno */} +
+ + { + if (!formData.refill) return 0; + const num = parseInt(formData.refill); + return isNaN(num) ? (formData.refill.toLowerCase() === 'da' ? 1 : 0) : num; + })()} + onChange={(e) => { + const value = parseInt(e.target.value) || 0; + handleChange({ + target: { + name: 'refill', + value: value > 0 ? value.toString() : 'Ne' + } + } as any); + }} + min="0" + step="1" + placeholder="0" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
- +
+ + { + if (!formData.vakum) return 0; + const match = formData.vakum.match(/^(\d+)\s*vakuum/); + if (match) return parseInt(match[1]); + return formData.vakum.toLowerCase().includes('vakuum') ? 1 : 0; + })()} + onChange={(e) => { + const value = parseInt(e.target.value) || 0; + handleChange({ + target: { + name: 'vakum', + value: value > 0 ? `${value} vakuum` : 'Ne' + } + } as any); + }} + min="0" + step="1" + placeholder="0" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
- +
+ + { + if (!formData.otvoreno) return 0; + const match = formData.otvoreno.match(/^(\d+)\s*otvorena/); + if (match) return parseInt(match[1]); + return formData.otvoreno.toLowerCase().includes('otvorena') ? 1 : 0; + })()} + onChange={(e) => { + const value = parseInt(e.target.value) || 0; + handleChange({ + target: { + name: 'otvoreno', + value: value > 0 ? `${value} otvorena` : 'Ne' + } + } as any); + }} + min="0" + step="1" + placeholder="0" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500" + />