rostyslavpwork commited on
Commit
8ac6f9c
·
verified ·
1 Parent(s): 5e4e9ba

Add 3 files

Browse files
Files changed (3) hide show
  1. README.md +7 -5
  2. index.html +1211 -19
  3. prompts.txt +0 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Inventarization
3
- emoji: 🦀
4
- colorFrom: pink
5
- colorTo: indigo
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: inventarization
3
+ emoji: 🐳
4
+ colorFrom: yellow
5
+ colorTo: red
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1211 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="uk">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Інвентаризація</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
9
+ <style>
10
+ :root {
11
+ --color-match: #10b981;
12
+ --color-missing: #ef4444;
13
+ --color-excess: #f59e0b;
14
+ --color-unchecked: #9ca3af;
15
+ }
16
+
17
+ @media (prefers-color-scheme: dark) {
18
+ :root {
19
+ --color-bg: #1f2937;
20
+ --color-text: #f3f4f6;
21
+ --color-card: #374151;
22
+ }
23
+ }
24
+
25
+ @media (prefers-color-scheme: light) {
26
+ :root {
27
+ --color-bg: #f3f4f6;
28
+ --color-text: #111827;
29
+ --color-card: #ffffff;
30
+ }
31
+ }
32
+
33
+ body {
34
+ background-color: var(--color-bg);
35
+ color: var(--color-text);
36
+ min-height: 100vh;
37
+ padding-bottom: env(safe-area-inset-bottom);
38
+ }
39
+
40
+ .tab-content {
41
+ display: none;
42
+ }
43
+
44
+ .tab-content.active {
45
+ display: block;
46
+ }
47
+
48
+ .status-match {
49
+ background-color: var(--color-match);
50
+ }
51
+
52
+ .status-missing {
53
+ background-color: var(--color-missing);
54
+ }
55
+
56
+ .status-excess {
57
+ background-color: var(--color-excess);
58
+ }
59
+
60
+ .status-unchecked {
61
+ background-color: var(--color-unchecked);
62
+ }
63
+
64
+ .toast {
65
+ animation: fadeInOut 3s ease-in-out forwards;
66
+ }
67
+
68
+ @keyframes fadeInOut {
69
+ 0% { opacity: 0; transform: translateY(20px); }
70
+ 10% { opacity: 1; transform: translateY(0); }
71
+ 90% { opacity: 1; transform: translateY(0); }
72
+ 100% { opacity: 0; transform: translateY(-20px); }
73
+ }
74
+
75
+ .progress-bar {
76
+ height: 1rem;
77
+ border-radius: 0.5rem;
78
+ background-color: #e5e7eb;
79
+ overflow: hidden;
80
+ }
81
+
82
+ .progress-fill {
83
+ height: 100%;
84
+ background: linear-gradient(90deg, var(--color-match), var(--color-excess));
85
+ transition: width 0.3s ease;
86
+ }
87
+
88
+ input[type="number"]::-webkit-inner-spin-button,
89
+ input[type="number"]::-webkit-outer-spin-button {
90
+ -webkit-appearance: none;
91
+ margin: 0;
92
+ }
93
+
94
+ input[type="number"] {
95
+ -moz-appearance: textfield;
96
+ }
97
+ </style>
98
+ </head>
99
+ <body class="font-sans">
100
+ <div class="container mx-auto px-4 py-6 max-w-4xl">
101
+ <!-- Header -->
102
+ <header class="mb-8">
103
+ <h1 class="text-3xl font-bold text-center">Інвентаризація v1.0</h1>
104
+ </header>
105
+
106
+ <!-- Tabs Navigation -->
107
+ <div class="flex border-b border-gray-300 mb-6">
108
+ <button class="tab-btn py-2 px-4 font-medium border-b-2 border-transparent hover:border-gray-400 transition" data-tab="scan">Сканування</button>
109
+ <button class="tab-btn py-2 px-4 font-medium border-b-2 border-transparent hover:border-gray-400 transition" data-tab="list">Список товарів</button>
110
+ <button class="tab-btn py-2 px-4 font-medium border-b-2 border-transparent hover:border-gray-400 transition" data-tab="results">Результати</button>
111
+ </div>
112
+
113
+ <!-- Toast Notifications -->
114
+ <div id="toast-container" class="fixed bottom-4 right-4 z-50 space-y-2"></div>
115
+
116
+ <!-- Scan Tab Content -->
117
+ <div id="scan" class="tab-content active">
118
+ <div class="space-y-6">
119
+ <!-- Input Fields -->
120
+ <div class="space-y-4">
121
+ <div>
122
+ <label for="sku-input" class="block text-sm font-medium mb-1">Артикул товару</label>
123
+ <div class="flex">
124
+ <input type="text" id="sku-input" class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Введіть або відскануйте артику��" inputmode="numeric">
125
+ <button id="sku-submit" class="px-4 py-2 bg-blue-500 text-white rounded-r-lg hover:bg-blue-600 transition">➜</button>
126
+ </div>
127
+ </div>
128
+
129
+ <div>
130
+ <label for="quantity-input" class="block text-sm font-medium mb-1">Зміна кількості</label>
131
+ <div class="flex">
132
+ <input type="number" id="quantity-input" disabled class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Число(+) або 0+Число(-)">
133
+ <button id="quantity-submit" disabled class="px-4 py-2 bg-blue-500 text-white rounded-r-lg hover:bg-blue-600 transition">➜</button>
134
+ </div>
135
+ <p class="text-xs text-gray-500 mt-1">Введіть число для додавання або 0+число для віднімання (напр. 5 або 05)</p>
136
+ </div>
137
+ </div>
138
+
139
+ <!-- Item Card -->
140
+ <div id="item-card" class="bg-white/10 p-4 rounded-lg shadow hidden">
141
+ <div class="flex justify-between items-start mb-2">
142
+ <h3 class="text-lg font-semibold" id="item-name">Назва товару</h3>
143
+ <span id="item-status" class="px-2 py-1 rounded-full text-xs font-medium text-white">Не перевірено</span>
144
+ </div>
145
+ <div class="grid grid-cols-2 gap-4">
146
+ <div>
147
+ <p class="text-sm text-gray-400">Артикул</p>
148
+ <p id="item-sku" class="font-mono">123456789</p>
149
+ </div>
150
+ <div>
151
+ <p class="text-sm text-gray-400">Очікувано</p>
152
+ <p id="item-expected">0</p>
153
+ </div>
154
+ <div>
155
+ <p class="text-sm text-gray-400">Фактично</p>
156
+ <p id="item-actual">0</p>
157
+ </div>
158
+ <div>
159
+ <p class="text-sm text-gray-400">Різниця</p>
160
+ <p id="item-difference">0</p>
161
+ </div>
162
+ </div>
163
+ </div>
164
+
165
+ <!-- Action Buttons -->
166
+ <div class="flex flex-wrap gap-2 mt-6">
167
+ <button id="open-file-btn" class="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition flex items-center gap-2">
168
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
169
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
170
+ </svg>
171
+ Відкрити файл
172
+ </button>
173
+ <button id="save-results-btn" disabled class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition flex items-center gap-2">
174
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
175
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4" />
176
+ </svg>
177
+ Зберегти інвентаризацію
178
+ </button>
179
+ <button id="clear-data-btn" class="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition flex items-center gap-2">
180
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
181
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
182
+ </svg>
183
+ Очистити дані
184
+ </button>
185
+ </div>
186
+
187
+ <input type="file" id="file-input" accept=".xlsx,.xls" class="hidden">
188
+ </div>
189
+ </div>
190
+
191
+ <!-- List Tab Content -->
192
+ <div id="list" class="tab-content">
193
+ <div class="space-y-6">
194
+ <!-- File Info -->
195
+ <div id="file-info" class="bg-white/10 p-4 rounded-lg shadow hidden">
196
+ <h3 class="font-medium mb-2">Інформація про файл</h3>
197
+ <div class="grid grid-cols-2 gap-4">
198
+ <div>
199
+ <p class="text-sm text-gray-400">Назва файлу</p>
200
+ <p id="file-name">-</p>
201
+ </div>
202
+ <div>
203
+ <p class="text-sm text-gray-400">Дата завантаження</p>
204
+ <p id="file-date">-</p>
205
+ </div>
206
+ </div>
207
+ </div>
208
+
209
+ <!-- Progress Stats -->
210
+ <div class="bg-white/10 p-4 rounded-lg shadow">
211
+ <h3 class="font-medium mb-2">Прогрес інвентаризації</h3>
212
+ <div class="progress-bar mb-2">
213
+ <div id="progress-fill" class="progress-fill" style="width: 0%"></div>
214
+ </div>
215
+ <div class="grid grid-cols-2 md:grid-cols-5 gap-2 text-sm">
216
+ <div class="bg-gray-100/10 p-2 rounded text-center">
217
+ <p class="text-gray-400">Всього</p>
218
+ <p id="total-count" class="font-bold">0</p>
219
+ </div>
220
+ <div class="bg-gray-100/10 p-2 rounded text-center">
221
+ <p class="text-gray-400">Перевірено</p>
222
+ <p id="checked-count" class="font-bold">0</p>
223
+ </div>
224
+ <div class="bg-green-500/20 p-2 rounded text-center">
225
+ <p class="text-gray-400">Відповідність</p>
226
+ <p id="match-count" class="font-bold">0</p>
227
+ </div>
228
+ <div class="bg-red-500/20 p-2 rounded text-center">
229
+ <p class="text-gray-400">Нестача</p>
230
+ <p id="missing-count" class="font-bold">0</p>
231
+ </div>
232
+ <div class="bg-yellow-500/20 p-2 rounded text-center">
233
+ <p class="text-gray-400">Надлишок</p>
234
+ <p id="excess-count" class="font-bold">0</p>
235
+ </div>
236
+ </div>
237
+ </div>
238
+
239
+ <!-- Filters -->
240
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
241
+ <div>
242
+ <label for="status-filter" class="block text-sm font-medium mb-1">Фільтр за статусом</label>
243
+ <select id="status-filter" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
244
+ <option value="all">Всі</option>
245
+ <option value="match">Відповідність</option>
246
+ <option value="missing">Нестача</option>
247
+ <option value="excess">Надлишок</option>
248
+ <option value="unchecked">Не перевірено</option>
249
+ </select>
250
+ </div>
251
+ <div>
252
+ <label for="search-input" class="block text-sm font-medium mb-1">Пошук за назвою</label>
253
+ <div class="flex">
254
+ <input type="text" id="search-input" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Введіть назву товару">
255
+ <button id="clear-search" class="ml-2 px-3 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition">
256
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
257
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
258
+ </svg>
259
+ </button>
260
+ </div>
261
+ </div>
262
+ </div>
263
+
264
+ <!-- Items Table -->
265
+ <div class="overflow-x-auto">
266
+ <table id="items-table" class="w-full border-collapse">
267
+ <thead>
268
+ <tr class="border-b border-gray-300">
269
+ <th class="px-4 py-2 text-left">Статус</th>
270
+ <th class="px-4 py-2 text-left">Артикул</th>
271
+ <th class="px-4 py-2 text-left">Назва</th>
272
+ <th class="px-4 py-2 text-right">Очікувано</th>
273
+ <th class="px-4 py-2 text-right">Фактично</th>
274
+ </tr>
275
+ </thead>
276
+ <tbody id="items-table-body">
277
+ <!-- Items will be inserted here dynamically -->
278
+ </tbody>
279
+ </table>
280
+ </div>
281
+
282
+ <div id="no-items-message" class="text-center py-8 text-gray-500">
283
+ <p>Немає товарів для відображення. Завантажте файл з даними.</p>
284
+ </div>
285
+ </div>
286
+ </div>
287
+
288
+ <!-- Results Tab Content -->
289
+ <div id="results" class="tab-content">
290
+ <div class="space-y-6">
291
+ <div class="bg-white/10 p-4 rounded-lg shadow">
292
+ <h2 class="text-xl font-bold mb-2">Результати інвентаризації</h2>
293
+ <p id="results-summary" class="text-gray-400">Знайдено 0 розбіжностей</p>
294
+ </div>
295
+
296
+ <!-- Results Table -->
297
+ <div class="overflow-x-auto">
298
+ <table id="results-table" class="w-full border-collapse">
299
+ <thead>
300
+ <tr class="border-b border-gray-300">
301
+ <th class="px-4 py-2 text-left">Статус</th>
302
+ <th class="px-4 py-2 text-left">Артикул</th>
303
+ <th class="px-4 py-2 text-left">Назва</th>
304
+ <th class="px-4 py-2 text-right">Очікувано</th>
305
+ <th class="px-4 py-2 text-right">Фактично</th>
306
+ <th class="px-4 py-2 text-right">Різниця</th>
307
+ </tr>
308
+ </thead>
309
+ <tbody id="results-table-body">
310
+ <!-- Results will be inserted here dynamically -->
311
+ </tbody>
312
+ </table>
313
+ </div>
314
+
315
+ <div id="no-results-message" class="text-center py-8 text-gray-500">
316
+ <p>Немає розбіжностей для відображення.</p>
317
+ </div>
318
+ </div>
319
+ </div>
320
+ </div>
321
+
322
+ <script>
323
+ // Global variables
324
+ let items = [];
325
+ let currentItem = null;
326
+ let fileMetadata = {
327
+ fileName: null,
328
+ fileDate: null,
329
+ totalItems: 0
330
+ };
331
+
332
+ // DOM elements
333
+ const skuInput = document.getElementById('sku-input');
334
+ const skuSubmit = document.getElementById('sku-submit');
335
+ const quantityInput = document.getElementById('quantity-input');
336
+ const quantitySubmit = document.getElementById('quantity-submit');
337
+ const itemCard = document.getElementById('item-card');
338
+ const itemName = document.getElementById('item-name');
339
+ const itemSku = document.getElementById('item-sku');
340
+ const itemExpected = document.getElementById('item-expected');
341
+ const itemActual = document.getElementById('item-actual');
342
+ const itemDifference = document.getElementById('item-difference');
343
+ const itemStatus = document.getElementById('item-status');
344
+ const openFileBtn = document.getElementById('open-file-btn');
345
+ const fileInput = document.getElementById('file-input');
346
+ const saveResultsBtn = document.getElementById('save-results-btn');
347
+ const clearDataBtn = document.getElementById('clear-data-btn');
348
+ const fileInfo = document.getElementById('file-info');
349
+ const fileName = document.getElementById('file-name');
350
+ const fileDate = document.getElementById('file-date');
351
+ const progressFill = document.getElementById('progress-fill');
352
+ const totalCount = document.getElementById('total-count');
353
+ const checkedCount = document.getElementById('checked-count');
354
+ const matchCount = document.getElementById('match-count');
355
+ const missingCount = document.getElementById('missing-count');
356
+ const excessCount = document.getElementById('excess-count');
357
+ const statusFilter = document.getElementById('status-filter');
358
+ const searchInput = document.getElementById('search-input');
359
+ const clearSearch = document.getElementById('clear-search');
360
+ const itemsTableBody = document.getElementById('items-table-body');
361
+ const noItemsMessage = document.getElementById('no-items-message');
362
+ const resultsTableBody = document.getElementById('results-table-body');
363
+ const noResultsMessage = document.getElementById('no-results-message');
364
+ const resultsSummary = document.getElementById('results-summary');
365
+ const tabContents = document.querySelectorAll('.tab-content');
366
+ const tabButtons = document.querySelectorAll('.tab-btn');
367
+ const toastContainer = document.getElementById('toast-container');
368
+
369
+ // Initialize IndexedDB
370
+ let db;
371
+ const DB_NAME = 'inventoryDB';
372
+ const DB_VERSION = 1;
373
+ const ITEMS_STORE = 'items';
374
+ const METADATA_STORE = 'metadata';
375
+
376
+ function openDB() {
377
+ return new Promise((resolve, reject) => {
378
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
379
+
380
+ request.onupgradeneeded = (event) => {
381
+ const db = event.target.result;
382
+
383
+ if (!db.objectStoreNames.contains(ITEMS_STORE)) {
384
+ db.createObjectStore(ITEMS_STORE, { keyPath: 'sku' });
385
+ }
386
+
387
+ if (!db.objectStoreNames.contains(METADATA_STORE)) {
388
+ db.createObjectStore(METADATA_STORE, { keyPath: 'id' });
389
+ }
390
+ };
391
+
392
+ request.onsuccess = (event) => {
393
+ db = event.target.result;
394
+ resolve(db);
395
+ };
396
+
397
+ request.onerror = (event) => {
398
+ console.error('Error opening DB', event.target.error);
399
+ reject(event.target.error);
400
+ };
401
+ });
402
+ }
403
+
404
+ function saveItemToDB(item) {
405
+ return new Promise((resolve, reject) => {
406
+ const transaction = db.transaction([ITEMS_STORE], 'readwrite');
407
+ const store = transaction.objectStore(ITEMS_STORE);
408
+ const request = store.put(item);
409
+
410
+ request.onsuccess = () => resolve();
411
+ request.onerror = (event) => reject(event.target.error);
412
+ });
413
+ }
414
+
415
+ function saveAllItemsToDB(items) {
416
+ return new Promise((resolve, reject) => {
417
+ const transaction = db.transaction([ITEMS_STORE], 'readwrite');
418
+ const store = transaction.objectStore(ITEMS_STORE);
419
+
420
+ // Clear existing items
421
+ const clearRequest = store.clear();
422
+
423
+ clearRequest.onsuccess = () => {
424
+ // Add all new items
425
+ const requests = items.map(item => {
426
+ return new Promise((res, rej) => {
427
+ const addRequest = store.add(item);
428
+ addRequest.onsuccess = () => res();
429
+ addRequest.onerror = (event) => rej(event.target.error);
430
+ });
431
+ });
432
+
433
+ Promise.all(requests)
434
+ .then(() => resolve())
435
+ .catch(error => reject(error));
436
+ };
437
+
438
+ clearRequest.onerror = (event) => reject(event.target.error);
439
+ });
440
+ }
441
+
442
+ function getAllItemsFromDB() {
443
+ return new Promise((resolve, reject) => {
444
+ const transaction = db.transaction([ITEMS_STORE], 'readonly');
445
+ const store = transaction.objectStore(ITEMS_STORE);
446
+ const request = store.getAll();
447
+
448
+ request.onsuccess = (event) => resolve(event.target.result || []);
449
+ request.onerror = (event) => reject(event.target.error);
450
+ });
451
+ }
452
+
453
+ function clearItemsDB() {
454
+ return new Promise((resolve, reject) => {
455
+ const transaction = db.transaction([ITEMS_STORE], 'readwrite');
456
+ const store = transaction.objectStore(ITEMS_STORE);
457
+ const request = store.clear();
458
+
459
+ request.onsuccess = () => resolve();
460
+ request.onerror = (event) => reject(event.target.error);
461
+ });
462
+ }
463
+
464
+ function saveMetadataToDB(metadata) {
465
+ return new Promise((resolve, reject) => {
466
+ const transaction = db.transaction([METADATA_STORE], 'readwrite');
467
+ const store = transaction.objectStore(METADATA_STORE);
468
+ const request = store.put({ id: 'current', ...metadata });
469
+
470
+ request.onsuccess = () => resolve();
471
+ request.onerror = (event) => reject(event.target.error);
472
+ });
473
+ }
474
+
475
+ function getMetadataFromDB() {
476
+ return new Promise((resolve, reject) => {
477
+ const transaction = db.transaction([METADATA_STORE], 'readonly');
478
+ const store = transaction.objectStore(METADATA_STORE);
479
+ const request = store.get('current');
480
+
481
+ request.onsuccess = (event) => resolve(event.target.result || null);
482
+ request.onerror = (event) => reject(event.target.error);
483
+ });
484
+ }
485
+
486
+ function clearMetadataDB() {
487
+ return new Promise((resolve, reject) => {
488
+ const transaction = db.transaction([METADATA_STORE], 'readwrite');
489
+ const store = transaction.objectStore(METADATA_STORE);
490
+ const request = store.clear();
491
+
492
+ request.onsuccess = () => resolve();
493
+ request.onerror = (event) => reject(event.target.error);
494
+ });
495
+ }
496
+
497
+ // Initialize the app
498
+ async function initializeApp() {
499
+ try {
500
+ await openDB();
501
+
502
+ // Load saved data
503
+ const savedItems = await getAllItemsFromDB();
504
+ const savedMetadata = await getMetadataFromDB();
505
+
506
+ if (savedItems.length > 0) {
507
+ items = savedItems;
508
+ fileMetadata = savedMetadata || fileMetadata;
509
+ updateUI();
510
+ showToast('Відновлено попередню сесію', 'success');
511
+ }
512
+
513
+ // Set up event listeners
514
+ setupEventListeners();
515
+
516
+ // Focus on SKU input
517
+ skuInput.focus();
518
+ } catch (error) {
519
+ console.error('Initialization error:', error);
520
+ showToast('Помилка ініціалізації додатку', 'error');
521
+ }
522
+ }
523
+
524
+ // Set up event listeners
525
+ function setupEventListeners() {
526
+ // Tab switching
527
+ tabButtons.forEach(button => {
528
+ button.addEventListener('click', () => {
529
+ const tabId = button.getAttribute('data-tab');
530
+ switchTab(tabId);
531
+ });
532
+ });
533
+
534
+ // SKU input handling
535
+ skuInput.addEventListener('keydown', (e) => {
536
+ if (e.key === 'Enter') {
537
+ handleSkuInput();
538
+ }
539
+ });
540
+
541
+ skuSubmit.addEventListener('click', handleSkuInput);
542
+
543
+ // Quantity input handling
544
+ quantityInput.addEventListener('keydown', (e) => {
545
+ if (e.key === 'Enter') {
546
+ handleQuantityInput();
547
+ }
548
+ });
549
+
550
+ quantitySubmit.addEventListener('click', handleQuantityInput);
551
+
552
+ // File handling
553
+ openFileBtn.addEventListener('click', () => fileInput.click());
554
+ fileInput.addEventListener('change', handleFileUpload);
555
+
556
+ // Save results
557
+ saveResultsBtn.addEventListener('click', saveResultsToFile);
558
+
559
+ // Clear data
560
+ clearDataBtn.addEventListener('click', confirmClearData);
561
+
562
+ // Filter and search
563
+ statusFilter.addEventListener('change', updateUI);
564
+ searchInput.addEventListener('input', updateUI);
565
+ clearSearch.addEventListener('click', () => {
566
+ searchInput.value = '';
567
+ updateUI();
568
+ });
569
+
570
+ // Click outside to hide keyboard (for mobile)
571
+ document.addEventListener('click', (e) => {
572
+ if (!['sku-input', 'quantity-input', 'search-input'].includes(e.target.id)) {
573
+ document.activeElement.blur();
574
+ }
575
+ });
576
+ }
577
+
578
+ // Switch between tabs
579
+ function switchTab(tabId) {
580
+ // Update active tab button
581
+ tabButtons.forEach(button => {
582
+ if (button.getAttribute('data-tab') === tabId) {
583
+ button.classList.add('border-blue-500', 'text-blue-500');
584
+ button.classList.remove('border-transparent');
585
+ } else {
586
+ button.classList.remove('border-blue-500', 'text-blue-500');
587
+ button.classList.add('border-transparent');
588
+ }
589
+ });
590
+
591
+ // Update active tab content
592
+ tabContents.forEach(content => {
593
+ if (content.id === tabId) {
594
+ content.classList.add('active');
595
+ } else {
596
+ content.classList.remove('active');
597
+ }
598
+ });
599
+
600
+ // Focus on appropriate input
601
+ if (tabId === 'scan') {
602
+ skuInput.focus();
603
+ } else if (tabId === 'list') {
604
+ searchInput.focus();
605
+ }
606
+
607
+ // Update UI for the new tab
608
+ updateUI();
609
+ }
610
+
611
+ // Handle SKU input
612
+ function handleSkuInput() {
613
+ const sku = skuInput.value.trim();
614
+
615
+ if (!sku) {
616
+ showToast('Введіть артикул товару', 'warning');
617
+ vibrate();
618
+ return;
619
+ }
620
+
621
+ // Find item by SKU
622
+ currentItem = items.find(item => item.sku === sku);
623
+
624
+ if (currentItem) {
625
+ // Item found - display it
626
+ displayItem(currentItem);
627
+ quantityInput.disabled = false;
628
+ quantityInput.focus();
629
+ showToast(`Знайдено товар: ${currentItem.name}`, 'success');
630
+ } else {
631
+ // Item not found - ask to create new
632
+ const createNew = confirm(`Товар з артикулом ${sku} не знайдено. Створити новий запис?`);
633
+
634
+ if (createNew) {
635
+ const name = prompt('Введіть назву товару:');
636
+
637
+ if (name) {
638
+ currentItem = {
639
+ sku,
640
+ name,
641
+ expectedQuantity: 0,
642
+ actualQuantity: null,
643
+ status: 'unchecked'
644
+ };
645
+
646
+ items.push(currentItem);
647
+ saveItemToDB(currentItem)
648
+ .then(() => {
649
+ displayItem(currentItem);
650
+ quantityInput.disabled = false;
651
+ quantityInput.focus();
652
+ updateUI();
653
+ showToast('Новий товар додано', 'success');
654
+ })
655
+ .catch(error => {
656
+ console.error('Error saving new item:', error);
657
+ showToast('Помилка збереження нового товару', 'error');
658
+ });
659
+ }
660
+ } else {
661
+ skuInput.focus();
662
+ }
663
+ }
664
+
665
+ skuInput.value = '';
666
+ }
667
+
668
+ // Display item in the card
669
+ function displayItem(item) {
670
+ itemName.textContent = item.name;
671
+ itemSku.textContent = item.sku;
672
+ itemExpected.textContent = item.expectedQuantity;
673
+ itemActual.textContent = item.actualQuantity !== null ? item.actualQuantity : '-';
674
+
675
+ const difference = item.actualQuantity !== null ?
676
+ item.actualQuantity - item.expectedQuantity :
677
+ 0;
678
+
679
+ itemDifference.textContent = difference !== 0 ? difference : '-';
680
+
681
+ // Update status display
682
+ itemStatus.textContent = getStatusText(item.status);
683
+ itemStatus.className = 'px-2 py-1 rounded-full text-xs font-medium text-white';
684
+ itemStatus.classList.add(`status-${item.status}`);
685
+
686
+ // Show the card if hidden
687
+ itemCard.classList.remove('hidden');
688
+ }
689
+
690
+ // Handle quantity input
691
+ function handleQuantityInput() {
692
+ if (!currentItem) {
693
+ showToast('Спочатку знайдіть товар', 'warning');
694
+ vibrate();
695
+ return;
696
+ }
697
+
698
+ const quantityStr = quantityInput.value.trim();
699
+
700
+ if (!quantityStr) {
701
+ showToast('Введіть кількість', 'warning');
702
+ vibrate();
703
+ return;
704
+ }
705
+
706
+ let newQuantity;
707
+
708
+ // Check if it's a subtraction (starts with 0)
709
+ if (quantityStr.startsWith('0') && quantityStr.length > 1) {
710
+ const subtractValue = parseInt(quantityStr.substring(1), 10);
711
+
712
+ if (isNaN(subtractValue)) {
713
+ showToast('Невірний формат кількості', 'error');
714
+ vibrate();
715
+ return;
716
+ }
717
+
718
+ newQuantity = (currentItem.actualQuantity || 0) - subtractValue;
719
+ } else {
720
+ const addValue = parseInt(quantityStr, 10);
721
+
722
+ if (isNaN(addValue)) {
723
+ showToast('Невірний формат кількості', 'error');
724
+ vibrate();
725
+ return;
726
+ }
727
+
728
+ newQuantity = (currentItem.actualQuantity || 0) + addValue;
729
+ }
730
+
731
+ // Update item
732
+ currentItem.actualQuantity = newQuantity;
733
+ currentItem.status = calculateStatus(currentItem);
734
+
735
+ // Save to DB and update UI
736
+ saveItemToDB(currentItem)
737
+ .then(() => {
738
+ displayItem(currentItem);
739
+ quantityInput.value = '';
740
+ skuInput.focus();
741
+ updateUI();
742
+ showToast('Кількість оновлено', 'success');
743
+ })
744
+ .catch(error => {
745
+ console.error('Error updating item:', error);
746
+ showToast('Помилка оновлення кількості', 'error');
747
+ });
748
+ }
749
+
750
+ // Calculate item status
751
+ function calculateStatus(item) {
752
+ if (item.actualQuantity === null) {
753
+ return 'unchecked';
754
+ }
755
+
756
+ if (item.actualQuantity === item.expectedQuantity) {
757
+ return 'match';
758
+ } else if (item.actualQuantity < item.expectedQuantity) {
759
+ return 'missing';
760
+ } else {
761
+ return 'excess';
762
+ }
763
+ }
764
+
765
+ // Get status text
766
+ function getStatusText(status) {
767
+ const statusMap = {
768
+ 'match': 'Відповідність',
769
+ 'missing': 'Нестача',
770
+ 'excess': 'Надлишок',
771
+ 'unchecked': 'Не перевірено'
772
+ };
773
+
774
+ return statusMap[status] || status;
775
+ }
776
+
777
+ // Handle file upload
778
+ function handleFileUpload(event) {
779
+ const file = event.target.files[0];
780
+
781
+ if (!file) {
782
+ return;
783
+ }
784
+
785
+ const reader = new FileReader();
786
+
787
+ reader.onload = function(e) {
788
+ try {
789
+ const data = new Uint8Array(e.target.result);
790
+ const workbook = XLSX.read(data, { type: 'array' });
791
+
792
+ // Get first sheet
793
+ const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
794
+
795
+ // Convert to JSON
796
+ const jsonData = XLSX.utils.sheet_to_json(firstSheet);
797
+
798
+ if (jsonData.length === 0) {
799
+ showToast('Файл не містить даних', 'error');
800
+ return;
801
+ }
802
+
803
+ // Check if it's a results file (has status column)
804
+ const isResultsFile = jsonData[0].hasOwnProperty('Статус') ||
805
+ jsonData[0].hasOwnProperty('Status');
806
+
807
+ // Process data
808
+ const newItems = [];
809
+
810
+ jsonData.forEach(row => {
811
+ let sku, name, expected, actual;
812
+
813
+ if (isResultsFile) {
814
+ // Results file format
815
+ sku = row['Артикул'] || row['SKU'] || row['Арт.'] || '';
816
+ name = row['Назва'] || row['Name'] || row['Товар'] || '';
817
+ expected = parseInt(row['Очікувано'] || row['Expected'] || row['Кількість'] || 0, 10);
818
+ actual = parseInt(row['Фактично'] || row['Actual'] || row['Факт'] || 0, 10);
819
+ } else {
820
+ // New file format (only expected quantities)
821
+ sku = row['Артикул'] || row['SKU'] || row['Арт.'] || '';
822
+ name = row['Назва'] || row['Name'] || row['Товар'] || '';
823
+ expected = parseInt(row['Кількість'] || row['Quantity'] || row['Очікувано'] || 0, 10);
824
+ actual = null;
825
+ }
826
+
827
+ if (sku) {
828
+ newItems.push({
829
+ sku: sku.toString(),
830
+ name: name.toString(),
831
+ expectedQuantity: expected,
832
+ actualQuantity: actual,
833
+ status: actual !== null ? calculateStatus({ expectedQuantity: expected, actualQuantity: actual }) : 'unchecked'
834
+ });
835
+ }
836
+ });
837
+
838
+ if (newItems.length === 0) {
839
+ showToast('Не вдалося знайти товари у файлі', 'error');
840
+ return;
841
+ }
842
+
843
+ // Update items and metadata
844
+ items = newItems;
845
+ fileMetadata = {
846
+ fileName: file.name,
847
+ fileDate: new Date().toLocaleString(),
848
+ totalItems: newItems.length
849
+ };
850
+
851
+ // Save to DB
852
+ Promise.all([
853
+ saveAllItemsToDB(items),
854
+ saveMetadataToDB(fileMetadata)
855
+ ])
856
+ .then(() => {
857
+ updateUI();
858
+ showToast(`Завантажено ${newItems.length} товарів`, 'success');
859
+ switchTab('list');
860
+ })
861
+ .catch(error => {
862
+ console.error('Error saving file data:', error);
863
+ showToast('Помилка збереження даних з файлу', 'error');
864
+ });
865
+
866
+ // Reset file input
867
+ event.target.value = '';
868
+ } catch (error) {
869
+ console.error('File processing error:', error);
870
+ showToast('Помилка обробки файлу', 'error');
871
+ }
872
+ };
873
+
874
+ reader.onerror = function() {
875
+ showToast('Помилка читання файлу', 'error');
876
+ };
877
+
878
+ reader.readAsArrayBuffer(file);
879
+ }
880
+
881
+ // Save results to file
882
+ function saveResultsToFile() {
883
+ if (items.length === 0) {
884
+ showToast('Немає даних для збереження', 'warning');
885
+ return;
886
+ }
887
+
888
+ // Prepare data for export
889
+ const exportData = items.map(item => ({
890
+ 'Артикул': item.sku,
891
+ 'Назва': item.name,
892
+ 'Очікувано': item.expectedQuantity,
893
+ 'Фактично': item.actualQuantity !== null ? item.actualQuantity : '',
894
+ 'Різниця': item.actualQuantity !== null ? item.actualQuantity - item.expectedQuantity : '',
895
+ 'Статус': getStatusText(item.status)
896
+ }));
897
+
898
+ // Create workbook
899
+ const wb = XLSX.utils.book_new();
900
+ const ws = XLSX.utils.json_to_sheet(exportData);
901
+ XLSX.utils.book_append_sheet(wb, ws, 'Інвентаризація');
902
+
903
+ // Add metadata sheet
904
+ const metadata = [
905
+ { 'Параметр': 'Значення' },
906
+ { 'Параметр': 'Назва оригінального файлу', 'Значення': fileMetadata.fileName || 'Невідомо' },
907
+ { 'Параметр': 'Дата завантаження', 'Значення': fileMetadata.fileDate || 'Невідомо' },
908
+ { 'Параметр': 'Дата експорту', 'Значення': new Date().toLocaleString() },
909
+ { 'Параметр': 'Всього товарів', 'Значення': items.length },
910
+ { 'Параметр': 'Перевірено', 'Значення': items.filter(i => i.status !== 'unchecked').length },
911
+ { 'Параметр': 'Відповідність', 'Значення': items.filter(i => i.status === 'match').length },
912
+ { 'Параметр': 'Нестача', 'Значення': items.filter(i => i.status === 'missing').length },
913
+ { 'Параметр': 'Надлишок', 'Значення': items.filter(i => i.status === 'excess').length }
914
+ ];
915
+
916
+ const wsMetadata = XLSX.utils.json_to_sheet(metadata);
917
+ XLSX.utils.book_append_sheet(wb, wsMetadata, 'Метадані');
918
+
919
+ // Generate file name
920
+ const fileName = `Інвентаризація_${formatDateForFileName(new Date())}.xlsx`;
921
+
922
+ // Export
923
+ XLSX.writeFile(wb, fileName);
924
+ showToast('Результати збережено у файл', 'success');
925
+ }
926
+
927
+ // Format date for file name
928
+ function formatDateForFileName(date) {
929
+ const pad = num => num.toString().padStart(2, '0');
930
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}_${pad(date.getHours())}${pad(date.getMinutes())}`;
931
+ }
932
+
933
+ // Confirm clear data
934
+ function confirmClearData() {
935
+ if (items.length === 0) {
936
+ showToast('Немає даних для очищення', 'info');
937
+ return;
938
+ }
939
+
940
+ const confirmClear = confirm('Ви впевнені, що хочете очистити всі дані інвентаризації? Цю дію не можна скасувати.');
941
+
942
+ if (confirmClear) {
943
+ Promise.all([
944
+ clearItemsDB(),
945
+ clearMetadataDB()
946
+ ])
947
+ .then(() => {
948
+ items = [];
949
+ fileMetadata = {
950
+ fileName: null,
951
+ fileDate: null,
952
+ totalItems: 0
953
+ };
954
+ currentItem = null;
955
+ updateUI();
956
+ showToast('Всі дані очищено', 'success');
957
+ })
958
+ .catch(error => {
959
+ console.error('Error clearing data:', error);
960
+ showToast('Помилка очищення даних', 'error');
961
+ });
962
+ }
963
+ }
964
+
965
+ // Update UI based on current state
966
+ function updateUI() {
967
+ // Update file info
968
+ if (fileMetadata.fileName) {
969
+ fileInfo.classList.remove('hidden');
970
+ fileName.textContent = fileMetadata.fileName;
971
+ fileDate.textContent = fileMetadata.fileDate;
972
+ } else {
973
+ fileInfo.classList.add('hidden');
974
+ }
975
+
976
+ // Filter items based on status and search
977
+ const statusFilterValue = statusFilter.value;
978
+ const searchQuery = searchInput.value.toLowerCase();
979
+
980
+ const filteredItems = items.filter(item => {
981
+ // Status filter
982
+ const statusMatch = statusFilterValue === 'all' ||
983
+ (statusFilterValue === 'match' && item.status === 'match') ||
984
+ (statusFilterValue === 'missing' && item.status === 'missing') ||
985
+ (statusFilterValue === 'excess' && item.status === 'excess') ||
986
+ (statusFilterValue === 'unchecked' && item.status === 'unchecked');
987
+
988
+ // Search filter
989
+ const searchMatch = searchQuery === '' ||
990
+ item.name.toLowerCase().includes(searchQuery) ||
991
+ item.sku.toLowerCase().includes(searchQuery);
992
+
993
+ return statusMatch && searchMatch;
994
+ });
995
+
996
+ // Update items table
997
+ renderItemsTable(filteredItems);
998
+
999
+ // Update results table
1000
+ const resultsItems = items.filter(item => item.status === 'missing' || item.status === 'excess');
1001
+ renderResultsTable(resultsItems);
1002
+
1003
+ // Update progress and stats
1004
+ updateProgressAndStats();
1005
+
1006
+ // Update control states
1007
+ updateControlStates();
1008
+ }
1009
+
1010
+ // Render items table
1011
+ function renderItemsTable(itemsToRender) {
1012
+ itemsTableBody.innerHTML = '';
1013
+
1014
+ if (itemsToRender.length === 0) {
1015
+ noItemsMessage.classList.remove('hidden');
1016
+ return;
1017
+ }
1018
+
1019
+ noItemsMessage.classList.add('hidden');
1020
+
1021
+ itemsToRender.forEach(item => {
1022
+ const row = document.createElement('tr');
1023
+ row.className = 'border-b border-gray-200 hover:bg-gray-100/10 transition';
1024
+ row.addEventListener('click', () => {
1025
+ switchTab('scan');
1026
+ currentItem = item;
1027
+ displayItem(item);
1028
+ quantityInput.disabled = false;
1029
+ quantityInput.focus();
1030
+ });
1031
+
1032
+ // Status cell
1033
+ const statusCell = document.createElement('td');
1034
+ statusCell.className = 'px-4 py-2';
1035
+
1036
+ const statusBadge = document.createElement('span');
1037
+ statusBadge.className = `px-2 py-1 rounded-full text-xs font-medium text-white status-${item.status}`;
1038
+ statusBadge.textContent = getStatusText(item.status);
1039
+
1040
+ statusCell.appendChild(statusBadge);
1041
+ row.appendChild(statusCell);
1042
+
1043
+ // SKU cell
1044
+ const skuCell = document.createElement('td');
1045
+ skuCell.className = 'px-4 py-2 font-mono';
1046
+ skuCell.textContent = item.sku;
1047
+ row.appendChild(skuCell);
1048
+
1049
+ // Name cell
1050
+ const nameCell = document.createElement('td');
1051
+ nameCell.className = 'px-4 py-2';
1052
+ nameCell.textContent = item.name;
1053
+ row.appendChild(nameCell);
1054
+
1055
+ // Expected cell
1056
+ const expectedCell = document.createElement('td');
1057
+ expectedCell.className = 'px-4 py-2 text-right';
1058
+ expectedCell.textContent = item.expectedQuantity;
1059
+ row.appendChild(expectedCell);
1060
+
1061
+ // Actual cell
1062
+ const actualCell = document.createElement('td');
1063
+ actualCell.className = 'px-4 py-2 text-right';
1064
+ actualCell.textContent = item.actualQuantity !== null ? item.actualQuantity : '-';
1065
+ row.appendChild(actualCell);
1066
+
1067
+ itemsTableBody.appendChild(row);
1068
+ });
1069
+ }
1070
+
1071
+ // Render results table
1072
+ function renderResultsTable(itemsToRender) {
1073
+ resultsTableBody.innerHTML = '';
1074
+
1075
+ if (itemsToRender.length === 0) {
1076
+ noResultsMessage.classList.remove('hidden');
1077
+ resultsSummary.textContent = 'Знайдено 0 розбіжностей';
1078
+ return;
1079
+ }
1080
+
1081
+ noResultsMessage.classList.add('hidden');
1082
+ resultsSummary.textContent = `Знайдено ${itemsToRender.length} розбіжностей`;
1083
+
1084
+ itemsToRender.forEach(item => {
1085
+ const row = document.createElement('tr');
1086
+ row.className = 'border-b border-gray-200 hover:bg-gray-100/10 transition';
1087
+ row.addEventListener('click', () => {
1088
+ switchTab('scan');
1089
+ currentItem = item;
1090
+ displayItem(item);
1091
+ quantityInput.disabled = false;
1092
+ quantityInput.focus();
1093
+ });
1094
+
1095
+ // Status cell
1096
+ const statusCell = document.createElement('td');
1097
+ statusCell.className = 'px-4 py-2';
1098
+
1099
+ const statusBadge = document.createElement('span');
1100
+ statusBadge.className = `px-2 py-1 rounded-full text-xs font-medium text-white status-${item.status}`;
1101
+ statusBadge.textContent = getStatusText(item.status);
1102
+
1103
+ statusCell.appendChild(statusBadge);
1104
+ row.appendChild(statusCell);
1105
+
1106
+ // SKU cell
1107
+ const skuCell = document.createElement('td');
1108
+ skuCell.className = 'px-4 py-2 font-mono';
1109
+ skuCell.textContent = item.sku;
1110
+ row.appendChild(skuCell);
1111
+
1112
+ // Name cell
1113
+ const nameCell = document.createElement('td');
1114
+ nameCell.className = 'px-4 py-2';
1115
+ nameCell.textContent = item.name;
1116
+ row.appendChild(nameCell);
1117
+
1118
+ // Expected cell
1119
+ const expectedCell = document.createElement('td');
1120
+ expectedCell.className = 'px-4 py-2 text-right';
1121
+ expectedCell.textContent = item.expectedQuantity;
1122
+ row.appendChild(expectedCell);
1123
+
1124
+ // Actual cell
1125
+ const actualCell = document.createElement('td');
1126
+ actualCell.className = 'px-4 py-2 text-right';
1127
+ actualCell.textContent = item.actualQuantity !== null ? item.actualQuantity : '-';
1128
+ row.appendChild(actualCell);
1129
+
1130
+ // Difference cell
1131
+ const differenceCell = document.createElement('td');
1132
+ differenceCell.className = 'px-4 py-2 text-right font-medium';
1133
+
1134
+ const difference = item.actualQuantity - item.expectedQuantity;
1135
+ differenceCell.textContent = difference;
1136
+
1137
+ if (difference > 0) {
1138
+ differenceCell.classList.add('text-green-500');
1139
+ } else if (difference < 0) {
1140
+ differenceCell.classList.add('text-red-500');
1141
+ }
1142
+
1143
+ row.appendChild(differenceCell);
1144
+
1145
+ resultsTableBody.appendChild(row);
1146
+ });
1147
+ }
1148
+
1149
+ // Update progress and statistics
1150
+ function updateProgressAndStats() {
1151
+ const total = items.length;
1152
+ const checked = items.filter(item => item.status !== 'unchecked').length;
1153
+ const match = items.filter(item => item.status === 'match').length;
1154
+ const missing = items.filter(item => item.status === 'missing').length;
1155
+ const excess = items.filter(item => item.status === 'excess').length;
1156
+
1157
+ // Update counts
1158
+ totalCount.textContent = total;
1159
+ checkedCount.textContent = checked;
1160
+ matchCount.textContent = match;
1161
+ missingCount.textContent = missing;
1162
+ excessCount.textContent = excess;
1163
+
1164
+ // Update progress bar
1165
+ const progressPercent = total > 0 ? (checked / total) * 100 : 0;
1166
+ progressFill.style.width = `${progressPercent}%`;
1167
+ }
1168
+
1169
+ // Update control states (enabled/disabled)
1170
+ function updateControlStates() {
1171
+ // Save button
1172
+ saveResultsBtn.disabled = items.length === 0;
1173
+
1174
+ // Clear button
1175
+ clearDataBtn.disabled = items.length === 0;
1176
+
1177
+ // Quantity input (enabled only if an item is selected)
1178
+ quantityInput.disabled = currentItem === null;
1179
+ quantitySubmit.disabled = currentItem === null;
1180
+ }
1181
+
1182
+ // Show toast notification
1183
+ function showToast(message, type) {
1184
+ const toast = document.createElement('div');
1185
+ toast.className = `toast px-4 py-2 rounded-lg shadow-lg text-white ${
1186
+ type === 'success' ? 'bg-green-500' :
1187
+ type === 'error' ? 'bg-red-500' :
1188
+ type === 'warning' ? 'bg-yellow-500' : 'bg-blue-500'
1189
+ }`;
1190
+ toast.textContent = message;
1191
+
1192
+ toastContainer.appendChild(toast);
1193
+
1194
+ // Remove after animation
1195
+ setTimeout(() => {
1196
+ toast.remove();
1197
+ }, 3000);
1198
+ }
1199
+
1200
+ // Vibrate device (if supported)
1201
+ function vibrate() {
1202
+ if ('vibrate' in navigator) {
1203
+ navigator.vibrate(50);
1204
+ }
1205
+ }
1206
+
1207
+ // Initialize the app when DOM is loaded
1208
+ document.addEventListener('DOMContentLoaded', initializeApp);
1209
+ </script>
1210
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=rostyslavpwork/inventarization" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1211
+ </html>
prompts.txt ADDED
File without changes