@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,683 @@
1
+ <template>
2
+ <div class="count-session-list">
3
+ <!-- Header -->
4
+ <div class="count-session-list__header">
5
+ <h1 class="count-session-list__title">Cycle Counts</h1>
6
+ <InvButton
7
+ variant="primary"
8
+ size="md"
9
+ @click="showCreateForm = !showCreateForm"
10
+ >
11
+ <template #icon-left>
12
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
13
+ <path d="M8 3V13M3 8H13" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
14
+ </svg>
15
+ </template>
16
+ New
17
+ </InvButton>
18
+ </div>
19
+
20
+ <!-- Inline create form -->
21
+ <Transition name="count-create">
22
+ <InvCard
23
+ v-if="showCreateForm"
24
+ title="New Count Session"
25
+ class="count-session-list__create"
26
+ >
27
+ <form @submit.prevent="handleCreate">
28
+ <div class="count-session-list__create-fields">
29
+ <InvInput
30
+ v-model="newSessionName"
31
+ label="Session Name"
32
+ placeholder="e.g. Full Warehouse Count - March 2026"
33
+ :error="createError"
34
+ autofocus
35
+ />
36
+ <div class="count-session-list__scope-field">
37
+ <label class="count-session-list__scope-label">
38
+ Scope (optional)
39
+ </label>
40
+ <p class="count-session-list__scope-hint">
41
+ Limit count to a specific location and its children
42
+ </p>
43
+ <div class="count-session-list__scope-input">
44
+ <input
45
+ v-model="scopeSearch"
46
+ type="search"
47
+ class="count-session-list__scope-search"
48
+ placeholder="Search locations..."
49
+ @input="handleScopeSearch"
50
+ />
51
+ <div
52
+ v-if="scopeResults.length > 0"
53
+ class="count-session-list__scope-results"
54
+ role="listbox"
55
+ aria-label="Scope location results"
56
+ >
57
+ <button
58
+ v-for="loc in scopeResults"
59
+ :key="loc.id"
60
+ type="button"
61
+ role="option"
62
+ :aria-selected="selectedScope?.id === loc.id"
63
+ class="count-session-list__scope-option"
64
+ :class="{ 'count-session-list__scope-option--selected': selectedScope?.id === loc.id }"
65
+ @click="selectScope(loc)"
66
+ >
67
+ <LocationCodeBadge :code="loc.full_code" />
68
+ <span>{{ loc.name }}</span>
69
+ </button>
70
+ </div>
71
+ <div
72
+ v-if="selectedScope"
73
+ class="count-session-list__scope-selected"
74
+ >
75
+ <LocationCodeBadge :code="selectedScope.full_code" />
76
+ <span>{{ selectedScope.name }}</span>
77
+ <button
78
+ type="button"
79
+ class="count-session-list__scope-clear"
80
+ aria-label="Clear scope selection"
81
+ @click="clearScope"
82
+ >
83
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
84
+ <path d="M10 4L4 10M4 4L10 10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
85
+ </svg>
86
+ </button>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ <div class="count-session-list__create-actions">
92
+ <InvButton
93
+ variant="ghost"
94
+ size="sm"
95
+ @click="cancelCreate"
96
+ >
97
+ Cancel
98
+ </InvButton>
99
+ <InvButton
100
+ variant="primary"
101
+ size="sm"
102
+ type="submit"
103
+ :loading="creating"
104
+ :disabled="!newSessionName.trim()"
105
+ >
106
+ Create Session
107
+ </InvButton>
108
+ </div>
109
+ </form>
110
+ </InvCard>
111
+ </Transition>
112
+
113
+ <!-- Loading state -->
114
+ <div v-if="loading && sessions.length === 0" class="count-session-list__loading">
115
+ <InvCard loading />
116
+ <InvCard loading />
117
+ </div>
118
+
119
+ <!-- Active sessions -->
120
+ <section
121
+ v-if="activeSessions.length > 0"
122
+ class="count-session-list__section"
123
+ >
124
+ <h2 class="count-session-list__section-title">Active</h2>
125
+ <div class="count-session-list__cards">
126
+ <InvCard
127
+ v-for="session in activeSessions"
128
+ :key="session.id"
129
+ class="count-session-list__active-card"
130
+ >
131
+ <div class="count-session-list__card-header">
132
+ <h3 class="count-session-list__card-name">{{ session.name }}</h3>
133
+ <InvBadge
134
+ :variant="session.status === 'in_progress' ? 'default' : 'muted'"
135
+ >
136
+ {{ session.status === 'in_progress' ? 'In Progress' : 'Pending' }}
137
+ </InvBadge>
138
+ </div>
139
+
140
+ <!-- Progress bar -->
141
+ <div
142
+ class="count-session-list__progress"
143
+ role="progressbar"
144
+ :aria-valuenow="session.progress.percent_complete"
145
+ aria-valuemin="0"
146
+ aria-valuemax="100"
147
+ :aria-label="`Counting progress: ${session.progress.percent_complete}%`"
148
+ >
149
+ <div class="count-session-list__progress-track">
150
+ <div
151
+ class="count-session-list__progress-fill"
152
+ :class="progressColorClass(session.progress.percent_complete)"
153
+ :style="{ width: `${session.progress.percent_complete}%` }"
154
+ />
155
+ </div>
156
+ <span class="count-session-list__progress-label">
157
+ {{ session.progress.percent_complete }}%
158
+ </span>
159
+ </div>
160
+
161
+ <p class="count-session-list__card-stats">
162
+ {{ session.progress.counted }}/{{ session.progress.total }} locations counted,
163
+ {{ session.progress.discrepancies }} discrepancies
164
+ </p>
165
+
166
+ <div v-if="session.started_at" class="count-session-list__card-meta">
167
+ Started: {{ formatDate(session.started_at) }}
168
+ </div>
169
+
170
+ <div class="count-session-list__card-actions">
171
+ <InvButton
172
+ v-if="session.status === 'in_progress'"
173
+ variant="primary"
174
+ size="sm"
175
+ @click="navigateToCount(session.id)"
176
+ >
177
+ Continue Counting
178
+ </InvButton>
179
+ <InvButton
180
+ variant="secondary"
181
+ size="sm"
182
+ @click="navigateToDetail(session.id)"
183
+ >
184
+ View
185
+ </InvButton>
186
+ </div>
187
+ </InvCard>
188
+ </div>
189
+ </section>
190
+
191
+ <!-- Completed / cancelled sessions -->
192
+ <section
193
+ v-if="completedSessions.length > 0"
194
+ class="count-session-list__section"
195
+ >
196
+ <h2 class="count-session-list__section-title">Recent</h2>
197
+ <div class="count-session-list__completed">
198
+ <button
199
+ v-for="session in completedSessions"
200
+ :key="session.id"
201
+ type="button"
202
+ class="count-session-list__completed-row"
203
+ @click="navigateToDetail(session.id)"
204
+ >
205
+ <div class="count-session-list__completed-info">
206
+ <span class="count-session-list__completed-name">{{ session.name }}</span>
207
+ <InvBadge
208
+ :variant="session.status === 'completed' ? 'success' : 'muted'"
209
+ size="sm"
210
+ >
211
+ {{ session.status === 'completed' ? 'Completed' : 'Cancelled' }}
212
+ </InvBadge>
213
+ </div>
214
+ <div class="count-session-list__completed-meta">
215
+ <span v-if="session.status === 'completed' && session.progress">
216
+ Accuracy: {{ session.progress.total > 0
217
+ ? (((session.progress.total - session.progress.discrepancies) / session.progress.total) * 100).toFixed(1)
218
+ : '0.0'
219
+ }}%
220
+ </span>
221
+ <span v-if="session.completed_at">{{ formatDate(session.completed_at) }}</span>
222
+ <span v-else-if="session.updated_at">{{ formatDate(session.updated_at) }}</span>
223
+ </div>
224
+ </button>
225
+ </div>
226
+ </section>
227
+
228
+ <!-- Empty state -->
229
+ <InvEmptyState
230
+ v-if="!loading && sessions.length === 0"
231
+ title="No count sessions"
232
+ description="Create a cycle count session to track inventory accuracy."
233
+ action-label="Create Session"
234
+ @action="showCreateForm = true"
235
+ />
236
+ </div>
237
+ </template>
238
+
239
+ <script setup lang="ts">
240
+ import { ref, computed, onMounted } from 'vue'
241
+ import { useRouter } from 'vue-router'
242
+ import { useCountSessions } from '../../composables/useCountSessions'
243
+ import { useLocationSearch } from '../../composables/useLocationSearch'
244
+ import type { Location, CountSession } from '../../types'
245
+ import InvCard from '../shared/InvCard.vue'
246
+ import InvButton from '../shared/InvButton.vue'
247
+ import InvBadge from '../shared/InvBadge.vue'
248
+ import InvInput from '../shared/InvInput.vue'
249
+ import InvEmptyState from '../shared/InvEmptyState.vue'
250
+ import LocationCodeBadge from '../locations/LocationCodeBadge.vue'
251
+
252
+ const router = useRouter()
253
+ const {
254
+ sessions,
255
+ loading,
256
+ fetchSessions,
257
+ createSession,
258
+ } = useCountSessions()
259
+
260
+ const locationSearch = useLocationSearch()
261
+
262
+ const showCreateForm = ref(false)
263
+ const newSessionName = ref('')
264
+ const createError = ref<string | undefined>(undefined)
265
+ const creating = ref(false)
266
+ const scopeSearch = ref('')
267
+ const scopeResults = ref<Location[]>([])
268
+ const selectedScope = ref<Location | null>(null)
269
+
270
+ onMounted(async () => {
271
+ await fetchSessions()
272
+ })
273
+
274
+ const activeSessions = computed<CountSession[]>(() =>
275
+ sessions.value.filter(s => s.status === 'pending' || s.status === 'in_progress')
276
+ )
277
+
278
+ const completedSessions = computed<CountSession[]>(() =>
279
+ sessions.value.filter(s => s.status === 'completed' || s.status === 'cancelled')
280
+ )
281
+
282
+ function progressColorClass(percent: number): string {
283
+ if (percent >= 90) return 'count-session-list__progress-fill--success'
284
+ if (percent >= 50) return 'count-session-list__progress-fill--primary'
285
+ return 'count-session-list__progress-fill--warning'
286
+ }
287
+
288
+ function handleScopeSearch() {
289
+ if (selectedScope.value) return
290
+ locationSearch.search(scopeSearch.value)
291
+ scopeResults.value = locationSearch.results.value
292
+ }
293
+
294
+ function selectScope(location: Location) {
295
+ selectedScope.value = location
296
+ scopeSearch.value = ''
297
+ scopeResults.value = []
298
+ }
299
+
300
+ function clearScope() {
301
+ selectedScope.value = null
302
+ scopeSearch.value = ''
303
+ scopeResults.value = []
304
+ }
305
+
306
+ function cancelCreate() {
307
+ showCreateForm.value = false
308
+ newSessionName.value = ''
309
+ createError.value = undefined
310
+ clearScope()
311
+ }
312
+
313
+ async function handleCreate() {
314
+ const name = newSessionName.value.trim()
315
+ if (!name) {
316
+ createError.value = 'Name is required'
317
+ return
318
+ }
319
+
320
+ creating.value = true
321
+ createError.value = undefined
322
+
323
+ try {
324
+ const session = await createSession(name, selectedScope.value?.id)
325
+ showCreateForm.value = false
326
+ newSessionName.value = ''
327
+ clearScope()
328
+ navigateToDetail(session.id)
329
+ } catch (err: any) {
330
+ createError.value = err?.response?.data?.message || 'Failed to create session'
331
+ } finally {
332
+ creating.value = false
333
+ }
334
+ }
335
+
336
+ function navigateToDetail(id: number) {
337
+ router.push({ name: 'inventory-count-detail', params: { id } })
338
+ }
339
+
340
+ function navigateToCount(id: number) {
341
+ router.push({ name: 'inventory-count-entry', params: { id } })
342
+ }
343
+
344
+ function formatDate(isoDate: string): string {
345
+ return new Date(isoDate).toLocaleDateString('en-US', {
346
+ month: 'short',
347
+ day: 'numeric',
348
+ year: 'numeric',
349
+ })
350
+ }
351
+ </script>
352
+
353
+ <style scoped>
354
+ .count-session-list {
355
+ max-width: 800px;
356
+ }
357
+
358
+ .count-session-list__header {
359
+ display: flex;
360
+ align-items: center;
361
+ justify-content: space-between;
362
+ gap: var(--space-3, 0.75rem);
363
+ margin-bottom: var(--space-5, 1.25rem);
364
+ }
365
+
366
+ .count-session-list__title {
367
+ margin: 0;
368
+ font-size: var(--text-xl, 1.25rem);
369
+ font-weight: 700;
370
+ color: var(--admin-text-primary);
371
+ }
372
+
373
+ .count-session-list__create {
374
+ margin-bottom: var(--space-5, 1.25rem);
375
+ }
376
+
377
+ .count-session-list__create-fields {
378
+ display: flex;
379
+ flex-direction: column;
380
+ gap: var(--space-4, 1rem);
381
+ }
382
+
383
+ .count-session-list__scope-field {
384
+ display: flex;
385
+ flex-direction: column;
386
+ gap: var(--space-1, 0.25rem);
387
+ }
388
+
389
+ .count-session-list__scope-label {
390
+ font-size: var(--text-sm, 0.875rem);
391
+ font-weight: 500;
392
+ color: var(--admin-text-primary);
393
+ }
394
+
395
+ .count-session-list__scope-hint {
396
+ margin: 0;
397
+ font-size: var(--text-xs, 0.75rem);
398
+ color: var(--admin-text-tertiary);
399
+ line-height: 1.4;
400
+ }
401
+
402
+ .count-session-list__scope-input {
403
+ position: relative;
404
+ margin-top: var(--space-1, 0.25rem);
405
+ }
406
+
407
+ .count-session-list__scope-search {
408
+ width: 100%;
409
+ height: 44px;
410
+ padding: 0 var(--space-3, 0.75rem);
411
+ border: 1px solid var(--admin-border);
412
+ border-radius: 6px;
413
+ background: var(--admin-card-bg);
414
+ color: var(--admin-text-primary);
415
+ font-size: var(--text-sm, 0.875rem);
416
+ outline: none;
417
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
418
+ }
419
+
420
+ .count-session-list__scope-search:focus {
421
+ border-color: var(--color-primary, #2563eb);
422
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
423
+ }
424
+
425
+ .count-session-list__scope-results {
426
+ position: absolute;
427
+ top: 100%;
428
+ left: 0;
429
+ right: 0;
430
+ z-index: 10;
431
+ max-height: 200px;
432
+ overflow-y: auto;
433
+ background: var(--admin-card-bg);
434
+ border: 1px solid var(--admin-border);
435
+ border-radius: 6px;
436
+ box-shadow: var(--shadow-md);
437
+ margin-top: var(--space-1, 0.25rem);
438
+ }
439
+
440
+ .count-session-list__scope-option {
441
+ display: flex;
442
+ align-items: center;
443
+ gap: var(--space-2, 0.5rem);
444
+ width: 100%;
445
+ padding: var(--space-2, 0.5rem) var(--space-3, 0.75rem);
446
+ border: none;
447
+ background: none;
448
+ color: var(--admin-text-primary);
449
+ font-size: var(--text-sm, 0.875rem);
450
+ cursor: pointer;
451
+ text-align: left;
452
+ }
453
+
454
+ .count-session-list__scope-option:hover,
455
+ .count-session-list__scope-option--selected {
456
+ background: var(--admin-content-bg);
457
+ }
458
+
459
+ .count-session-list__scope-option:focus-visible {
460
+ outline: 2px solid var(--admin-focus-ring, #2563eb);
461
+ outline-offset: -2px;
462
+ }
463
+
464
+ .count-session-list__scope-selected {
465
+ display: flex;
466
+ align-items: center;
467
+ gap: var(--space-2, 0.5rem);
468
+ margin-top: var(--space-2, 0.5rem);
469
+ padding: var(--space-2, 0.5rem) var(--space-3, 0.75rem);
470
+ border: 1px solid var(--admin-border);
471
+ border-radius: 6px;
472
+ background: var(--admin-content-bg);
473
+ font-size: var(--text-sm, 0.875rem);
474
+ color: var(--admin-text-primary);
475
+ }
476
+
477
+ .count-session-list__scope-clear {
478
+ display: flex;
479
+ align-items: center;
480
+ justify-content: center;
481
+ width: 24px;
482
+ height: 24px;
483
+ padding: 0;
484
+ border: none;
485
+ border-radius: 50%;
486
+ background: none;
487
+ color: var(--admin-text-tertiary);
488
+ cursor: pointer;
489
+ margin-left: auto;
490
+ flex-shrink: 0;
491
+ }
492
+
493
+ .count-session-list__scope-clear:hover {
494
+ color: var(--admin-text-primary);
495
+ background: var(--admin-border);
496
+ }
497
+
498
+ .count-session-list__scope-clear:focus-visible {
499
+ outline: 2px solid var(--admin-focus-ring, #2563eb);
500
+ outline-offset: 2px;
501
+ }
502
+
503
+ .count-session-list__create-actions {
504
+ display: flex;
505
+ justify-content: flex-end;
506
+ gap: var(--space-2, 0.5rem);
507
+ margin-top: var(--space-4, 1rem);
508
+ }
509
+
510
+ .count-session-list__loading {
511
+ display: flex;
512
+ flex-direction: column;
513
+ gap: var(--space-4, 1rem);
514
+ }
515
+
516
+ .count-session-list__section {
517
+ margin-bottom: var(--space-6, 1.5rem);
518
+ }
519
+
520
+ .count-session-list__section-title {
521
+ margin: 0 0 var(--space-3, 0.75rem);
522
+ font-size: var(--text-sm, 0.875rem);
523
+ font-weight: 600;
524
+ text-transform: uppercase;
525
+ letter-spacing: 0.05em;
526
+ color: var(--admin-text-secondary);
527
+ }
528
+
529
+ .count-session-list__cards {
530
+ display: flex;
531
+ flex-direction: column;
532
+ gap: var(--space-4, 1rem);
533
+ }
534
+
535
+ .count-session-list__card-header {
536
+ display: flex;
537
+ align-items: center;
538
+ justify-content: space-between;
539
+ gap: var(--space-2, 0.5rem);
540
+ margin-bottom: var(--space-3, 0.75rem);
541
+ }
542
+
543
+ .count-session-list__card-name {
544
+ margin: 0;
545
+ font-size: var(--text-base, 1rem);
546
+ font-weight: 600;
547
+ color: var(--admin-text-primary);
548
+ }
549
+
550
+ .count-session-list__progress {
551
+ display: flex;
552
+ align-items: center;
553
+ gap: var(--space-2, 0.5rem);
554
+ margin-bottom: var(--space-2, 0.5rem);
555
+ }
556
+
557
+ .count-session-list__progress-track {
558
+ flex: 1;
559
+ height: 8px;
560
+ border-radius: 4px;
561
+ background: var(--admin-border);
562
+ overflow: hidden;
563
+ }
564
+
565
+ .count-session-list__progress-fill {
566
+ height: 100%;
567
+ border-radius: 4px;
568
+ transition: width 0.3s ease;
569
+ }
570
+
571
+ .count-session-list__progress-fill--primary {
572
+ background: var(--color-primary, #2563eb);
573
+ }
574
+
575
+ .count-session-list__progress-fill--success {
576
+ background: var(--color-success, #059669);
577
+ }
578
+
579
+ .count-session-list__progress-fill--warning {
580
+ background: var(--color-warning, #d97706);
581
+ }
582
+
583
+ .count-session-list__progress-label {
584
+ font-size: var(--text-sm, 0.875rem);
585
+ font-weight: 600;
586
+ font-variant-numeric: tabular-nums;
587
+ color: var(--admin-text-secondary);
588
+ min-width: 36px;
589
+ text-align: right;
590
+ }
591
+
592
+ .count-session-list__card-stats {
593
+ margin: 0 0 var(--space-2, 0.5rem);
594
+ font-size: var(--text-sm, 0.875rem);
595
+ color: var(--admin-text-secondary);
596
+ }
597
+
598
+ .count-session-list__card-meta {
599
+ font-size: var(--text-xs, 0.75rem);
600
+ color: var(--admin-text-tertiary);
601
+ margin-bottom: var(--space-3, 0.75rem);
602
+ }
603
+
604
+ .count-session-list__card-actions {
605
+ display: flex;
606
+ gap: var(--space-2, 0.5rem);
607
+ }
608
+
609
+ .count-session-list__completed {
610
+ display: flex;
611
+ flex-direction: column;
612
+ border: 1px solid var(--admin-border);
613
+ border-radius: 8px;
614
+ overflow: hidden;
615
+ }
616
+
617
+ .count-session-list__completed-row {
618
+ display: flex;
619
+ align-items: center;
620
+ justify-content: space-between;
621
+ gap: var(--space-3, 0.75rem);
622
+ padding: var(--space-3, 0.75rem) var(--space-4, 1rem);
623
+ border: none;
624
+ background: var(--admin-card-bg);
625
+ color: var(--admin-text-primary);
626
+ cursor: pointer;
627
+ width: 100%;
628
+ text-align: left;
629
+ font: inherit;
630
+ border-bottom: 1px solid var(--admin-border);
631
+ transition: background-color 0.1s ease;
632
+ }
633
+
634
+ .count-session-list__completed-row:last-child {
635
+ border-bottom: none;
636
+ }
637
+
638
+ .count-session-list__completed-row:hover {
639
+ background: var(--admin-content-bg);
640
+ }
641
+
642
+ .count-session-list__completed-row:focus-visible {
643
+ outline: 2px solid var(--admin-focus-ring, #2563eb);
644
+ outline-offset: -2px;
645
+ }
646
+
647
+ .count-session-list__completed-info {
648
+ display: flex;
649
+ align-items: center;
650
+ gap: var(--space-2, 0.5rem);
651
+ min-width: 0;
652
+ }
653
+
654
+ .count-session-list__completed-name {
655
+ font-size: var(--text-sm, 0.875rem);
656
+ font-weight: 500;
657
+ overflow: hidden;
658
+ text-overflow: ellipsis;
659
+ white-space: nowrap;
660
+ }
661
+
662
+ .count-session-list__completed-meta {
663
+ display: flex;
664
+ align-items: center;
665
+ gap: var(--space-3, 0.75rem);
666
+ font-size: var(--text-xs, 0.75rem);
667
+ color: var(--admin-text-tertiary);
668
+ flex-shrink: 0;
669
+ }
670
+
671
+ /* Transition */
672
+ .count-create-enter-active,
673
+ .count-create-leave-active {
674
+ transition: opacity 0.2s ease, max-height 0.2s ease;
675
+ overflow: hidden;
676
+ }
677
+
678
+ .count-create-enter-from,
679
+ .count-create-leave-to {
680
+ opacity: 0;
681
+ max-height: 0;
682
+ }
683
+ </style>