@codingfactory/inventory-locator-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/package.json +47 -0
  2. package/src/api/client.ts +31 -0
  3. package/src/components/counting/CountEntryForm.vue +833 -0
  4. package/src/components/counting/CountReport.vue +385 -0
  5. package/src/components/counting/CountSessionDetail.vue +650 -0
  6. package/src/components/counting/CountSessionList.vue +683 -0
  7. package/src/components/dashboard/InventoryDashboard.vue +670 -0
  8. package/src/components/dashboard/UnlocatedProducts.vue +468 -0
  9. package/src/components/labels/LabelBatchPrint.vue +528 -0
  10. package/src/components/labels/LabelPreview.vue +293 -0
  11. package/src/components/locations/InventoryLocatorShell.vue +408 -0
  12. package/src/components/locations/LocationBreadcrumb.vue +144 -0
  13. package/src/components/locations/LocationCodeBadge.vue +46 -0
  14. package/src/components/locations/LocationDetail.vue +884 -0
  15. package/src/components/locations/LocationForm.vue +360 -0
  16. package/src/components/locations/LocationSearchInput.vue +428 -0
  17. package/src/components/locations/LocationTree.vue +156 -0
  18. package/src/components/locations/LocationTreeNode.vue +280 -0
  19. package/src/components/locations/LocationTypeIcon.vue +58 -0
  20. package/src/components/products/LocationProductAdd.vue +637 -0
  21. package/src/components/products/LocationProductList.vue +547 -0
  22. package/src/components/products/ProductLocationList.vue +215 -0
  23. package/src/components/products/QuickMoveModal.vue +592 -0
  24. package/src/components/scanning/ScanHistory.vue +146 -0
  25. package/src/components/scanning/ScanResult.vue +350 -0
  26. package/src/components/scanning/ScannerOverlay.vue +696 -0
  27. package/src/components/shared/InvBadge.vue +71 -0
  28. package/src/components/shared/InvButton.vue +206 -0
  29. package/src/components/shared/InvCard.vue +254 -0
  30. package/src/components/shared/InvEmptyState.vue +132 -0
  31. package/src/components/shared/InvInput.vue +125 -0
  32. package/src/components/shared/InvModal.vue +296 -0
  33. package/src/components/shared/InvSelect.vue +155 -0
  34. package/src/components/shared/InvTable.vue +288 -0
  35. package/src/composables/useCountSessions.ts +184 -0
  36. package/src/composables/useLabelPrinting.ts +71 -0
  37. package/src/composables/useLocationBreadcrumbs.ts +19 -0
  38. package/src/composables/useLocationProducts.ts +125 -0
  39. package/src/composables/useLocationSearch.ts +46 -0
  40. package/src/composables/useLocations.ts +159 -0
  41. package/src/composables/useMovements.ts +71 -0
  42. package/src/composables/useScanner.ts +83 -0
  43. package/src/env.d.ts +7 -0
  44. package/src/index.ts +46 -0
  45. package/src/plugin.ts +14 -0
  46. package/src/stores/countStore.ts +95 -0
  47. package/src/stores/locationStore.ts +113 -0
  48. package/src/stores/scannerStore.ts +51 -0
  49. package/src/types/index.ts +216 -0
  50. package/src/utils/codeFormatter.ts +29 -0
  51. package/src/utils/locationIcons.ts +64 -0
  52. package/tsconfig.json +21 -0
  53. package/vite.config.ts +37 -0
@@ -0,0 +1,670 @@
1
+ <template>
2
+ <div class="inv-dashboard">
3
+ <h1 class="inv-dashboard__title">Inventory Dashboard</h1>
4
+
5
+ <!-- Stat cards -->
6
+ <div class="inv-dashboard__stats">
7
+ <div class="inv-dashboard__stat-card inv-dashboard__stat-card--primary">
8
+ <div v-if="statsLoading" class="inv-dashboard__stat-shimmer" />
9
+ <template v-else>
10
+ <span class="inv-dashboard__stat-value">{{ stats?.total_locations ?? 0 }}</span>
11
+ <span class="inv-dashboard__stat-label">Total Locations</span>
12
+ </template>
13
+ </div>
14
+ <div class="inv-dashboard__stat-card inv-dashboard__stat-card--success">
15
+ <div v-if="statsLoading" class="inv-dashboard__stat-shimmer" />
16
+ <template v-else>
17
+ <span class="inv-dashboard__stat-value">{{ formatNumber(stats?.total_products_tracked ?? 0) }}</span>
18
+ <span class="inv-dashboard__stat-label">Products Tracked</span>
19
+ </template>
20
+ </div>
21
+ <div class="inv-dashboard__stat-card inv-dashboard__stat-card--info">
22
+ <div v-if="statsLoading" class="inv-dashboard__stat-shimmer" />
23
+ <template v-else>
24
+ <span class="inv-dashboard__stat-value">{{ stats?.locations_with_products ?? 0 }}</span>
25
+ <span class="inv-dashboard__stat-label">In Use</span>
26
+ </template>
27
+ </div>
28
+ <div class="inv-dashboard__stat-card inv-dashboard__stat-card--warning">
29
+ <div v-if="statsLoading" class="inv-dashboard__stat-shimmer" />
30
+ <template v-else>
31
+ <span class="inv-dashboard__stat-value">{{ stats?.empty_leaf_locations ?? 0 }}</span>
32
+ <span class="inv-dashboard__stat-label">Empty</span>
33
+ </template>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- Locations by type -->
38
+ <InvCard
39
+ v-if="stats && Object.keys(stats.locations_by_type).length > 0"
40
+ title="Locations by Type"
41
+ compact
42
+ class="inv-dashboard__by-type"
43
+ >
44
+ <div class="inv-dashboard__type-grid">
45
+ <div
46
+ v-for="(count, type) in stats.locations_by_type"
47
+ :key="type"
48
+ class="inv-dashboard__type-item"
49
+ >
50
+ <span class="inv-dashboard__type-count">{{ count }}</span>
51
+ <span class="inv-dashboard__type-label">{{ formatTypeName(type as string) }}</span>
52
+ </div>
53
+ </div>
54
+ </InvCard>
55
+
56
+ <!-- Quick actions -->
57
+ <InvCard title="Quick Actions" compact class="inv-dashboard__quick-actions">
58
+ <div class="inv-dashboard__actions-grid">
59
+ <button
60
+ v-for="action in quickActions"
61
+ :key="action.label"
62
+ type="button"
63
+ class="inv-dashboard__action-btn"
64
+ @click="handleAction(action.route)"
65
+ >
66
+ <span class="inv-dashboard__action-icon" v-html="action.icon" />
67
+ <span class="inv-dashboard__action-label">{{ action.label }}</span>
68
+ </button>
69
+ </div>
70
+ </InvCard>
71
+
72
+ <!-- Recent Activity -->
73
+ <InvCard title="Recent Activity" class="inv-dashboard__activity" :loading="movementsLoading">
74
+ <div v-if="movements.length > 0" class="inv-dashboard__activity-list" role="list">
75
+ <button
76
+ v-for="movement in movements"
77
+ :key="movement.id"
78
+ type="button"
79
+ role="listitem"
80
+ class="inv-dashboard__activity-row"
81
+ @click="navigateToMovementLocation(movement)"
82
+ >
83
+ <span
84
+ class="inv-dashboard__activity-icon"
85
+ :class="`inv-dashboard__activity-icon--${movement.reason}`"
86
+ aria-hidden="true"
87
+ >
88
+ {{ movementIcon(movement.reason) }}
89
+ </span>
90
+ <div class="inv-dashboard__activity-info">
91
+ <span class="inv-dashboard__activity-product">{{ movement.product_name }}</span>
92
+ <span class="inv-dashboard__activity-location">
93
+ <template v-if="movement.reason === 'moved' && movement.from_location && movement.to_location">
94
+ {{ movement.from_location.full_code }} &rarr; {{ movement.to_location.full_code }}
95
+ </template>
96
+ <template v-else-if="movement.to_location">
97
+ &rarr; {{ movement.to_location.full_code }}
98
+ </template>
99
+ <template v-else-if="movement.from_location">
100
+ &larr; {{ movement.from_location.full_code }}
101
+ </template>
102
+ </span>
103
+ </div>
104
+ <span class="inv-dashboard__activity-time">{{ formatTimeAgo(movement.performed_at) }}</span>
105
+ </button>
106
+ </div>
107
+ <p v-else-if="!movementsLoading" class="inv-dashboard__no-activity">
108
+ No recent activity
109
+ </p>
110
+ </InvCard>
111
+
112
+ <!-- Bottom widgets row -->
113
+ <div class="inv-dashboard__bottom-row">
114
+ <!-- Unlocated products summary -->
115
+ <InvCard title="Unlocated Products" compact class="inv-dashboard__unlocated-widget">
116
+ <div v-if="unlocatedCount > 0" class="inv-dashboard__unlocated-content">
117
+ <p class="inv-dashboard__unlocated-text">
118
+ <strong>{{ unlocatedCount }}</strong> products have no assigned location
119
+ </p>
120
+ <InvButton
121
+ variant="secondary"
122
+ size="sm"
123
+ @click="router.push({ name: 'inventory-unlocated' })"
124
+ >
125
+ View All
126
+ </InvButton>
127
+ </div>
128
+ <p v-else class="inv-dashboard__unlocated-none">
129
+ All products have assigned locations
130
+ </p>
131
+ </InvCard>
132
+
133
+ <!-- Active counts -->
134
+ <InvCard title="Active Counts" compact class="inv-dashboard__counts-widget">
135
+ <div v-if="activeCounts.length > 0">
136
+ <div
137
+ v-for="count in activeCounts"
138
+ :key="count.id"
139
+ class="inv-dashboard__count-row"
140
+ >
141
+ <div class="inv-dashboard__count-info">
142
+ <span class="inv-dashboard__count-name">{{ count.name }}</span>
143
+ <div
144
+ class="inv-dashboard__count-progress"
145
+ role="progressbar"
146
+ :aria-valuenow="count.progress.percent_complete"
147
+ aria-valuemin="0"
148
+ aria-valuemax="100"
149
+ >
150
+ <div class="inv-dashboard__count-progress-track">
151
+ <div
152
+ class="inv-dashboard__count-progress-fill"
153
+ :style="{ width: `${count.progress.percent_complete}%` }"
154
+ />
155
+ </div>
156
+ <span class="inv-dashboard__count-pct">
157
+ {{ count.progress.percent_complete }}%
158
+ </span>
159
+ </div>
160
+ </div>
161
+ <InvButton
162
+ variant="secondary"
163
+ size="sm"
164
+ @click="router.push({ name: 'inventory-count-detail', params: { id: count.id } })"
165
+ >
166
+ Continue
167
+ </InvButton>
168
+ </div>
169
+ </div>
170
+ <p v-else class="inv-dashboard__no-counts">
171
+ No active count sessions
172
+ </p>
173
+ </InvCard>
174
+ </div>
175
+ </div>
176
+ </template>
177
+
178
+ <script setup lang="ts">
179
+ import { ref, onMounted } from 'vue'
180
+ import { useRouter } from 'vue-router'
181
+ import { getApiClient, apiUrl } from '../../api/client'
182
+ import { useMovements } from '../../composables/useMovements'
183
+ import { useCountSessions } from '../../composables/useCountSessions'
184
+ import type { DashboardStats, CountSession, MovementReason } from '../../types'
185
+ import InvCard from '../shared/InvCard.vue'
186
+ import InvButton from '../shared/InvButton.vue'
187
+
188
+ const router = useRouter()
189
+ const { movements, loading: movementsLoading, fetchRecent } = useMovements()
190
+ const { sessions: activeCounts, fetchSessions } = useCountSessions()
191
+
192
+ const stats = ref<DashboardStats | null>(null)
193
+ const statsLoading = ref(true)
194
+ const unlocatedCount = ref(0)
195
+
196
+ const quickActions = [
197
+ {
198
+ label: 'Scan Location',
199
+ route: 'inventory-scan',
200
+ icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect x="3" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.5"/><rect x="14" y="3" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.5"/><rect x="3" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.5"/><rect x="14" y="14" width="7" height="7" rx="1" stroke="currentColor" stroke-width="1.5"/></svg>',
201
+ },
202
+ {
203
+ label: 'Add Location',
204
+ route: 'inventory-locations',
205
+ icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect x="4" y="4" width="16" height="16" rx="2" stroke="currentColor" stroke-width="1.5"/><path d="M12 8V16M8 12H16" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
206
+ },
207
+ {
208
+ label: 'Find Product',
209
+ route: 'inventory-locations',
210
+ icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="1.5"/><path d="M16 16L20 20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
211
+ },
212
+ {
213
+ label: 'Start Count',
214
+ route: 'inventory-counts',
215
+ icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect x="4" y="4" width="16" height="16" rx="2" stroke="currentColor" stroke-width="1.5"/><path d="M8 8h2v2H8V8zm4 0h2v2h-2V8zm-4 4h2v2H8v-2zm4 0h2v2h-2v-2z" fill="currentColor"/></svg>',
216
+ },
217
+ {
218
+ label: 'Print Labels',
219
+ route: 'inventory-labels',
220
+ icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect x="6" y="2" width="12" height="7" rx="1" stroke="currentColor" stroke-width="1.5"/><rect x="3" y="9" width="18" height="9" rx="1" stroke="currentColor" stroke-width="1.5"/><rect x="7" y="15" width="10" height="7" rx="1" stroke="currentColor" stroke-width="1.5"/></svg>',
221
+ },
222
+ {
223
+ label: 'Browse Locations',
224
+ route: 'inventory-locations',
225
+ icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><path d="M3 7H21M3 12H21M3 17H21" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>',
226
+ },
227
+ ]
228
+
229
+ onMounted(() => {
230
+ loadStats()
231
+ fetchRecent()
232
+ fetchSessions('in_progress')
233
+ loadUnlocatedCount()
234
+ })
235
+
236
+ async function loadStats() {
237
+ statsLoading.value = true
238
+ try {
239
+ const response = await getApiClient().get(apiUrl('/dashboard/stats'))
240
+ stats.value = response.data
241
+ } catch {
242
+ // Stats are non-critical, degrade gracefully
243
+ } finally {
244
+ statsLoading.value = false
245
+ }
246
+ }
247
+
248
+ async function loadUnlocatedCount() {
249
+ try {
250
+ const response = await getApiClient().get(apiUrl('/dashboard/unlocated-products?limit=1'))
251
+ unlocatedCount.value = response.data.meta?.total ?? response.data.data?.length ?? 0
252
+ } catch {
253
+ // Non-critical
254
+ }
255
+ }
256
+
257
+ function handleAction(routeName: string) {
258
+ router.push({ name: routeName })
259
+ }
260
+
261
+ function navigateToMovementLocation(movement: any) {
262
+ const loc = movement.to_location || movement.from_location
263
+ if (loc) {
264
+ router.push({ name: 'inventory-location', params: { code: loc.full_code } })
265
+ }
266
+ }
267
+
268
+ function movementIcon(reason: MovementReason): string {
269
+ const icons: Record<MovementReason, string> = {
270
+ placed: '+',
271
+ moved: '\u2194',
272
+ picked: '-',
273
+ adjusted: '\u27F3',
274
+ counted: '#',
275
+ received: '+',
276
+ returned: '\u21A9',
277
+ }
278
+ return icons[reason] ?? '?'
279
+ }
280
+
281
+ function formatTimeAgo(isoDate: string): string {
282
+ const now = Date.now()
283
+ const then = new Date(isoDate).getTime()
284
+ const diffMs = now - then
285
+
286
+ const seconds = Math.floor(diffMs / 1000)
287
+ if (seconds < 60) return 'just now'
288
+
289
+ const minutes = Math.floor(seconds / 60)
290
+ if (minutes < 60) return `${minutes} min ago`
291
+
292
+ const hours = Math.floor(minutes / 60)
293
+ if (hours < 24) return `${hours}h ago`
294
+
295
+ const days = Math.floor(hours / 24)
296
+ if (days === 1) return 'yesterday'
297
+ return `${days}d ago`
298
+ }
299
+
300
+ function formatNumber(n: number): string {
301
+ return n.toLocaleString('en-US')
302
+ }
303
+
304
+ function formatTypeName(type: string): string {
305
+ return type.charAt(0).toUpperCase() + type.slice(1) + 's'
306
+ }
307
+ </script>
308
+
309
+ <style scoped>
310
+ .inv-dashboard {
311
+ max-width: 960px;
312
+ display: flex;
313
+ flex-direction: column;
314
+ gap: var(--space-5, 1.25rem);
315
+ }
316
+
317
+ .inv-dashboard__title {
318
+ margin: 0;
319
+ font-size: var(--text-xl, 1.25rem);
320
+ font-weight: 700;
321
+ color: var(--admin-text-primary);
322
+ }
323
+
324
+ /* Stat cards */
325
+ .inv-dashboard__stats {
326
+ display: grid;
327
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
328
+ gap: var(--space-4, 1rem);
329
+ }
330
+
331
+ .inv-dashboard__stat-card {
332
+ display: flex;
333
+ flex-direction: column;
334
+ align-items: flex-start;
335
+ gap: var(--space-1, 0.25rem);
336
+ padding: var(--space-4, 1rem) var(--space-5, 1.25rem);
337
+ background: var(--admin-card-bg);
338
+ border: 1px solid var(--admin-border);
339
+ border-radius: 8px;
340
+ border-left-width: 4px;
341
+ min-height: 80px;
342
+ }
343
+
344
+ .inv-dashboard__stat-card--primary {
345
+ border-left-color: var(--color-primary, #2563eb);
346
+ }
347
+
348
+ .inv-dashboard__stat-card--success {
349
+ border-left-color: var(--color-success, #059669);
350
+ }
351
+
352
+ .inv-dashboard__stat-card--info {
353
+ border-left-color: var(--color-info, #0891b2);
354
+ }
355
+
356
+ .inv-dashboard__stat-card--warning {
357
+ border-left-color: var(--color-warning, #d97706);
358
+ }
359
+
360
+ .inv-dashboard__stat-shimmer {
361
+ width: 60px;
362
+ height: 28px;
363
+ border-radius: 4px;
364
+ background: linear-gradient(
365
+ 90deg,
366
+ var(--admin-border) 25%,
367
+ var(--admin-content-bg) 50%,
368
+ var(--admin-border) 75%
369
+ );
370
+ background-size: 200% 100%;
371
+ animation: dashboard-shimmer 1.5s ease-in-out infinite;
372
+ }
373
+
374
+ @keyframes dashboard-shimmer {
375
+ 0% { background-position: 200% 0; }
376
+ 100% { background-position: -200% 0; }
377
+ }
378
+
379
+ .inv-dashboard__stat-value {
380
+ font-size: var(--text-2xl, 1.5rem);
381
+ font-weight: 700;
382
+ font-variant-numeric: tabular-nums;
383
+ color: var(--admin-text-primary);
384
+ line-height: 1.2;
385
+ }
386
+
387
+ .inv-dashboard__stat-label {
388
+ font-size: var(--text-xs, 0.75rem);
389
+ color: var(--admin-text-secondary);
390
+ text-transform: uppercase;
391
+ letter-spacing: 0.05em;
392
+ }
393
+
394
+ /* Locations by type */
395
+ .inv-dashboard__type-grid {
396
+ display: flex;
397
+ flex-wrap: wrap;
398
+ gap: var(--space-4, 1rem);
399
+ }
400
+
401
+ .inv-dashboard__type-item {
402
+ display: flex;
403
+ align-items: baseline;
404
+ gap: var(--space-1, 0.25rem);
405
+ }
406
+
407
+ .inv-dashboard__type-count {
408
+ font-weight: 700;
409
+ font-variant-numeric: tabular-nums;
410
+ color: var(--admin-text-primary);
411
+ }
412
+
413
+ .inv-dashboard__type-label {
414
+ font-size: var(--text-sm, 0.875rem);
415
+ color: var(--admin-text-secondary);
416
+ }
417
+
418
+ /* Quick actions */
419
+ .inv-dashboard__actions-grid {
420
+ display: grid;
421
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
422
+ gap: var(--space-3, 0.75rem);
423
+ }
424
+
425
+ .inv-dashboard__action-btn {
426
+ display: flex;
427
+ flex-direction: column;
428
+ align-items: center;
429
+ justify-content: center;
430
+ gap: var(--space-2, 0.5rem);
431
+ min-height: 80px;
432
+ padding: var(--space-3, 0.75rem);
433
+ border: 1px solid var(--admin-border);
434
+ border-radius: 8px;
435
+ background: var(--admin-card-bg);
436
+ color: var(--admin-text-primary);
437
+ cursor: pointer;
438
+ transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
439
+ }
440
+
441
+ .inv-dashboard__action-btn:hover {
442
+ background: var(--admin-content-bg);
443
+ border-color: var(--admin-text-tertiary);
444
+ }
445
+
446
+ .inv-dashboard__action-btn:active {
447
+ background: var(--admin-table-header-bg);
448
+ }
449
+
450
+ .inv-dashboard__action-btn:focus-visible {
451
+ outline: 2px solid var(--admin-focus-ring, #2563eb);
452
+ outline-offset: 2px;
453
+ }
454
+
455
+ .inv-dashboard__action-icon {
456
+ display: flex;
457
+ align-items: center;
458
+ justify-content: center;
459
+ color: var(--color-primary, #2563eb);
460
+ }
461
+
462
+ .inv-dashboard__action-label {
463
+ font-size: var(--text-sm, 0.875rem);
464
+ font-weight: 500;
465
+ text-align: center;
466
+ }
467
+
468
+ /* Recent activity */
469
+ .inv-dashboard__activity-list {
470
+ display: flex;
471
+ flex-direction: column;
472
+ }
473
+
474
+ .inv-dashboard__activity-row {
475
+ display: flex;
476
+ align-items: center;
477
+ gap: var(--space-3, 0.75rem);
478
+ padding: var(--space-2, 0.5rem) 0;
479
+ border: none;
480
+ background: none;
481
+ color: var(--admin-text-primary);
482
+ cursor: pointer;
483
+ text-align: left;
484
+ font: inherit;
485
+ width: 100%;
486
+ border-bottom: 1px solid var(--admin-border);
487
+ border-radius: 0;
488
+ transition: background-color 0.1s ease;
489
+ }
490
+
491
+ .inv-dashboard__activity-row:last-child {
492
+ border-bottom: none;
493
+ }
494
+
495
+ .inv-dashboard__activity-row:hover {
496
+ background: var(--admin-content-bg);
497
+ }
498
+
499
+ .inv-dashboard__activity-row:focus-visible {
500
+ outline: 2px solid var(--admin-focus-ring, #2563eb);
501
+ outline-offset: 2px;
502
+ }
503
+
504
+ .inv-dashboard__activity-icon {
505
+ display: flex;
506
+ align-items: center;
507
+ justify-content: center;
508
+ width: 28px;
509
+ height: 28px;
510
+ border-radius: 50%;
511
+ font-weight: 700;
512
+ font-size: var(--text-sm, 0.875rem);
513
+ flex-shrink: 0;
514
+ }
515
+
516
+ .inv-dashboard__activity-icon--placed,
517
+ .inv-dashboard__activity-icon--received {
518
+ background: color-mix(in srgb, var(--color-success, #059669) 12%, transparent);
519
+ color: var(--color-success, #059669);
520
+ }
521
+
522
+ .inv-dashboard__activity-icon--moved {
523
+ background: color-mix(in srgb, var(--color-primary, #2563eb) 12%, transparent);
524
+ color: var(--color-primary, #2563eb);
525
+ }
526
+
527
+ .inv-dashboard__activity-icon--picked {
528
+ background: color-mix(in srgb, var(--color-error, #dc2626) 12%, transparent);
529
+ color: var(--color-error, #dc2626);
530
+ }
531
+
532
+ .inv-dashboard__activity-icon--adjusted,
533
+ .inv-dashboard__activity-icon--returned {
534
+ background: color-mix(in srgb, var(--color-warning, #d97706) 12%, transparent);
535
+ color: var(--color-warning, #d97706);
536
+ }
537
+
538
+ .inv-dashboard__activity-icon--counted {
539
+ background: color-mix(in srgb, #7c3aed 12%, transparent);
540
+ color: #7c3aed;
541
+ }
542
+
543
+ .inv-dashboard__activity-info {
544
+ display: flex;
545
+ flex-direction: column;
546
+ gap: 1px;
547
+ min-width: 0;
548
+ flex: 1;
549
+ }
550
+
551
+ .inv-dashboard__activity-product {
552
+ font-size: var(--text-sm, 0.875rem);
553
+ font-weight: 500;
554
+ overflow: hidden;
555
+ text-overflow: ellipsis;
556
+ white-space: nowrap;
557
+ }
558
+
559
+ .inv-dashboard__activity-location {
560
+ font-size: var(--text-xs, 0.75rem);
561
+ color: var(--admin-text-tertiary);
562
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
563
+ }
564
+
565
+ .inv-dashboard__activity-time {
566
+ font-size: var(--text-xs, 0.75rem);
567
+ color: var(--admin-text-tertiary);
568
+ flex-shrink: 0;
569
+ white-space: nowrap;
570
+ }
571
+
572
+ .inv-dashboard__no-activity {
573
+ margin: 0;
574
+ padding: var(--space-4, 1rem) 0;
575
+ text-align: center;
576
+ font-size: var(--text-sm, 0.875rem);
577
+ color: var(--admin-text-tertiary);
578
+ font-style: italic;
579
+ }
580
+
581
+ /* Bottom widgets */
582
+ .inv-dashboard__bottom-row {
583
+ display: grid;
584
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
585
+ gap: var(--space-4, 1rem);
586
+ }
587
+
588
+ .inv-dashboard__unlocated-content {
589
+ display: flex;
590
+ align-items: center;
591
+ justify-content: space-between;
592
+ gap: var(--space-3, 0.75rem);
593
+ }
594
+
595
+ .inv-dashboard__unlocated-text {
596
+ margin: 0;
597
+ font-size: var(--text-sm, 0.875rem);
598
+ color: var(--admin-text-secondary);
599
+ }
600
+
601
+ .inv-dashboard__unlocated-none {
602
+ margin: 0;
603
+ font-size: var(--text-sm, 0.875rem);
604
+ color: var(--admin-text-tertiary);
605
+ font-style: italic;
606
+ }
607
+
608
+ .inv-dashboard__count-row {
609
+ display: flex;
610
+ align-items: center;
611
+ gap: var(--space-3, 0.75rem);
612
+ padding: var(--space-2, 0.5rem) 0;
613
+ border-bottom: 1px solid var(--admin-border);
614
+ }
615
+
616
+ .inv-dashboard__count-row:last-child {
617
+ border-bottom: none;
618
+ }
619
+
620
+ .inv-dashboard__count-info {
621
+ flex: 1;
622
+ min-width: 0;
623
+ }
624
+
625
+ .inv-dashboard__count-name {
626
+ display: block;
627
+ font-size: var(--text-sm, 0.875rem);
628
+ font-weight: 500;
629
+ color: var(--admin-text-primary);
630
+ overflow: hidden;
631
+ text-overflow: ellipsis;
632
+ white-space: nowrap;
633
+ margin-bottom: var(--space-1, 0.25rem);
634
+ }
635
+
636
+ .inv-dashboard__count-progress {
637
+ display: flex;
638
+ align-items: center;
639
+ gap: var(--space-2, 0.5rem);
640
+ }
641
+
642
+ .inv-dashboard__count-progress-track {
643
+ flex: 1;
644
+ height: 6px;
645
+ border-radius: 3px;
646
+ background: var(--admin-border);
647
+ overflow: hidden;
648
+ }
649
+
650
+ .inv-dashboard__count-progress-fill {
651
+ height: 100%;
652
+ border-radius: 3px;
653
+ background: var(--color-primary, #2563eb);
654
+ transition: width 0.3s ease;
655
+ }
656
+
657
+ .inv-dashboard__count-pct {
658
+ font-size: var(--text-xs, 0.75rem);
659
+ font-weight: 600;
660
+ font-variant-numeric: tabular-nums;
661
+ color: var(--admin-text-secondary);
662
+ }
663
+
664
+ .inv-dashboard__no-counts {
665
+ margin: 0;
666
+ font-size: var(--text-sm, 0.875rem);
667
+ color: var(--admin-text-tertiary);
668
+ font-style: italic;
669
+ }
670
+ </style>