diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 863431a..bb0b611 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "axios": "^1.6.2", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "xlsx": "^0.18.5" }, "devDependencies": { "@types/react": "^18.2.43", @@ -1158,6 +1159,15 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1253,6 +1263,28 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1272,6 +1304,18 @@ "dev": true, "license": "MIT" }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -1457,6 +1501,15 @@ "node": ">= 6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1844,6 +1897,18 @@ "node": ">=0.10.0" } }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -1935,6 +2000,45 @@ } } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 2f5d10b..897d84e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,8 @@ "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", - "axios": "^1.6.2" + "axios": "^1.6.2", + "xlsx": "^0.18.5" }, "devDependencies": { "@types/react": "^18.2.43", diff --git a/frontend/src/App.css b/frontend/src/App.css index 4a52a05..8d424ca 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -105,6 +105,17 @@ header h1 { background: #dc2626; } +.btn-success { + background: #10b981; + color: white; +} + +.btn-success:hover { + background: #059669; + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(16, 185, 129, 0.4); +} + .loading { text-align: center; padding: 40px; diff --git a/frontend/src/components/GuestList.jsx b/frontend/src/components/GuestList.jsx index ffdd127..ae95166 100644 --- a/frontend/src/components/GuestList.jsx +++ b/frontend/src/components/GuestList.jsx @@ -1,5 +1,6 @@ import { useState } from 'react' import { deleteGuest, deleteGuestsBulk } from '../api/api' +import * as XLSX from 'xlsx' import './GuestList.css' function GuestList({ guests, onEdit, onUpdate }) { @@ -80,6 +81,50 @@ function GuestList({ guests, onEdit, onUpdate }) { } } + const exportToExcel = () => { + // Prepare data for export + const exportData = guests.map(guest => ({ + 'שם פרטי': guest.first_name, + 'שם משפחה': guest.last_name, + 'אימייל': guest.email || '', + 'טלפון': guest.phone_number || '', + 'סטטוס אישור': getRsvpLabel(guest.rsvp_status), + 'העדפת ארוחה': guest.meal_preference || '', + 'פלאס ואן': guest.has_plus_one ? 'כן' : 'לא', + 'שם פלאס ואן': guest.plus_one_name || '', + 'מספר שולחן': guest.table_number || '', + 'מקור': guest.owner || '' + })) + + // Create worksheet + const ws = XLSX.utils.json_to_sheet(exportData) + + // Set column widths + ws['!cols'] = [ + { wch: 15 }, // שם פרטי + { wch: 15 }, // שם משפחה + { wch: 25 }, // אימייל + { wch: 15 }, // טלפון + { wch: 12 }, // סטטוס אישור + { wch: 15 }, // העדפת ארוחה + { wch: 10 }, // פלאס ואן + { wch: 15 }, // שם פלאס ואן + { wch: 12 }, // מספר שולחן + { wch: 20 } // מקור + ] + + // Create workbook + const wb = XLSX.utils.book_new() + XLSX.utils.book_append_sheet(wb, ws, 'רשימת אורחים') + + // Generate file name with date + const date = new Date().toISOString().split('T')[0] + const fileName = `guest-list-${date}.xlsx` + + // Save file + XLSX.writeFile(wb, fileName) + } + if (guests.length === 0) { return (
@@ -93,6 +138,9 @@ function GuestList({ guests, onEdit, onUpdate }) {

רשימת אורחים ({guests.length})

+