From 0df9d5d29428aac7537907f5017d036d445a4dfb Mon Sep 17 00:00:00 2001 From: DaX Date: Sat, 5 Jul 2025 14:48:31 +0200 Subject: [PATCH] Add sale management feature for admin panel - Add database migration for sale fields (percentage, active, dates) - Update API to handle sale operations and bulk updates - Create SaleManager component for admin interface - Update FilamentTableV2 to display sale prices on frontend - Add sale column in admin dashboard - Implement sale price calculations with strikethrough styling --- api-update-sale.tar.gz | Bin 0 -> 2357 bytes api/server.js | 54 +++++- app/upadaj/dashboard/page.tsx | 23 +++ database/migrations/014_add_sale_fields.sql | 10 ++ scripts/deploy-api-update.sh | 39 +++++ scripts/run-sale-migration.sh | 17 ++ src/components/FilamentTableV2.tsx | 24 ++- src/components/SaleManager.tsx | 177 ++++++++++++++++++++ src/services/api.ts | 11 ++ src/types/filament.ts | 4 + 10 files changed, 354 insertions(+), 5 deletions(-) create mode 100644 api-update-sale.tar.gz create mode 100644 database/migrations/014_add_sale_fields.sql create mode 100755 scripts/deploy-api-update.sh create mode 100755 scripts/run-sale-migration.sh create mode 100644 src/components/SaleManager.tsx diff --git a/api-update-sale.tar.gz b/api-update-sale.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..b99fd732919717a7d30816d44b52da75341919f2 GIT binary patch literal 2357 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 b)|dVj{P}o3o{#6_=^g(Ewnnk504@Lk81|gh literal 0 HcmV?d00001 diff --git a/api/server.js b/api/server.js index f571424..04738c4 100644 --- a/api/server.js +++ b/api/server.js @@ -153,7 +153,7 @@ app.post('/api/filaments', authenticateToken, async (req, res) => { app.put('/api/filaments/:id', authenticateToken, async (req, res) => { const { id } = req.params; - const { tip, finish, boja, boja_hex, refill, spulna, cena } = req.body; + const { tip, finish, boja, boja_hex, refill, spulna, cena, sale_percentage, sale_active, sale_start_date, sale_end_date } = req.body; try { // Ensure refill and spulna are numbers @@ -165,9 +165,12 @@ app.put('/api/filaments/:id', authenticateToken, async (req, res) => { `UPDATE filaments SET tip = $1, finish = $2, boja = $3, boja_hex = $4, refill = $5, spulna = $6, kolicina = $7, cena = $8, + sale_percentage = $9, sale_active = $10, + sale_start_date = $11, sale_end_date = $12, updated_at = CURRENT_TIMESTAMP - WHERE id = $9 RETURNING *`, - [tip, finish, boja, boja_hex, refillNum, spulnaNum, kolicina, cena, id] + WHERE id = $13 RETURNING *`, + [tip, finish, boja, boja_hex, refillNum, spulnaNum, kolicina, cena, + sale_percentage || 0, sale_active || false, sale_start_date, sale_end_date, id] ); res.json(result.rows[0]); } catch (error) { @@ -188,6 +191,51 @@ app.delete('/api/filaments/:id', authenticateToken, async (req, res) => { } }); +// Bulk sale update endpoint +app.post('/api/filaments/sale/bulk', authenticateToken, async (req, res) => { + const { filamentIds, salePercentage, saleStartDate, saleEndDate, enableSale } = req.body; + + try { + let query; + let params; + + if (filamentIds && filamentIds.length > 0) { + // Update specific filaments + query = ` + UPDATE filaments + SET sale_percentage = $1, + sale_active = $2, + sale_start_date = $3, + sale_end_date = $4, + updated_at = CURRENT_TIMESTAMP + WHERE id = ANY($5) + RETURNING *`; + params = [salePercentage || 0, enableSale || false, saleStartDate, saleEndDate, filamentIds]; + } else { + // Update all filaments + query = ` + UPDATE filaments + SET sale_percentage = $1, + sale_active = $2, + sale_start_date = $3, + sale_end_date = $4, + updated_at = CURRENT_TIMESTAMP + RETURNING *`; + params = [salePercentage || 0, enableSale || false, saleStartDate, saleEndDate]; + } + + const result = await pool.query(query, params); + res.json({ + success: true, + updatedCount: result.rowCount, + updatedFilaments: result.rows + }); + } catch (error) { + console.error('Error updating sale:', error); + res.status(500).json({ error: 'Failed to update sale' }); + } +}); + app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); }); \ No newline at end of file diff --git a/app/upadaj/dashboard/page.tsx b/app/upadaj/dashboard/page.tsx index 99a4cae..e9f37f3 100644 --- a/app/upadaj/dashboard/page.tsx +++ b/app/upadaj/dashboard/page.tsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'; import { filamentService, colorService } from '@/src/services/api'; import { Filament } from '@/src/types/filament'; import { trackEvent } from '@/src/components/MatomoAnalytics'; +import { SaleManager } from '@/src/components/SaleManager'; // Removed unused imports for Bambu Lab color categorization import '@/src/styles/select.css'; @@ -339,6 +340,11 @@ export default function AdminDashboard() { Obriši izabrane ({selectedFilaments.size}) )} + + + {showSaleModal && ( +
+
+

+ Upravljanje popustima +

+ +
+
+ + + {!applyToAll && ( +

+ Izabrano: {selectedFilaments.size} filament{selectedFilaments.size === 1 ? '' : 'a'} +

+ )} +
+ +
+ + setSalePercentage(parseInt(e.target.value) || 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" + /> +
+ +
+ + setSaleStartDate(e.target.value)} + min={getCurrentDateTime()} + 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" + /> +
+ +
+ + setSaleEndDate(e.target.value)} + min={saleStartDate || getCurrentDateTime()} + 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" + /> +
+ +
+ +
+
+ +
+ + +
+
+
+ )} + + ); +} \ No newline at end of file diff --git a/src/services/api.ts b/src/services/api.ts index 73b2bd2..394859c 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -87,6 +87,17 @@ export const filamentService = { const response = await api.delete(`/filaments/${id}`); return response.data; }, + + updateBulkSale: async (data: { + filamentIds?: string[]; + salePercentage: number; + saleStartDate?: string; + saleEndDate?: string; + enableSale: boolean; + }) => { + const response = await api.post('/filaments/sale/bulk', data); + return response.data; + }, }; export default api; \ No newline at end of file diff --git a/src/types/filament.ts b/src/types/filament.ts index 7e7d092..1b62ea6 100644 --- a/src/types/filament.ts +++ b/src/types/filament.ts @@ -11,4 +11,8 @@ export interface Filament { status?: string; created_at?: string; // Using snake_case to match database updated_at?: string; // Using snake_case to match database + sale_percentage?: number; + sale_active?: boolean; + sale_start_date?: string; + sale_end_date?: string; } \ No newline at end of file