diff --git a/package-lock.json b/package-lock.json index 9685b7d..bef50ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "hotelnext", + "name": "hotelsync", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "hotelnext", + "name": "hotelsync", "version": "0.1.0", "dependencies": { "clsx": "^2.1.1", @@ -14,7 +14,8 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.0", - "tailwind-merge": "^2.5.2" + "tailwind-merge": "^2.5.2", + "xlsx": "^0.18.5" }, "devDependencies": { "@types/react": "^18.3.5", @@ -1268,6 +1269,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/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -1437,6 +1447,19 @@ ], "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/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -1484,6 +1507,15 @@ "node": ">=6" } }, + "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/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1501,6 +1533,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/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1672,6 +1716,15 @@ "node": ">=8" } }, + "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/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -2452,6 +2505,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/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -2739,6 +2804,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/package.json b/package.json index 6b85be7..f03b4b9 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,14 @@ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" }, "dependencies": { + "clsx": "^2.1.1", + "date-fns": "^3.6.0", + "lucide-react": "^0.446.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.26.0", - "date-fns": "^3.6.0", - "lucide-react": "^0.446.0", - "clsx": "^2.1.1", - "tailwind-merge": "^2.5.2" + "tailwind-merge": "^2.5.2", + "xlsx": "^0.18.5" }, "devDependencies": { "@types/react": "^18.3.5", diff --git a/src/pages/TTLockPage.tsx b/src/pages/TTLockPage.tsx index f3baba0..365a943 100644 --- a/src/pages/TTLockPage.tsx +++ b/src/pages/TTLockPage.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useCallback, useRef } from 'react' +import * as XLSX from 'xlsx' import { KeyRound, Save, RefreshCw, Check, X, AlertCircle, ChevronDown, ChevronRight, Trash2, Plus, Eye, EyeOff, @@ -174,21 +175,17 @@ export function TTLockPage() { const handleImportFile = async (file: File) => { try { - const { read, utils } = await import('xlsx') const buf = await file.arrayBuffer() - const wb = read(buf, { type: 'array' }) + const wb = XLSX.read(buf, { type: 'array' }) const ws = wb.Sheets[wb.SheetNames[0]] - // header: true — первая строка как ключи - const rows = utils.sheet_to_json>(ws, { defval: '' }) + const rows = XLSX.utils.sheet_to_json(ws, { defval: '' }) as Array> const parsed: ImportRow[] = rows.map(row => { const doorName = String(row['Door Name'] ?? '').trim() const rawMac = String(row['Lock Mac'] ?? '').trim() const mac = rawMac.replace(/:/g, '').toUpperCase() - // Ищем комнату по номеру - const matched = rooms.find(r => String(r.number).trim() === doorName) - // Уже привязана? + const matched = rooms.find(r => String(r.number).trim() === doorName) const alreadyMapped = matched ? mappings.some(m => m.roomId === matched.id) : false return {