@visns-studio/visns-components 5.9.6 → 5.9.7

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.
@@ -3,16 +3,19 @@ import { Link, useLocation, useNavigate } from 'react-router-dom';
3
3
  import {
4
4
  Bug,
5
5
  Calendar,
6
+ CreditCardAlt1,
6
7
  Draft,
7
8
  PeopleGroup,
8
9
  Person,
9
10
  Search,
10
11
  SignOut,
11
12
  Shield,
13
+ Image,
12
14
  } from 'akar-icons';
13
15
  import CustomFetch from './Fetch';
14
16
  import Notification from './Notification';
15
17
  import SwitchAccount from './SwitchAccount';
18
+ import BusinessCardOcr from './BusinessCardOcr';
16
19
  import styles from './styles/Navigation.module.scss';
17
20
 
18
21
  const SearchComponent = ({
@@ -193,6 +196,7 @@ function Navigation({
193
196
  const [isOpen, setOpen] = useState(false);
194
197
  const [isScrolled, setIsScrolled] = useState(false);
195
198
  const [navData, setNavData] = useState(navConfig);
199
+ const [showBusinessCardOcr, setShowBusinessCardOcr] = useState(false);
196
200
 
197
201
  const appNavClasses = styles['app-nav'];
198
202
 
@@ -223,6 +227,7 @@ function Navigation({
223
227
  bug: Bug,
224
228
  calendar: Calendar,
225
229
  draft: Draft,
230
+ image: Image,
226
231
  peopleGroup: PeopleGroup,
227
232
  person: Person,
228
233
  shield: Shield,
@@ -241,6 +246,20 @@ function Navigation({
241
246
  <Notification setSystemAuth={setSystemAuth} />
242
247
  </li>
243
248
  );
249
+ } else if (n.id === 'businessCardOcr') {
250
+ return (
251
+ <li>
252
+ <button
253
+ title="Scan Business Card"
254
+ data-tooltip-id="system-tooltip"
255
+ data-tooltip-content="Scan Business Card"
256
+ onClick={() => setShowBusinessCardOcr(true)}
257
+ className={activeClass}
258
+ >
259
+ <CreditCardAlt1 size={20} />
260
+ </button>
261
+ </li>
262
+ );
244
263
  } else if (n.id === 'switchAccount') {
245
264
  return (
246
265
  <li>
@@ -599,6 +618,15 @@ function Navigation({
599
618
  </div>
600
619
  {children}
601
620
  </main>
621
+
622
+ <BusinessCardOcr
623
+ isOpen={showBusinessCardOcr}
624
+ onClose={() => setShowBusinessCardOcr(false)}
625
+ onContactSaved={(contact) => {
626
+ console.log('Contact saved:', contact);
627
+ }}
628
+ fields={navData.settings.find(s => s.id === 'businessCardOcr')?.fields || []}
629
+ />
602
630
  </div>
603
631
  ) : (
604
632
  <div className={styles.hwrap}>
@@ -635,6 +663,15 @@ function Navigation({
635
663
  )}
636
664
  </ul>
637
665
  </div>
666
+
667
+ <BusinessCardOcr
668
+ isOpen={showBusinessCardOcr}
669
+ onClose={() => setShowBusinessCardOcr(false)}
670
+ onContactSaved={(contact) => {
671
+ console.log('Contact saved:', contact);
672
+ }}
673
+ fields={navData.settings.find(s => s.id === 'businessCardOcr')?.fields || []}
674
+ />
638
675
  </div>
639
676
  );
640
677
  }
@@ -0,0 +1,646 @@
1
+ // Business Card OCR Component Styles - Matching Project Theme
2
+
3
+ // Modal Base Styles
4
+ .modalOverlay {
5
+ position: fixed;
6
+ top: 0;
7
+ left: 0;
8
+ right: 0;
9
+ bottom: 0;
10
+ background: rgba(0, 0, 0, 0.5);
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ z-index: 1000;
15
+ padding: 1rem;
16
+ }
17
+
18
+ .modalContent {
19
+ background: var(--bg-color, white);
20
+ border-radius: var(--br, 8px);
21
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
22
+ overflow: hidden;
23
+ max-width: 95vw;
24
+ width: 800px;
25
+ max-height: 90vh;
26
+ display: flex;
27
+ flex-direction: column;
28
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.1);
29
+ }
30
+
31
+ .modalHeader {
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: space-between;
35
+ padding: 1rem 1.5rem;
36
+ border-bottom: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.08);
37
+ background: var(--bg-color, white);
38
+ min-height: 60px;
39
+
40
+ h2 {
41
+ margin: 0;
42
+ font-size: 1.1rem;
43
+ font-weight: var(--h-weight, 600);
44
+ color: var(--paragraph-color, #333);
45
+ line-height: 1.3;
46
+ }
47
+ }
48
+
49
+ .closeButton {
50
+ background: none;
51
+ border: none;
52
+ cursor: pointer;
53
+ padding: 0.375rem;
54
+ border-radius: var(--br, 6px);
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ transition: all 0.2s ease;
59
+ color: rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.6);
60
+ min-width: 40px;
61
+ min-height: 40px;
62
+
63
+ &:hover {
64
+ background: var(--highlight-color, #f8f9fa);
65
+ color: var(--paragraph-color, #333);
66
+ transform: scale(1.05);
67
+ }
68
+
69
+ &:active {
70
+ transform: scale(0.95);
71
+ }
72
+ }
73
+
74
+ .modalBody {
75
+ flex: 1;
76
+ overflow-y: auto;
77
+ padding: 1rem;
78
+ }
79
+
80
+ // Camera Interface
81
+ .cameraContainer {
82
+ display: flex;
83
+ flex-direction: column;
84
+ align-items: center;
85
+ gap: 1rem;
86
+ padding: 1rem;
87
+ }
88
+
89
+ .videoWrapper {
90
+ position: relative;
91
+ width: 100%;
92
+ max-width: 500px;
93
+ border-radius: var(--br, 12px);
94
+ overflow: hidden;
95
+ background: #000;
96
+ border: 2px solid var(--primary-color, #007bff);
97
+ }
98
+
99
+ .cameraVideo {
100
+ width: 100%;
101
+ height: auto;
102
+ min-height: 300px;
103
+ object-fit: cover;
104
+ display: block;
105
+ }
106
+
107
+ .cameraOverlay {
108
+ position: absolute;
109
+ top: 0;
110
+ left: 0;
111
+ right: 0;
112
+ bottom: 0;
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ pointer-events: none;
117
+ }
118
+
119
+ .captureGuide {
120
+ background: var(--highlight-color, #f8f9fa);
121
+ color: var(--paragraph-color, #333);
122
+ padding: 0.75rem 1.5rem;
123
+ border-radius: var(--br, 8px);
124
+ text-align: center;
125
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.1);
126
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
127
+ margin-bottom: 1rem;
128
+
129
+ p {
130
+ margin: 0;
131
+ font-size: 1rem;
132
+ font-weight: 600;
133
+ line-height: 1.3;
134
+ }
135
+ }
136
+
137
+ .cameraControls {
138
+ display: flex;
139
+ gap: 1rem;
140
+ flex-wrap: wrap;
141
+ justify-content: center;
142
+ align-items: center;
143
+ }
144
+
145
+ .captureButton {
146
+ background: var(--primary-color, #007bff);
147
+ color: white;
148
+ border: none;
149
+ border-radius: var(--br, 50px);
150
+ padding: 0.5rem 1.25rem;
151
+ font-size: 0.9rem;
152
+ font-weight: var(--h-weight, 600);
153
+ cursor: pointer;
154
+ display: flex;
155
+ align-items: center;
156
+ gap: 0.375rem;
157
+ transition: all 0.2s cubic-bezier(0.85, 0, 0.15, 1);
158
+
159
+ &:hover {
160
+ background: var(--primary-color-dark, #0056b3);
161
+ transform: translateY(-1px);
162
+ box-shadow: 0 2px 8px rgba(var(--primary-color-rgb, 0, 123, 255), 0.25);
163
+ }
164
+
165
+ &:active {
166
+ transform: translateY(0);
167
+ }
168
+ }
169
+
170
+ // Preview Section
171
+ .previewContainer {
172
+ display: flex;
173
+ flex-direction: column;
174
+ align-items: center;
175
+ gap: 1.5rem;
176
+ padding: 1rem;
177
+ }
178
+
179
+ .imagePreview {
180
+ width: 100%;
181
+ max-width: 500px;
182
+ border-radius: var(--br, 12px);
183
+ overflow: hidden;
184
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
185
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.1);
186
+ }
187
+
188
+ .previewImage {
189
+ width: 100%;
190
+ height: auto;
191
+ max-height: 400px;
192
+ object-fit: contain;
193
+ display: block;
194
+ }
195
+
196
+ .previewControls {
197
+ display: flex;
198
+ gap: 1rem;
199
+ flex-wrap: wrap;
200
+ justify-content: center;
201
+ }
202
+
203
+ // Processing Section
204
+ .processingContainer {
205
+ display: flex;
206
+ flex-direction: column;
207
+ align-items: center;
208
+ justify-content: center;
209
+ padding: 3rem;
210
+ text-align: center;
211
+ gap: 1rem;
212
+
213
+ p {
214
+ font-size: 1.1rem;
215
+ font-weight: var(--para-weight, 500);
216
+ margin: 0;
217
+ color: var(--paragraph-color, #333);
218
+ }
219
+
220
+ small {
221
+ color: rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.6);
222
+ margin: 0;
223
+ }
224
+ }
225
+
226
+ .loadingSpinner {
227
+ width: 48px;
228
+ height: 48px;
229
+ border: 4px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.1);
230
+ border-top: 4px solid var(--primary-color, #007bff);
231
+ border-radius: 50%;
232
+ animation: spin 1s linear infinite;
233
+ }
234
+
235
+ .progressBar {
236
+ width: 200px;
237
+ height: 8px;
238
+ background: rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.1);
239
+ border-radius: var(--br, 4px);
240
+ overflow: hidden;
241
+ }
242
+
243
+ .progressFill {
244
+ height: 100%;
245
+ background: var(--primary-color, #007bff);
246
+ transition: width 0.3s ease;
247
+ }
248
+
249
+ @keyframes spin {
250
+ 0% {
251
+ transform: rotate(0deg);
252
+ }
253
+ 100% {
254
+ transform: rotate(360deg);
255
+ }
256
+ }
257
+
258
+ // Contact Form
259
+ .contactForm {
260
+ padding: 1rem;
261
+ max-height: 65vh;
262
+ overflow-y: auto;
263
+ }
264
+
265
+ .formGrid {
266
+ display: grid;
267
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
268
+ gap: 0.75rem;
269
+ margin-bottom: 1.5rem;
270
+ }
271
+
272
+ .fieldGroup {
273
+ display: flex;
274
+ flex-direction: column;
275
+ gap: 0.375rem;
276
+
277
+ label {
278
+ font-weight: var(--h-weight, 500);
279
+ color: var(--paragraph-color, #333);
280
+ display: flex;
281
+ align-items: center;
282
+ gap: 0.375rem;
283
+ font-size: 0.85rem;
284
+ }
285
+ }
286
+
287
+ .inputField {
288
+ width: 100%;
289
+ padding: 0.5rem 0.75rem;
290
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.2);
291
+ border-radius: var(--br, 6px);
292
+ font-size: 0.9rem;
293
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
294
+ background: var(--bg-color, white);
295
+ color: var(--paragraph-color, #333);
296
+
297
+ &:focus {
298
+ outline: none;
299
+ border-color: var(--primary-color, #007bff);
300
+ box-shadow: 0 0 0 2px rgba(var(--primary-color-rgb, 0, 123, 255), 0.1);
301
+ }
302
+
303
+ &::placeholder {
304
+ color: rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.5);
305
+ }
306
+ }
307
+
308
+ .unusedDataSection {
309
+ background: var(--highlight-color, #f8f9fa);
310
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.1);
311
+ border-radius: var(--br, 8px);
312
+ padding: 1rem;
313
+ margin: 1rem 0;
314
+
315
+ h4 {
316
+ margin: 0 0 0.5rem 0;
317
+ font-size: 1rem;
318
+ font-weight: var(--h-weight, 600);
319
+ color: var(--paragraph-color, #333);
320
+ }
321
+
322
+ p {
323
+ margin: 0 0 0.75rem 0;
324
+ font-size: 0.85rem;
325
+ color: rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.7);
326
+ }
327
+ }
328
+
329
+ .unusedDataGrid {
330
+ display: flex;
331
+ flex-wrap: wrap;
332
+ gap: 0.5rem;
333
+ margin-bottom: 1rem;
334
+ }
335
+
336
+ .unusedDataItem {
337
+ background: var(--bg-color, white);
338
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.2);
339
+ border-radius: var(--br, 6px);
340
+ padding: 0.375rem 0.75rem;
341
+ font-size: 0.85rem;
342
+ cursor: pointer;
343
+ transition: all 0.2s ease;
344
+ color: var(--paragraph-color, #333);
345
+
346
+ &:hover {
347
+ border-color: var(--primary-color, #007bff);
348
+ background: rgba(var(--primary-color-rgb, 0, 123, 255), 0.05);
349
+ transform: translateY(-1px);
350
+ }
351
+ }
352
+
353
+ .assignmentDropdown {
354
+ background: var(--bg-color, white);
355
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.2);
356
+ border-radius: var(--br, 8px);
357
+ padding: 1rem;
358
+ margin-top: 0.75rem;
359
+
360
+ p {
361
+ margin: 0 0 0.75rem 0;
362
+ font-weight: var(--h-weight, 500);
363
+ color: var(--paragraph-color, #333);
364
+ }
365
+ }
366
+
367
+ .assignmentOptions {
368
+ display: flex;
369
+ flex-wrap: wrap;
370
+ gap: 0.5rem;
371
+ margin-bottom: 0.75rem;
372
+ }
373
+
374
+ .assignmentOption {
375
+ background: var(--highlight-color, #f8f9fa);
376
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.2);
377
+ border-radius: var(--br, 6px);
378
+ padding: 0.375rem 0.75rem;
379
+ font-size: 0.85rem;
380
+ cursor: pointer;
381
+ transition: all 0.2s ease;
382
+ color: var(--paragraph-color, #333);
383
+
384
+ &:hover {
385
+ background: var(--primary-color, #007bff);
386
+ color: white;
387
+ border-color: var(--primary-color, #007bff);
388
+ }
389
+ }
390
+
391
+ .cancelAssignment {
392
+ background: none;
393
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.3);
394
+ border-radius: var(--br, 6px);
395
+ padding: 0.375rem 0.75rem;
396
+ font-size: 0.85rem;
397
+ cursor: pointer;
398
+ color: rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.6);
399
+ transition: all 0.2s ease;
400
+
401
+ &:hover {
402
+ background: rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.05);
403
+ color: var(--paragraph-color, #333);
404
+ }
405
+ }
406
+
407
+ .rawTextSection {
408
+ background: rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.03);
409
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.08);
410
+ border-radius: var(--br, 20px);
411
+ padding: 0.5rem 0.75rem;
412
+ margin: 0.75rem 0;
413
+ transition: all 0.2s ease;
414
+ display: inline-block;
415
+ min-width: 200px;
416
+
417
+ &[open] {
418
+ display: block;
419
+ border-radius: var(--br, 8px);
420
+ border-color: var(--primary-color, #007bff);
421
+ background: rgba(var(--primary-color-rgb, 0, 123, 255), 0.02);
422
+ padding: 1rem;
423
+ margin: 1rem 0;
424
+ }
425
+
426
+ &[open] .expandIcon {
427
+ transform: rotate(90deg);
428
+ }
429
+
430
+ &[open] .summaryHint {
431
+ opacity: 0;
432
+ }
433
+ }
434
+
435
+ .rawTextSummary {
436
+ cursor: pointer;
437
+ font-weight: var(--h-weight, 500);
438
+ color: var(--paragraph-color, #333);
439
+ margin: 0;
440
+ display: flex;
441
+ align-items: center;
442
+ justify-content: space-between;
443
+ list-style: none;
444
+ outline: none;
445
+ user-select: none;
446
+ transition: all 0.2s ease;
447
+
448
+ &::-webkit-details-marker {
449
+ display: none;
450
+ }
451
+
452
+ &:hover {
453
+ color: var(--primary-color, #007bff);
454
+
455
+ .expandIcon {
456
+ color: var(--primary-color, #007bff);
457
+ transform: scale(1.1);
458
+ }
459
+
460
+ .summaryHint {
461
+ opacity: 0.8;
462
+ }
463
+ }
464
+
465
+ // When expanded, give it more breathing room
466
+ .rawTextSection[open] & {
467
+ margin-bottom: 0.5rem;
468
+ }
469
+ }
470
+
471
+ .summaryText {
472
+ display: flex;
473
+ align-items: center;
474
+ gap: 0.375rem;
475
+ font-size: 0.85rem;
476
+ }
477
+
478
+ .summaryHint {
479
+ font-size: 0.75rem;
480
+ color: rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.5);
481
+ font-weight: 400;
482
+ transition: opacity 0.2s ease;
483
+ white-space: nowrap;
484
+ }
485
+
486
+ .expandIcon {
487
+ color: var(--primary-color, #007bff);
488
+ font-size: 0.75rem;
489
+ transition: all 0.2s ease;
490
+ transform-origin: center;
491
+ margin-left: 0.25rem;
492
+ }
493
+
494
+ .rawTextContent {
495
+ background: var(--bg-color, white);
496
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.1);
497
+ border-radius: var(--br, 4px);
498
+ padding: 0.75rem;
499
+ font-family: monospace;
500
+ font-size: 0.9rem;
501
+ white-space: pre-wrap;
502
+ max-height: 200px;
503
+ overflow: auto;
504
+ margin-top: 0.5rem;
505
+ color: var(--paragraph-color, #333);
506
+ }
507
+
508
+ .formActions {
509
+ display: flex;
510
+ gap: 1rem;
511
+ justify-content: center;
512
+ flex-wrap: wrap;
513
+ padding-top: 1rem;
514
+ border-top: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.1);
515
+ }
516
+
517
+ // Button Styles
518
+ .primaryButton {
519
+ background: var(--primary-color, #007bff);
520
+ color: white;
521
+ border: none;
522
+ border-radius: var(--br, 8px);
523
+ padding: 0.75rem 1.5rem;
524
+ font-size: 1rem;
525
+ font-weight: var(--h-weight, 500);
526
+ cursor: pointer;
527
+ display: flex;
528
+ align-items: center;
529
+ gap: 0.5rem;
530
+ transition: all 0.2s cubic-bezier(0.85, 0, 0.15, 1);
531
+
532
+ &:hover:not(:disabled) {
533
+ background: var(--primary-color-dark, #0056b3);
534
+ transform: translateY(-1px);
535
+ box-shadow: 0 4px 12px rgba(var(--primary-color-rgb, 0, 123, 255), 0.3);
536
+ }
537
+
538
+ &:disabled {
539
+ opacity: 0.6;
540
+ cursor: not-allowed;
541
+ transform: none;
542
+ }
543
+ }
544
+
545
+ .secondaryButton {
546
+ background: var(--highlight-color, #f8f9fa);
547
+ color: var(--paragraph-color, #333);
548
+ border: 1px solid rgba(var(--paragraph-color-rgb, 0, 0, 0), 0.2);
549
+ border-radius: var(--br, 8px);
550
+ padding: 0.5rem 1rem;
551
+ font-size: 0.9rem;
552
+ font-weight: var(--h-weight, 500);
553
+ cursor: pointer;
554
+ display: flex;
555
+ align-items: center;
556
+ gap: 0.375rem;
557
+ transition: all 0.2s cubic-bezier(0.85, 0, 0.15, 1);
558
+
559
+ &:hover {
560
+ background: var(--bg-color, white);
561
+ border-color: var(--primary-color, #007bff);
562
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
563
+ }
564
+ }
565
+
566
+ // Mobile Responsive Design
567
+ @media (max-width: 768px) {
568
+ .modalContent {
569
+ max-width: 95vw;
570
+ max-height: 95vh;
571
+ margin: 2.5vh auto;
572
+ }
573
+
574
+ .cameraContainer {
575
+ padding: 0.5rem;
576
+ }
577
+
578
+ .cameraControls {
579
+ flex-direction: column;
580
+ width: 100%;
581
+
582
+ button {
583
+ width: 100%;
584
+ justify-content: center;
585
+ }
586
+ }
587
+
588
+ .captureButton {
589
+ padding: 0.6rem 1.5rem;
590
+ font-size: 1rem;
591
+ }
592
+
593
+ .previewControls,
594
+ .formActions {
595
+ flex-direction: column;
596
+ width: 100%;
597
+
598
+ button {
599
+ width: 100%;
600
+ justify-content: center;
601
+ }
602
+ }
603
+
604
+ .formGrid {
605
+ grid-template-columns: 1fr;
606
+ gap: 1rem;
607
+ }
608
+
609
+ .contactForm {
610
+ padding: 0.5rem;
611
+ max-height: 70vh;
612
+ }
613
+ }
614
+
615
+ @media (max-width: 480px) {
616
+ .cameraVideo {
617
+ min-height: 250px;
618
+ }
619
+
620
+ .captureGuide p {
621
+ font-size: 0.8rem;
622
+ }
623
+
624
+ .fieldGroup label {
625
+ font-size: 0.85rem;
626
+ }
627
+
628
+ .primaryButton,
629
+ .secondaryButton {
630
+ padding: 0.6rem 1.25rem;
631
+ font-size: 0.9rem;
632
+ }
633
+ }
634
+
635
+ // Dark mode support (if project supports it)
636
+ @media (prefers-color-scheme: dark) {
637
+ .captureGuide {
638
+ background: rgba(255, 255, 255, 0.1);
639
+ color: white;
640
+ }
641
+
642
+ .loadingSpinner {
643
+ border-color: rgba(var(--paragraph-color-rgb, 255, 255, 255), 0.2);
644
+ border-top-color: var(--primary-color, #007bff);
645
+ }
646
+ }