@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,650 @@
1
+ <template>
2
+ <div class="count-detail">
3
+ <!-- Loading -->
4
+ <InvCard v-if="loading && !session" loading />
5
+
6
+ <!-- Error -->
7
+ <InvEmptyState
8
+ v-else-if="error && !session"
9
+ title="Could not load session"
10
+ :description="error"
11
+ action-label="Retry"
12
+ @action="loadSession"
13
+ />
14
+
15
+ <template v-if="session">
16
+ <!-- Header -->
17
+ <div class="count-detail__header">
18
+ <div class="count-detail__header-info">
19
+ <h1 class="count-detail__title">{{ session.name }}</h1>
20
+ <div class="count-detail__meta">
21
+ <InvBadge :variant="statusVariant">{{ statusLabel }}</InvBadge>
22
+ <span v-if="session.started_at" class="count-detail__meta-item">
23
+ Started: {{ formatDate(session.started_at) }}
24
+ </span>
25
+ <span v-if="session.scope_location_id" class="count-detail__meta-item">
26
+ Scope: Location #{{ session.scope_location_id }}
27
+ </span>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <!-- Progress card -->
33
+ <InvCard title="Progress" class="count-detail__progress-card">
34
+ <div
35
+ class="count-detail__progress-bar"
36
+ role="progressbar"
37
+ :aria-valuenow="session.progress.percent_complete"
38
+ aria-valuemin="0"
39
+ aria-valuemax="100"
40
+ :aria-label="`${session.progress.percent_complete}% complete`"
41
+ >
42
+ <div class="count-detail__progress-track">
43
+ <div
44
+ class="count-detail__progress-fill"
45
+ :class="progressClass"
46
+ :style="{ width: `${session.progress.percent_complete}%` }"
47
+ />
48
+ </div>
49
+ <span class="count-detail__progress-pct">
50
+ {{ session.progress.percent_complete }}%
51
+ </span>
52
+ </div>
53
+
54
+ <div class="count-detail__stats-grid">
55
+ <div class="count-detail__stat">
56
+ <span class="count-detail__stat-value">{{ session.progress.total }}</span>
57
+ <span class="count-detail__stat-label">Total</span>
58
+ </div>
59
+ <div class="count-detail__stat">
60
+ <span class="count-detail__stat-value">{{ session.progress.counted }}</span>
61
+ <span class="count-detail__stat-label">Counted</span>
62
+ </div>
63
+ <div class="count-detail__stat">
64
+ <span class="count-detail__stat-value">{{ session.progress.verified }}</span>
65
+ <span class="count-detail__stat-label">Verified</span>
66
+ </div>
67
+ <div class="count-detail__stat">
68
+ <span class="count-detail__stat-value count-detail__stat-value--pending">
69
+ {{ session.progress.total - session.progress.counted }}
70
+ </span>
71
+ <span class="count-detail__stat-label">Pending</span>
72
+ </div>
73
+ <div class="count-detail__stat">
74
+ <span class="count-detail__stat-value count-detail__stat-value--warning">
75
+ {{ session.progress.discrepancies }}
76
+ </span>
77
+ <span class="count-detail__stat-label">Discrepancies</span>
78
+ </div>
79
+ </div>
80
+ </InvCard>
81
+
82
+ <!-- Action buttons -->
83
+ <InvCard title="Actions" class="count-detail__actions-card">
84
+ <div class="count-detail__action-grid">
85
+ <InvButton
86
+ v-if="canGenerate"
87
+ variant="secondary"
88
+ size="md"
89
+ :loading="generating"
90
+ @click="handleGenerate"
91
+ >
92
+ Generate Entries
93
+ </InvButton>
94
+ <InvButton
95
+ v-if="canStartCounting"
96
+ variant="primary"
97
+ size="md"
98
+ @click="handleStartCounting"
99
+ >
100
+ Start Counting
101
+ </InvButton>
102
+ <InvButton
103
+ v-if="hasCounted"
104
+ variant="secondary"
105
+ size="md"
106
+ @click="toggleDiscrepancyFilter"
107
+ >
108
+ {{ showDiscrepanciesOnly ? 'Show All' : 'Review Discrepancies' }}
109
+ </InvButton>
110
+ <InvButton
111
+ v-if="canApply"
112
+ variant="primary"
113
+ size="md"
114
+ :loading="applying"
115
+ @click="handleApply"
116
+ >
117
+ Apply Adjustments
118
+ </InvButton>
119
+ <InvButton
120
+ v-if="session.status === 'completed'"
121
+ variant="secondary"
122
+ size="md"
123
+ @click="handleViewReport"
124
+ >
125
+ View Report
126
+ </InvButton>
127
+ <InvButton
128
+ v-if="session.status !== 'completed' && session.status !== 'cancelled'"
129
+ variant="danger"
130
+ size="md"
131
+ @click="handleCancel"
132
+ >
133
+ Cancel
134
+ </InvButton>
135
+ </div>
136
+ </InvCard>
137
+
138
+ <!-- Entry table -->
139
+ <InvCard title="Entries" class="count-detail__entries-card" no-padding>
140
+ <!-- Filters -->
141
+ <template #actions>
142
+ <div class="count-detail__filters">
143
+ <InvSelect
144
+ v-model="statusFilter"
145
+ :options="statusOptions"
146
+ placeholder="All Statuses"
147
+ />
148
+ <label class="count-detail__discrepancy-toggle">
149
+ <input
150
+ v-model="showDiscrepanciesOnly"
151
+ type="checkbox"
152
+ class="count-detail__discrepancy-checkbox"
153
+ />
154
+ <span>Discrepancies only</span>
155
+ </label>
156
+ </div>
157
+ </template>
158
+
159
+ <InvTable
160
+ :columns="entryColumns"
161
+ :data="filteredEntries"
162
+ :loading="entriesLoading"
163
+ clickable
164
+ empty-message="No entries. Generate entries to start counting."
165
+ @row-click="handleEntryClick"
166
+ >
167
+ <template #cell-status="{ value }">
168
+ <span
169
+ class="count-detail__entry-status"
170
+ :title="entryStatusLabel(value)"
171
+ >
172
+ {{ entryStatusIcon(value) }}
173
+ </span>
174
+ </template>
175
+ <template #cell-product_name="{ value }">
176
+ <span class="count-detail__entry-product">{{ value }}</span>
177
+ </template>
178
+ <template #cell-expected_quantity="{ value }">
179
+ <span class="count-detail__entry-qty">{{ value ?? '-' }}</span>
180
+ </template>
181
+ <template #cell-counted_quantity="{ row }">
182
+ <span
183
+ v-if="row.counted_quantity !== null"
184
+ class="count-detail__entry-qty"
185
+ :class="{
186
+ 'count-detail__entry-qty--match': row.discrepancy === 0,
187
+ 'count-detail__entry-qty--discrepancy': row.discrepancy !== null && row.discrepancy !== 0,
188
+ }"
189
+ >
190
+ {{ row.counted_quantity }}
191
+ <span
192
+ v-if="row.discrepancy !== null && row.discrepancy !== 0"
193
+ class="count-detail__entry-disc"
194
+ >
195
+ ({{ row.discrepancy > 0 ? '+' : '' }}{{ row.discrepancy }})
196
+ </span>
197
+ </span>
198
+ <span v-else class="count-detail__entry-pending">pending</span>
199
+ </template>
200
+ </InvTable>
201
+ </InvCard>
202
+ </template>
203
+
204
+ <!-- Confirmation modal -->
205
+ <InvModal
206
+ :show="showConfirmModal"
207
+ :title="confirmTitle"
208
+ :description="confirmDescription"
209
+ size="sm"
210
+ @update:show="showConfirmModal = $event"
211
+ @close="showConfirmModal = false"
212
+ >
213
+ <template #footer>
214
+ <InvButton variant="ghost" size="sm" @click="showConfirmModal = false">
215
+ Cancel
216
+ </InvButton>
217
+ <InvButton
218
+ :variant="confirmDanger ? 'danger' : 'primary'"
219
+ size="sm"
220
+ :loading="confirmLoading"
221
+ @click="confirmAction"
222
+ >
223
+ {{ confirmLabel }}
224
+ </InvButton>
225
+ </template>
226
+ </InvModal>
227
+ </div>
228
+ </template>
229
+
230
+ <script setup lang="ts">
231
+ import { ref, computed, onMounted, watch } from 'vue'
232
+ import { useRouter } from 'vue-router'
233
+ import { useCountSessions } from '../../composables/useCountSessions'
234
+ import { useCountStore } from '../../stores/countStore'
235
+ import type { CountSession, CountEntry, CountEntryStatus, Column } from '../../types'
236
+ import InvCard from '../shared/InvCard.vue'
237
+ import InvButton from '../shared/InvButton.vue'
238
+ import InvBadge from '../shared/InvBadge.vue'
239
+ import InvTable from '../shared/InvTable.vue'
240
+ import InvSelect from '../shared/InvSelect.vue'
241
+ import InvModal from '../shared/InvModal.vue'
242
+ import InvEmptyState from '../shared/InvEmptyState.vue'
243
+
244
+ const props = defineProps<{
245
+ sessionId: number
246
+ }>()
247
+
248
+ const router = useRouter()
249
+ const countStore = useCountStore()
250
+ const {
251
+ currentSession: session,
252
+ entries,
253
+ loading,
254
+ error,
255
+ fetchSession,
256
+ fetchEntries,
257
+ generateEntries,
258
+ updateStatus,
259
+ applySession,
260
+ verifyEntry,
261
+ } = useCountSessions()
262
+
263
+ const entriesLoading = ref(false)
264
+ const generating = ref(false)
265
+ const applying = ref(false)
266
+ const statusFilter = ref<string>('')
267
+ const showDiscrepanciesOnly = ref(false)
268
+
269
+ // Confirmation modal state
270
+ const showConfirmModal = ref(false)
271
+ const confirmTitle = ref('')
272
+ const confirmDescription = ref('')
273
+ const confirmLabel = ref('')
274
+ const confirmDanger = ref(false)
275
+ const confirmLoading = ref(false)
276
+ let pendingConfirmAction: (() => Promise<void>) | null = null
277
+
278
+ const entryColumns: Column[] = [
279
+ { key: 'status', label: 'Status', width: '60px', align: 'center' },
280
+ { key: 'product_name', label: 'Product' },
281
+ { key: 'expected_quantity', label: 'Expected', width: '100px', align: 'right' },
282
+ { key: 'counted_quantity', label: 'Counted', width: '140px', align: 'right' },
283
+ ]
284
+
285
+ const statusOptions = [
286
+ { value: '', label: 'All Statuses' },
287
+ { value: 'pending', label: 'Pending' },
288
+ { value: 'counted', label: 'Counted' },
289
+ { value: 'verified', label: 'Verified' },
290
+ { value: 'adjusted', label: 'Adjusted' },
291
+ ]
292
+
293
+ const statusVariant = computed(() => {
294
+ if (!session.value) return 'muted' as const
295
+ const map: Record<string, 'default' | 'success' | 'warning' | 'error' | 'muted'> = {
296
+ pending: 'muted',
297
+ in_progress: 'default',
298
+ completed: 'success',
299
+ cancelled: 'error',
300
+ }
301
+ return map[session.value.status] ?? 'muted'
302
+ })
303
+
304
+ const statusLabel = computed(() => {
305
+ if (!session.value) return ''
306
+ const map: Record<string, string> = {
307
+ pending: 'Pending',
308
+ in_progress: 'In Progress',
309
+ completed: 'Completed',
310
+ cancelled: 'Cancelled',
311
+ }
312
+ return map[session.value.status] ?? session.value.status
313
+ })
314
+
315
+ const progressClass = computed(() => {
316
+ if (!session.value) return ''
317
+ const pct = session.value.progress.percent_complete
318
+ if (pct >= 90) return 'count-detail__progress-fill--success'
319
+ if (pct >= 50) return 'count-detail__progress-fill--primary'
320
+ return 'count-detail__progress-fill--warning'
321
+ })
322
+
323
+ const canGenerate = computed(() =>
324
+ session.value?.status === 'pending' && entries.value.length === 0
325
+ )
326
+
327
+ const canStartCounting = computed(() =>
328
+ session.value &&
329
+ (session.value.status === 'pending' || session.value.status === 'in_progress') &&
330
+ entries.value.length > 0
331
+ )
332
+
333
+ const hasCounted = computed(() =>
334
+ entries.value.some(e => e.status !== 'pending')
335
+ )
336
+
337
+ const canApply = computed(() =>
338
+ entries.value.some(
339
+ e => e.status === 'verified' && e.discrepancy !== null && e.discrepancy !== 0
340
+ )
341
+ )
342
+
343
+ const filteredEntries = computed(() => {
344
+ let result = entries.value
345
+ if (statusFilter.value) {
346
+ result = result.filter(e => e.status === statusFilter.value)
347
+ }
348
+ if (showDiscrepanciesOnly.value) {
349
+ result = result.filter(e => e.discrepancy !== null && e.discrepancy !== 0)
350
+ }
351
+ return result
352
+ })
353
+
354
+ onMounted(() => loadSession())
355
+
356
+ async function loadSession() {
357
+ try {
358
+ await fetchSession(props.sessionId)
359
+ await loadEntries()
360
+ } catch {
361
+ // error state handled by template
362
+ }
363
+ }
364
+
365
+ async function loadEntries() {
366
+ entriesLoading.value = true
367
+ try {
368
+ await fetchEntries(props.sessionId)
369
+ } finally {
370
+ entriesLoading.value = false
371
+ }
372
+ }
373
+
374
+ async function handleGenerate() {
375
+ generating.value = true
376
+ try {
377
+ await generateEntries(props.sessionId)
378
+ await loadEntries()
379
+ await fetchSession(props.sessionId)
380
+ } finally {
381
+ generating.value = false
382
+ }
383
+ }
384
+
385
+ function handleStartCounting() {
386
+ countStore.startCounting(props.sessionId)
387
+ router.push({ name: 'inventory-count-entry', params: { id: props.sessionId } })
388
+ }
389
+
390
+ function toggleDiscrepancyFilter() {
391
+ showDiscrepanciesOnly.value = !showDiscrepanciesOnly.value
392
+ }
393
+
394
+ function handleApply() {
395
+ confirmTitle.value = 'Apply Adjustments'
396
+ confirmDescription.value = 'This will adjust inventory quantities based on verified counts. This action cannot be undone.'
397
+ confirmLabel.value = 'Apply'
398
+ confirmDanger.value = false
399
+ pendingConfirmAction = async () => {
400
+ applying.value = true
401
+ try {
402
+ await applySession(props.sessionId)
403
+ await fetchSession(props.sessionId)
404
+ await loadEntries()
405
+ } finally {
406
+ applying.value = false
407
+ }
408
+ }
409
+ showConfirmModal.value = true
410
+ }
411
+
412
+ function handleCancel() {
413
+ confirmTitle.value = 'Cancel Session'
414
+ confirmDescription.value = 'Are you sure you want to cancel this count session? Uncounted entries will be discarded.'
415
+ confirmLabel.value = 'Cancel Session'
416
+ confirmDanger.value = true
417
+ pendingConfirmAction = async () => {
418
+ await updateStatus(props.sessionId, 'cancelled')
419
+ await fetchSession(props.sessionId)
420
+ }
421
+ showConfirmModal.value = true
422
+ }
423
+
424
+ async function confirmAction() {
425
+ if (!pendingConfirmAction) return
426
+ confirmLoading.value = true
427
+ try {
428
+ await pendingConfirmAction()
429
+ showConfirmModal.value = false
430
+ } finally {
431
+ confirmLoading.value = false
432
+ }
433
+ }
434
+
435
+ function handleViewReport() {
436
+ router.push({ name: 'inventory-count-report', params: { id: props.sessionId } })
437
+ }
438
+
439
+ function handleEntryClick(entry: CountEntry) {
440
+ if (entry.status === 'counted' && entry.discrepancy !== null && entry.discrepancy !== 0) {
441
+ verifyEntry(props.sessionId, entry.id)
442
+ .then(() => loadEntries())
443
+ }
444
+ }
445
+
446
+ function entryStatusIcon(status: CountEntryStatus): string {
447
+ const icons: Record<CountEntryStatus, string> = {
448
+ pending: '\u23F3',
449
+ counted: '\u2713',
450
+ verified: '\u2705',
451
+ adjusted: '\uD83D\uDD27',
452
+ }
453
+ return icons[status] ?? status
454
+ }
455
+
456
+ function entryStatusLabel(status: CountEntryStatus): string {
457
+ const labels: Record<CountEntryStatus, string> = {
458
+ pending: 'Pending',
459
+ counted: 'Counted',
460
+ verified: 'Verified',
461
+ adjusted: 'Adjusted',
462
+ }
463
+ return labels[status] ?? status
464
+ }
465
+
466
+ function formatDate(isoDate: string): string {
467
+ return new Date(isoDate).toLocaleDateString('en-US', {
468
+ month: 'short',
469
+ day: 'numeric',
470
+ year: 'numeric',
471
+ })
472
+ }
473
+ </script>
474
+
475
+ <style scoped>
476
+ .count-detail {
477
+ max-width: 900px;
478
+ display: flex;
479
+ flex-direction: column;
480
+ gap: var(--space-5, 1.25rem);
481
+ }
482
+
483
+ .count-detail__header {
484
+ display: flex;
485
+ align-items: flex-start;
486
+ justify-content: space-between;
487
+ gap: var(--space-3, 0.75rem);
488
+ }
489
+
490
+ .count-detail__header-info {
491
+ min-width: 0;
492
+ }
493
+
494
+ .count-detail__title {
495
+ margin: 0 0 var(--space-2, 0.5rem);
496
+ font-size: var(--text-xl, 1.25rem);
497
+ font-weight: 700;
498
+ color: var(--admin-text-primary);
499
+ }
500
+
501
+ .count-detail__meta {
502
+ display: flex;
503
+ align-items: center;
504
+ gap: var(--space-3, 0.75rem);
505
+ flex-wrap: wrap;
506
+ }
507
+
508
+ .count-detail__meta-item {
509
+ font-size: var(--text-sm, 0.875rem);
510
+ color: var(--admin-text-secondary);
511
+ }
512
+
513
+ .count-detail__progress-bar {
514
+ display: flex;
515
+ align-items: center;
516
+ gap: var(--space-3, 0.75rem);
517
+ margin-bottom: var(--space-4, 1rem);
518
+ }
519
+
520
+ .count-detail__progress-track {
521
+ flex: 1;
522
+ height: 10px;
523
+ border-radius: 5px;
524
+ background: var(--admin-border);
525
+ overflow: hidden;
526
+ }
527
+
528
+ .count-detail__progress-fill {
529
+ height: 100%;
530
+ border-radius: 5px;
531
+ transition: width 0.3s ease;
532
+ }
533
+
534
+ .count-detail__progress-fill--primary {
535
+ background: var(--color-primary, #2563eb);
536
+ }
537
+
538
+ .count-detail__progress-fill--success {
539
+ background: var(--color-success, #059669);
540
+ }
541
+
542
+ .count-detail__progress-fill--warning {
543
+ background: var(--color-warning, #d97706);
544
+ }
545
+
546
+ .count-detail__progress-pct {
547
+ font-size: var(--text-base, 1rem);
548
+ font-weight: 700;
549
+ font-variant-numeric: tabular-nums;
550
+ color: var(--admin-text-primary);
551
+ min-width: 42px;
552
+ text-align: right;
553
+ }
554
+
555
+ .count-detail__stats-grid {
556
+ display: grid;
557
+ grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
558
+ gap: var(--space-4, 1rem);
559
+ }
560
+
561
+ .count-detail__stat {
562
+ display: flex;
563
+ flex-direction: column;
564
+ align-items: center;
565
+ gap: var(--space-1, 0.25rem);
566
+ }
567
+
568
+ .count-detail__stat-value {
569
+ font-size: var(--text-lg, 1.125rem);
570
+ font-weight: 700;
571
+ font-variant-numeric: tabular-nums;
572
+ color: var(--admin-text-primary);
573
+ }
574
+
575
+ .count-detail__stat-value--pending {
576
+ color: var(--admin-text-tertiary);
577
+ }
578
+
579
+ .count-detail__stat-value--warning {
580
+ color: var(--color-warning, #d97706);
581
+ }
582
+
583
+ .count-detail__stat-label {
584
+ font-size: var(--text-xs, 0.75rem);
585
+ color: var(--admin-text-secondary);
586
+ text-transform: uppercase;
587
+ letter-spacing: 0.05em;
588
+ }
589
+
590
+ .count-detail__action-grid {
591
+ display: flex;
592
+ flex-wrap: wrap;
593
+ gap: var(--space-2, 0.5rem);
594
+ }
595
+
596
+ .count-detail__filters {
597
+ display: flex;
598
+ align-items: center;
599
+ gap: var(--space-3, 0.75rem);
600
+ }
601
+
602
+ .count-detail__discrepancy-toggle {
603
+ display: flex;
604
+ align-items: center;
605
+ gap: var(--space-2, 0.5rem);
606
+ font-size: var(--text-sm, 0.875rem);
607
+ color: var(--admin-text-secondary);
608
+ cursor: pointer;
609
+ white-space: nowrap;
610
+ }
611
+
612
+ .count-detail__discrepancy-checkbox {
613
+ width: 16px;
614
+ height: 16px;
615
+ accent-color: var(--color-primary, #2563eb);
616
+ cursor: pointer;
617
+ }
618
+
619
+ .count-detail__entry-status {
620
+ font-size: var(--text-base, 1rem);
621
+ }
622
+
623
+ .count-detail__entry-product {
624
+ font-weight: 500;
625
+ color: var(--admin-text-primary);
626
+ }
627
+
628
+ .count-detail__entry-qty {
629
+ font-variant-numeric: tabular-nums;
630
+ }
631
+
632
+ .count-detail__entry-qty--match {
633
+ color: var(--color-success, #059669);
634
+ }
635
+
636
+ .count-detail__entry-qty--discrepancy {
637
+ color: var(--color-warning, #d97706);
638
+ }
639
+
640
+ .count-detail__entry-disc {
641
+ font-size: var(--text-xs, 0.75rem);
642
+ font-weight: 600;
643
+ }
644
+
645
+ .count-detail__entry-pending {
646
+ color: var(--admin-text-tertiary);
647
+ font-style: italic;
648
+ font-size: var(--text-sm, 0.875rem);
649
+ }
650
+ </style>