diff --git a/app/upadaj/colors/page.tsx b/app/upadaj/colors/page.tsx index bdcc19c..6c5918e 100644 --- a/app/upadaj/colors/page.tsx +++ b/app/upadaj/colors/page.tsx @@ -4,6 +4,7 @@ import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { colorService } from '@/src/services/api'; import { bambuLabColors, getColorHex } from '@/src/data/bambuLabColorsComplete'; +import { BulkPriceEditor } from '@/src/components/BulkPriceEditor'; import '@/src/styles/select.css'; interface Color { @@ -221,7 +222,7 @@ export default function ColorsManagement() { />

Upravljanje bojama

-
+
{!showAddForm && !editingColor && ( )} + {selectedColors.size > 0 && ( + + {showModal && ( +
+
+
+

+ Masovno editovanje cena +

+ +
+ + {/* Filters */} +
+

Filteri:

+ +
+
+ + +
+ +
+ + +
+ +
+ + setSearchTerm(e.target.value)} + placeholder="Pretraži boju, tip, finiš..." + className="w-full px-3 py-2 border rounded dark:bg-gray-600 dark:border-gray-500 dark:text-white" + /> +
+
+ +

+ Prikazano: {filteredFilaments.length} filament(a) +

+
+ + {/* Price inputs */} +
+

+ Nove cene za filtrirane filamente: +

+
+
+ + setNewRefillPrice(e.target.value)} + placeholder="Npr. 3499" + className="w-full px-3 py-2 border rounded dark:bg-gray-600 dark:border-gray-500 dark:text-white" + /> +
+
+ + setNewSpoolPrice(e.target.value)} + placeholder="Npr. 3999" + className="w-full px-3 py-2 border rounded dark:bg-gray-600 dark:border-gray-500 dark:text-white" + /> +
+
+

+ Napomena: Možete promeniti samo refill, samo spulna, ili obe cene. Prazna polja će zadržati postojeće cene. +

+
+ + {/* Preview */} +
+

+ Pregled filamenata ({colorGroups.length} boja): +

+ {colorGroups.length === 0 ? ( +

+ Nema filamenata koji odgovaraju filterima +

+ ) : ( + + + + + + + + + + + + + + {colorGroups.map(([color, filamentGroup]) => + filamentGroup.map((f, idx) => { + const prices = f.cena.split('/'); + const currentRefillPrice = parseInt(prices[0]) || 3499; + const currentSpoolPrice = prices.length > 1 ? parseInt(prices[1]) || 3999 : 3999; + + const refillPrice = parseInt(newRefillPrice); + const spoolPrice = parseInt(newSpoolPrice); + + const finalRefillPrice = !isNaN(refillPrice) ? refillPrice : currentRefillPrice; + const finalSpoolPrice = !isNaN(spoolPrice) ? spoolPrice : currentSpoolPrice; + + let newPriceString = ''; + if (f.refill > 0 && f.spulna > 0) { + newPriceString = `${finalRefillPrice}/${finalSpoolPrice}`; + } else if (f.refill > 0) { + newPriceString = String(finalRefillPrice); + } else if (f.spulna > 0) { + newPriceString = String(finalSpoolPrice); + } + + const priceChanged = newPriceString !== f.cena; + + return ( + + + + + + + + + + ); + }) + )} + +
BojaTipFinišRefillSpulnaTrenutna cenaNova cena
{f.boja}{f.tip}{f.finish}{f.refill}{f.spulna}{f.cena} + {newPriceString || f.cena} +
+ )} +
+ + {/* Actions */} +
+ +
+ + +
+
+
+
+ )} + + ); +} diff --git a/src/components/BulkPriceEditor.tsx b/src/components/BulkPriceEditor.tsx new file mode 100644 index 0000000..19a1a3e --- /dev/null +++ b/src/components/BulkPriceEditor.tsx @@ -0,0 +1,288 @@ +import React, { useState, useEffect } from 'react'; +import { colorService } from '@/src/services/api'; + +interface Color { + id: string; + name: string; + hex: string; + cena_refill?: number; + cena_spulna?: number; +} + +interface BulkPriceEditorProps { + colors: Color[]; + onUpdate: () => void; +} + +export function BulkPriceEditor({ colors, onUpdate }: BulkPriceEditorProps) { + const [showModal, setShowModal] = useState(false); + const [loading, setLoading] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + const [priceChanges, setPriceChanges] = useState>({}); + const [globalRefillPrice, setGlobalRefillPrice] = useState(''); + const [globalSpulnaPrice, setGlobalSpulnaPrice] = useState(''); + + // Filter colors based on search + const filteredColors = colors.filter(color => + color.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + // Apply global price to all filtered colors + const applyGlobalRefillPrice = () => { + if (!globalRefillPrice) return; + const price = parseInt(globalRefillPrice); + if (isNaN(price) || price < 0) return; + + const updates: Record = { ...priceChanges }; + filteredColors.forEach(color => { + updates[color.id] = { + ...updates[color.id], + cena_refill: price + }; + }); + setPriceChanges(updates); + }; + + const applyGlobalSpulnaPrice = () => { + if (!globalSpulnaPrice) return; + const price = parseInt(globalSpulnaPrice); + if (isNaN(price) || price < 0) return; + + const updates: Record = { ...priceChanges }; + filteredColors.forEach(color => { + updates[color.id] = { + ...updates[color.id], + cena_spulna: price + }; + }); + setPriceChanges(updates); + }; + + // Update individual color price + const updatePrice = (colorId: string, field: 'cena_refill' | 'cena_spulna', value: string) => { + const price = parseInt(value); + if (value === '' || (price >= 0 && !isNaN(price))) { + setPriceChanges(prev => ({ + ...prev, + [colorId]: { + ...prev[colorId], + [field]: value === '' ? undefined : price + } + })); + } + }; + + // Get effective price (with changes or original) + const getEffectivePrice = (color: Color, field: 'cena_refill' | 'cena_spulna'): number => { + return priceChanges[color.id]?.[field] ?? color[field] ?? (field === 'cena_refill' ? 3499 : 3999); + }; + + // Save all changes + const handleSave = async () => { + const colorUpdates = Object.entries(priceChanges).filter(([_, changes]) => + changes.cena_refill !== undefined || changes.cena_spulna !== undefined + ); + + if (colorUpdates.length === 0) { + alert('Nema promena za čuvanje'); + return; + } + + if (!confirm(`Želite da sačuvate promene za ${colorUpdates.length} boja?`)) { + return; + } + + setLoading(true); + try { + // Update each color individually + await Promise.all( + colorUpdates.map(([colorId, changes]) => { + const color = colors.find(c => c.id === colorId); + if (!color) return Promise.resolve(); + + return colorService.update(colorId, { + name: color.name, + hex: color.hex, + cena_refill: changes.cena_refill ?? color.cena_refill, + cena_spulna: changes.cena_spulna ?? color.cena_spulna + }); + }) + ); + + alert(`Uspešno ažurirano ${colorUpdates.length} boja!`); + setPriceChanges({}); + setGlobalRefillPrice(''); + setGlobalSpulnaPrice(''); + setSearchTerm(''); + onUpdate(); + setShowModal(false); + } catch (error) { + console.error('Error updating prices:', error); + alert('Greška pri ažuriranju cena'); + } finally { + setLoading(false); + } + }; + + return ( + <> + + + {showModal && ( +
+
+
+

+ Masovno editovanje cena +

+ +
+ + {/* Global price controls */} +
+

Primeni na sve prikazane boje:

+
+
+ setGlobalRefillPrice(e.target.value)} + placeholder="Refill cena" + className="flex-1 px-3 py-2 border rounded dark:bg-gray-600 dark:border-gray-500 dark:text-white" + /> + +
+
+ setGlobalSpulnaPrice(e.target.value)} + placeholder="Spulna cena" + className="flex-1 px-3 py-2 border rounded dark:bg-gray-600 dark:border-gray-500 dark:text-white" + /> + +
+
+
+ + {/* Search */} +
+ setSearchTerm(e.target.value)} + placeholder="Pretraži boje..." + className="w-full px-3 py-2 border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white" + /> +

+ Prikazano: {filteredColors.length} od {colors.length} boja + {Object.keys(priceChanges).length > 0 && ` · ${Object.keys(priceChanges).length} promena`} +

+
+ + {/* Color list */} +
+ + + + + + + + + + {filteredColors.map(color => { + const hasChanges = priceChanges[color.id] !== undefined; + return ( + + + + + + ); + })} + +
BojaRefill cenaSpulna cena
+
+
+ {color.name} +
+
+ updatePrice(color.id, 'cena_refill', e.target.value)} + className="w-full px-2 py-1 border rounded dark:bg-gray-600 dark:border-gray-500 dark:text-white" + /> + + updatePrice(color.id, 'cena_spulna', e.target.value)} + className="w-full px-2 py-1 border rounded dark:bg-gray-600 dark:border-gray-500 dark:text-white" + /> +
+
+ + {/* Actions */} +
+ +
+ + +
+
+
+
+ )} + + ); +}