protae5544 commited on
Commit
12fcb74
·
verified ·
1 Parent(s): f586813

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +117 -1603
index.html CHANGED
@@ -3,12 +3,13 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
6
- <meta name="description" content="ระบบจัดการเอกสาร PDF พร้อมการกรอกข้อมูล JSON และ QR Code">
7
- <title>ระบบจัดการเอกสาร PDF Professional</title>
8
 
9
  <!-- Fonts -->
10
- <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700&display=swap">
11
- <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
 
12
 
13
  <!-- Libraries -->
14
  <script src="https://cdn.tailwindcss.com"></script>
@@ -17,1654 +18,167 @@
17
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
19
  <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcode-generator/1.4.4/qrcode.min.js"></script>
 
 
20
 
21
- <style>
22
- /* Font Configuration */
23
- @font-face {
24
- font-family: 'THSarabunPsk';
25
- src: url('https://oldqifkvaagtseibueaf.supabase.co/storage/v1/object/public/zzoo/ozz/ss-thsbn.woff2') format('woff2');
26
- font-weight: normal;
27
- unicode-range: U+0E00-0E7F, U+0020-007F;
28
- font-display: swap;
29
- }
30
-
31
- @font-face {
32
- font-family: 'THSarabunPsk';
33
- src: url('https://oldqifkvaagtseibueaf.supabase.co/storage/v1/object/public/zzoo/ozz/ss-thsbn-bold.woff2') format('woff2');
34
- font-weight: bold;
35
- unicode-range: U+0E00-0E7F, U+0020-007F;
36
- font-display: swap;
37
- }
38
-
39
- /* CSS Variables */
40
- :root {
41
- --header-height: 60px;
42
- --footer-height: 50px;
43
- --sidebar-width: 320px;
44
- --bg-primary: #f8fafc;
45
- --bg-secondary: #ffffff;
46
- --bg-tertiary: #f1f5f9;
47
- --text-primary: #0f172a;
48
- --text-secondary: #475569;
49
- --text-tertiary: #64748b;
50
- --border-primary: #e2e8f0;
51
- --border-secondary: #cbd5e1;
52
- --accent-primary: #3b82f6;
53
- --accent-secondary: #10b981;
54
- --accent-tertiary: #8b5cf6;
55
- --primary-color: #7c2a4a;
56
- --canvas-bg: #1e293b;
57
- --hud-bg: rgba(30, 41, 59, 0.95);
58
- --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
59
- }
60
-
61
- [data-theme="dark"] {
62
- --bg-primary: #0f172a;
63
- --bg-secondary: #1e293b;
64
- --bg-tertiary: #334155;
65
- --text-primary: #f8fafc;
66
- --text-secondary: #cbd5e1;
67
- --text-tertiary: #94a3b8;
68
- --border-primary: #334155;
69
- --border-secondary: #475569;
70
- --canvas-bg: #0f172a;
71
- }
72
-
73
- * {
74
- margin: 0;
75
- padding: 0;
76
- box-sizing: border-box;
77
- }
78
-
79
- html, body {
80
- height: 100%;
81
- overflow: hidden;
82
- font-family: 'Sarabun', 'THSarabunPsk', sans-serif;
83
- background: var(--canvas-bg);
84
- color: var(--text-primary);
85
- }
86
-
87
- /* App Layout */
88
- .app-container {
89
- display: flex;
90
- flex-direction: column;
91
- height: 100vh;
92
- width: 100vw;
93
- }
94
-
95
- /* Header */
96
- header {
97
- height: var(--header-height);
98
- background: var(--bg-secondary);
99
- border-bottom: 1px solid var(--border-primary);
100
- display: flex;
101
- align-items: center;
102
- justify-content: space-between;
103
- padding: 0 1rem;
104
- z-index: 100;
105
- flex-shrink: 0;
106
- }
107
-
108
- .header-title {
109
- font-size: 1.25rem;
110
- font-weight: 600;
111
- color: var(--text-primary);
112
- display: flex;
113
- align-items: center;
114
- gap: 0.5rem;
115
- }
116
-
117
- .header-actions {
118
- display: flex;
119
- gap: 0.5rem;
120
- }
121
-
122
- /* Main Content Area */
123
- .main-container {
124
- flex: 1;
125
- display: flex;
126
- overflow: hidden;
127
- }
128
-
129
- /* Sidebar */
130
- .sidebar {
131
- width: var(--sidebar-width);
132
- background: var(--bg-secondary);
133
- border-right: 1px solid var(--border-primary);
134
- display: flex;
135
- flex-direction: column;
136
- overflow: hidden;
137
- flex-shrink: 0;
138
- transition: transform 0.3s ease;
139
- }
140
-
141
- .sidebar-header {
142
- padding: 1rem;
143
- border-bottom: 1px solid var(--border-primary);
144
- display: flex;
145
- align-items: center;
146
- justify-content: space-between;
147
- }
148
-
149
- .sidebar-content {
150
- flex: 1;
151
- overflow-y: auto;
152
- padding: 1rem;
153
- }
154
-
155
- .sidebar-section {
156
- margin-bottom: 1.5rem;
157
- }
158
-
159
- .sidebar-section h3 {
160
- font-size: 0.875rem;
161
- font-weight: 600;
162
- color: var(--text-secondary);
163
- margin-bottom: 0.75rem;
164
- padding-left: 0.5rem;
165
- border-left: 3px solid var(--accent-primary);
166
- }
167
-
168
- /* Viewer Area */
169
- .viewer-container {
170
- flex: 1;
171
- display: flex;
172
- flex-direction: column;
173
- overflow: hidden;
174
- background: var(--canvas-bg);
175
- }
176
-
177
- .viewer-tabs {
178
- display: flex;
179
- background: var(--bg-tertiary);
180
- border-bottom: 1px solid var(--border-primary);
181
- padding: 0.5rem 1rem 0;
182
- }
183
-
184
- .tab-btn {
185
- padding: 0.75rem 1.5rem;
186
- background: transparent;
187
- border: none;
188
- border-radius: 0.5rem 0.5rem 0 0;
189
- cursor: pointer;
190
- font-size: 0.875rem;
191
- font-weight: 500;
192
- color: var(--text-tertiary);
193
- transition: var(--transition);
194
- }
195
-
196
- .tab-btn.active {
197
- background: var(--bg-secondary);
198
- color: var(--accent-primary);
199
- }
200
-
201
- .viewer-content {
202
- flex: 1;
203
- overflow: auto;
204
- padding: 1rem;
205
- }
206
-
207
- /* Template View */
208
- .template-viewer {
209
- display: flex;
210
- flex-direction: column;
211
- align-items: center;
212
- gap: 2rem;
213
- padding: 2rem;
214
- }
215
-
216
- .page {
217
- position: relative;
218
- width: 892px;
219
- height: 1261px;
220
- background: var(--bg-secondary);
221
- border-radius: 0.5rem;
222
- overflow: hidden;
223
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
224
- flex-shrink: 0;
225
- }
226
-
227
- .page .bg {
228
- position: absolute;
229
- inset: 0;
230
- width: 100%;
231
- height: 100%;
232
- object-fit: cover;
233
- z-index: 0;
234
- }
235
-
236
- .field {
237
- position: absolute;
238
- white-space: nowrap;
239
- z-index: 10;
240
- font-family: 'THSarabunPsk', sans-serif;
241
- }
242
-
243
- .profile {
244
- position: absolute;
245
- top: 46px;
246
- left: 725px;
247
- width: 110px;
248
- height: 138px;
249
- object-fit: cover;
250
- border: 2px solid var(--border-primary);
251
- border-radius: 0.375rem;
252
- z-index: 100;
253
- }
254
-
255
- .qr {
256
- position: absolute;
257
- object-fit: cover;
258
- z-index: 100;
259
- border-radius: 0.25rem;
260
- }
261
-
262
- /* PDF Viewer */
263
- .pdf-viewer {
264
- display: flex;
265
- flex-direction: column;
266
- align-items: center;
267
- gap: 1.5rem;
268
- padding: 2rem;
269
- }
270
-
271
- .pdf-page-wrapper {
272
- background: #fff;
273
- box-shadow: 0 10px 30px rgba(0,0,0,0.5);
274
- }
275
-
276
- /* Form Elements */
277
- .form-group {
278
- margin-bottom: 1rem;
279
- }
280
-
281
- .form-group label {
282
- display: block;
283
- font-size: 0.75rem;
284
- font-weight: 500;
285
- color: var(--text-secondary);
286
- margin-bottom: 0.375rem;
287
- }
288
-
289
- .form-control {
290
- width: 100%;
291
- padding: 0.5rem 0.75rem;
292
- font-size: 0.875rem;
293
- background: var(--bg-tertiary);
294
- border: 1px solid var(--border-primary);
295
- border-radius: 0.375rem;
296
- color: var(--text-primary);
297
- transition: var(--transition);
298
- }
299
-
300
- .form-control:focus {
301
- outline: none;
302
- border-color: var(--accent-primary);
303
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
304
- }
305
-
306
- /* File Input */
307
- .file-label {
308
- display: block;
309
- width: 100%;
310
- padding: 0.625rem 1rem;
311
- background: linear-gradient(135deg, var(--accent-primary), var(--accent-tertiary));
312
- color: white;
313
- font-size: 0.875rem;
314
- font-weight: 500;
315
- text-align: center;
316
- border-radius: 0.375rem;
317
- cursor: pointer;
318
- transition: var(--transition);
319
- }
320
-
321
- .file-label:hover {
322
- transform: translateY(-1px);
323
- box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
324
- }
325
-
326
- .file-name {
327
- font-size: 0.75rem;
328
- color: var(--text-tertiary);
329
- margin-top: 0.25rem;
330
- text-align: center;
331
- white-space: nowrap;
332
- overflow: hidden;
333
- text-overflow: ellipsis;
334
- }
335
-
336
- /* Buttons */
337
- .btn {
338
- display: inline-flex;
339
- align-items: center;
340
- justify-content: center;
341
- gap: 0.5rem;
342
- padding: 0.625rem 1rem;
343
- font-size: 0.875rem;
344
- font-weight: 500;
345
- border: none;
346
- border-radius: 0.375rem;
347
- cursor: pointer;
348
- transition: var(--transition);
349
- }
350
-
351
- .btn:disabled {
352
- opacity: 0.5;
353
- cursor: not-allowed;
354
- }
355
-
356
- .btn-primary {
357
- background: linear-gradient(135deg, var(--accent-secondary), #34d399);
358
- color: white;
359
- }
360
-
361
- .btn-secondary {
362
- background: var(--bg-tertiary);
363
- color: var(--text-primary);
364
- border: 1px solid var(--border-primary);
365
- }
366
-
367
- .btn-indigo {
368
- background: linear-gradient(135deg, #6366f1, #818cf8);
369
- color: white;
370
- }
371
-
372
- .btn-danger {
373
- background: linear-gradient(135deg, #ef4444, #f87171);
374
- color: white;
375
- }
376
-
377
- .btn-icon {
378
- width: 40px;
379
- height: 40px;
380
- padding: 0;
381
- border-radius: 0.5rem;
382
- }
383
-
384
- .btn:hover:not(:disabled) {
385
- transform: translateY(-1px);
386
- }
387
-
388
- /* HUD Controls */
389
- .hud-controls {
390
- position: fixed;
391
- bottom: calc(var(--footer-height) + 1rem);
392
- left: 50%;
393
- transform: translateX(-50%);
394
- background: var(--hud-bg);
395
- backdrop-filter: blur(10px);
396
- padding: 0.75rem 1.5rem;
397
- border-radius: 2rem;
398
- display: flex;
399
- align-items: center;
400
- gap: 1rem;
401
- color: white;
402
- z-index: 50;
403
- }
404
-
405
- .hud-controls .material-icons {
406
- cursor: pointer;
407
- transition: var(--transition);
408
- }
409
-
410
- .hud-controls .material-icons:hover {
411
- color: var(--accent-primary);
412
- }
413
-
414
- /* Footer */
415
- footer {
416
- height: var(--footer-height);
417
- background: var(--primary-color);
418
- color: white;
419
- display: flex;
420
- align-items: center;
421
- justify-content: space-between;
422
- padding: 0 1rem;
423
- font-size: 0.875rem;
424
- flex-shrink: 0;
425
- }
426
-
427
- /* Toast Notification */
428
- .toast {
429
- position: fixed;
430
- top: calc(var(--header-height) + 1rem);
431
- left: 50%;
432
- transform: translateX(-50%) translateY(-100px);
433
- background: var(--hud-bg);
434
- backdrop-filter: blur(10px);
435
- color: white;
436
- padding: 0.75rem 1.5rem;
437
- border-radius: 0.5rem;
438
- z-index: 1000;
439
- transition: transform 0.3s ease;
440
- max-width: 90%;
441
- text-align: center;
442
- }
443
-
444
- .toast.show {
445
- transform: translateX(-50%) translateY(0);
446
- }
447
-
448
- .toast.success {
449
- border-left: 4px solid var(--accent-secondary);
450
- }
451
-
452
- .toast.error {
453
- border-left: 4px solid #ef4444;
454
- }
455
-
456
- /* Modal */
457
- .modal-overlay {
458
- position: fixed;
459
- inset: 0;
460
- background: rgba(0, 0, 0, 0.7);
461
- backdrop-filter: blur(4px);
462
- display: none;
463
- align-items: center;
464
- justify-content: center;
465
- z-index: 200;
466
- }
467
-
468
- .modal-overlay.show {
469
- display: flex;
470
- }
471
-
472
- .modal-content {
473
- background: var(--bg-secondary);
474
- border-radius: 1rem;
475
- width: 90%;
476
- max-width: 500px;
477
- max-height: 80vh;
478
- overflow: hidden;
479
- display: flex;
480
- flex-direction: column;
481
- }
482
-
483
- .modal-header {
484
- padding: 1rem 1.5rem;
485
- border-bottom: 1px solid var(--border-primary);
486
- display: flex;
487
- align-items: center;
488
- justify-content: space-between;
489
- }
490
-
491
- .modal-header h3 {
492
- font-size: 1.125rem;
493
- font-weight: 600;
494
- }
495
-
496
- .modal-body {
497
- padding: 1.5rem;
498
- overflow-y: auto;
499
- }
500
-
501
- .modal-footer {
502
- padding: 1rem 1.5rem;
503
- border-top: 1px solid var(--border-primary);
504
- display: flex;
505
- gap: 0.75rem;
506
- justify-content: flex-end;
507
- }
508
-
509
- /* Loading Overlay */
510
- .loading-overlay {
511
- position: fixed;
512
- inset: 0;
513
- background: rgba(0, 0, 0, 0.8);
514
- display: none;
515
- align-items: center;
516
- justify-content: center;
517
- flex-direction: column;
518
- gap: 1rem;
519
- color: white;
520
- z-index: 300;
521
- }
522
-
523
- .loading-overlay.show {
524
- display: flex;
525
- }
526
-
527
- .loading-spinner {
528
- width: 50px;
529
- height: 50px;
530
- border: 4px solid rgba(255, 255, 255, 0.3);
531
- border-top-color: white;
532
- border-radius: 50%;
533
- animation: spin 1s linear infinite;
534
- }
535
-
536
- @keyframes spin {
537
- to { transform: rotate(360deg); }
538
- }
539
-
540
- /* QR Preview */
541
- .qr-preview {
542
- text-align: center;
543
- padding: 1rem;
544
- background: var(--bg-tertiary);
545
- border-radius: 0.5rem;
546
- margin: 1rem 0;
547
- }
548
-
549
- .qr-preview canvas {
550
- border: 1px solid var(--border-primary);
551
- background: white;
552
- }
553
-
554
- .qr-info {
555
- background: rgba(59, 130, 246, 0.1);
556
- border: 1px solid rgba(59, 130, 246, 0.3);
557
- border-radius: 0.5rem;
558
- padding: 0.75rem;
559
- font-size: 0.875rem;
560
- margin: 0.75rem 0;
561
- }
562
-
563
- /* History List */
564
- .history-item {
565
- padding: 0.75rem 1rem;
566
- border-bottom: 1px solid var(--border-primary);
567
- cursor: pointer;
568
- transition: var(--transition);
569
- display: flex;
570
- align-items: center;
571
- gap: 0.75rem;
572
- }
573
-
574
- .history-item:hover {
575
- background: var(--bg-tertiary);
576
- }
577
-
578
- .history-item .material-icons {
579
- color: var(--text-tertiary);
580
- }
581
-
582
- .history-item-info {
583
- flex: 1;
584
- min-width: 0;
585
- }
586
-
587
- .history-item-name {
588
- font-weight: 500;
589
- white-space: nowrap;
590
- overflow: hidden;
591
- text-overflow: ellipsis;
592
- }
593
-
594
- .history-item-date {
595
- font-size: 0.75rem;
596
- color: var(--text-tertiary);
597
- }
598
 
599
- /* Responsive */
600
- @media (max-width: 1024px) {
601
- .sidebar {
602
- position: fixed;
603
- left: 0;
604
- top: var(--header-height);
605
- bottom: var(--footer-height);
606
- z-index: 90;
607
- transform: translateX(-100%);
608
- }
609
-
610
- .sidebar.open {
611
- transform: translateX(0);
612
- }
613
-
614
- .sidebar-overlay {
615
- position: fixed;
616
- inset: 0;
617
- background: rgba(0, 0, 0, 0.5);
618
- z-index: 80;
619
- display: none;
620
- }
621
-
622
- .sidebar-overlay.show {
623
- display: block;
624
- }
625
-
626
- .page {
627
- transform: scale(0.5);
628
- transform-origin: top center;
629
- }
630
- }
631
 
632
- @media (max-width: 640px) {
633
- :root {
634
- --sidebar-width: 100%;
635
- }
636
 
637
- .page {
638
- transform: scale(0.35);
639
- }
640
 
641
- .hud-controls {
642
- padding: 0.5rem 1rem;
643
- gap: 0.75rem;
644
- font-size: 0.875rem;
645
- }
646
- }
647
-
648
- /* Scrollbar */
649
- ::-webkit-scrollbar {
650
- width: 8px;
651
- height: 8px;
652
- }
653
-
654
- ::-webkit-scrollbar-track {
655
- background: var(--bg-tertiary);
656
- }
657
-
658
- ::-webkit-scrollbar-thumb {
659
- background: var(--text-tertiary);
660
- border-radius: 4px;
661
- }
662
-
663
- ::-webkit-scrollbar-thumb:hover {
664
- background: var(--text-secondary);
665
- }
666
-
667
- /* Print Styles */
668
- @media print {
669
- header, footer, .sidebar, .hud-controls, .toast, .modal-overlay, .loading-overlay {
670
- display: none !important;
671
- }
672
-
673
- .page {
674
- margin: 0 !important;
675
- box-shadow: none !important;
676
- page-break-after: always;
677
- }
678
- }
679
- </style>
680
- </head>
681
- <body>
682
- <div class="app-container">
683
- <!-- Header -->
684
- <header>
685
- <div class="header-title">
686
- <button class="btn btn-icon btn-secondary" id="toggle-sidebar">
687
- <i class="material-icons">menu</i>
688
- </button>
689
- <span>ระบบจัดการเอกสาร PDF Professional</span>
690
- </div>
691
- <div class="header-actions">
692
- <button class="btn btn-icon btn-secondary" id="btn-theme" title="สลับธีม">
693
- <i class="material-icons">dark_mode</i>
694
- </button>
695
- <button class="btn btn-icon btn-secondary" id="btn-history" title="ประวัติ">
696
- <i class="material-icons">history</i>
697
- </button>
698
- </div>
699
- </header>
700
-
701
- <!-- Main Container -->
702
- <div class="main-container">
703
- <!-- Sidebar Overlay -->
704
- <div class="sidebar-overlay" id="sidebar-overlay"></div>
705
-
706
- <!-- Sidebar -->
707
- <aside class="sidebar" id="sidebar">
708
- <div class="sidebar-content">
709
- <!-- Background Section -->
710
- <div class="sidebar-section">
711
- <h3>พื้นหลังเอกสาร</h3>
712
- <div class="form-group">
713
- <label>หน้าที่ 1</label>
714
- <input type="file" id="bg1-file" accept="image/*,.svg" hidden>
715
- <label for="bg1-file" class="file-label">เลือกรูปภาพ</label>
716
- <div id="bg1-name" class="file-name">ยังไม่ได้เลือก</div>
717
- </div>
718
- <div class="form-group">
719
- <label>หน้าที่ 2</label>
720
- <input type="file" id="bg2-file" accept="image/*,.svg" hidden>
721
- <label for="bg2-file" class="file-label">เลือกรูปภาพ</label>
722
- <div id="bg2-name" class="file-name">ยังไม่ได้เลือก</div>
723
- </div>
724
- <button class="btn btn-primary" style="width:100%" onclick="applyBg()">
725
- <i class="material-icons" style="font-size:18px">check</i>
726
- ใช้พื้นหลัง
727
- </button>
728
- </div>
729
-
730
- <!-- JSON Import Section -->
731
- <div class="sidebar-section">
732
- <h3>นำเข้าข้อมูล JSON</h3>
733
- <div class="form-group">
734
- <input type="file" id="json-file" accept=".json" hidden>
735
- <label for="json-file" class="file-label">เลือกไฟล์ JSON</label>
736
- <div id="json-name" class="file-name">ยังไม่ได้เลือกไฟล์</div>
737
- </div>
738
- <div class="form-group">
739
- <label>เลือกรายการข้อมูล</label>
740
- <select id="data-select" class="form-control" onchange="showData(this.value)" disabled>
741
- <option value="">-- ไม่มีข้อมูล --</option>
742
- </select>
743
- </div>
744
- <button class="btn btn-indigo" style="width:100%" onclick="loadSample()">
745
- <i class="material-icons" style="font-size:18px">science</i>
746
- โหลดข้อมูลตัวอย่าง
747
- </button>
748
- </div>
749
-
750
- <!-- PDF Import Section -->
751
- <div class="sidebar-section">
752
- <h3>นำเข้า PDF</h3>
753
- <div class="form-group">
754
- <input type="file" id="pdf-upload" accept="application/pdf" hidden>
755
- <label for="pdf-upload" class="file-label">
756
- <i class="material-icons" style="font-size:18px;vertical-align:middle">upload_file</i>
757
- อัปโหลด PDF
758
- </label>
759
- <div id="pdf-name" class="file-name">ยังไม่ได้เลือกไฟล์</div>
760
- </div>
761
- </div>
762
-
763
- <!-- Export Section -->
764
- <div class="sidebar-section">
765
- <h3>ส่งออกเอกสาร</h3>
766
- <button class="btn btn-primary" style="width:100%;margin-bottom:0.5rem" onclick="downloadPDF()">
767
- <i class="material-icons" style="font-size:18px">picture_as_pdf</i>
768
- ดาวน์โหลด PDF ปัจจุบัน
769
- </button>
770
- <button class="btn btn-secondary" style="width:100%;margin-bottom:0.5rem" onclick="downloadAllPDFs()">
771
- <i class="material-icons" style="font-size:18px">download</i>
772
- ดาวน์โหลดทั้งหมด
773
- </button>
774
- <button class="btn btn-indigo" style="width:100%" onclick="openQRModal()">
775
- <i class="material-icons" style="font-size:18px">qr_code</i>
776
- เพิ่ม QR Code และดาวน์โหลด
777
- </button>
778
- </div>
779
- </div>
780
- </aside>
781
 
782
  <!-- Viewer Container -->
783
- <div class="viewer-container">
 
784
  <!-- Tabs -->
785
- <div class="viewer-tabs">
786
- <button class="tab-btn active" data-tab="template">
787
- <i class="material-icons" style="font-size:18px;vertical-align:middle">description</i>
788
- Template
789
  </button>
790
- <button class="tab-btn" data-tab="pdf">
791
- <i class="material-icons" style="font-size:18px;vertical-align:middle">picture_as_pdf</i>
792
- PDF Viewer
793
  </button>
794
  </div>
795
 
796
  <!-- Viewer Content -->
797
- <div class="viewer-content" id="viewer-content">
798
  <!-- Template View -->
799
- <div class="template-viewer" id="template-view">
 
800
  <!-- Page 1 -->
801
- <div id="page1" class="page">
802
- <img id="bg1" class="bg" src="" alt="">
803
- <img id="photo" class="profile" src="https://via.placeholder.com/110x138?text=Photo" alt="">
804
- <img id="qr1" class="qr" style="top:977px;left:762.5px;width:69px;height:69px;" src="" alt="">
805
- <p class="field" style="top:87px;left:238px;font-size:14px;font-weight:bold;" id="f1"><span style="color:#3b82f6">f1</span> - <span style="color:#8b5cf6">[7]</span></p>
806
- <p class="field" style="top:116px;left:238px;font-size:14px;font-weight:bold;" id="f2"><span style="color:#3b82f6">f2</span> - <span style="color:#8b5cf6">[1]</span></p>
807
- <p class="field" style="top:323px;left:204px;font-size:14px;font-weight:normal;" id="f3"><span style="color:#3b82f6">f3</span> - <span style="color:#8b5cf6">[2]</span></p>
808
- <p class="field" style="top:302px;left:204px;font-size:14px;font-weight:normal;" id="f4"><span style="color:#3b82f6">f4</span> - <span style="color:#8b5cf6">[8]</span></p>
809
- <p class="field" style="top:302px;left:592px;font-size:14px;font-weight:normal;" id="f5"><span style="color:#3b82f6">f5</span> - <span style="color:#8b5cf6">[9]</span></p>
810
- <p class="field" style="top:323px;left:592px;font-size:14px;font-weight:normal;" id="f6"><span style="color:#3b82f6">f6</span> - <span style="color:#8b5cf6">[1]</span></p>
811
- <p class="field" style="top:345px;left:204px;font-size:14px;font-weight:normal;" id="f7"><span style="color:#3b82f6">f7</span> - <span style="color:#8b5cf6">[5]</span></p>
812
- <p class="field" style="top:345px;left:592px;font-size:14px;font-weight:normal;" id="f8"><span style="color:#3b82f6">f8</span> - <span style="color:#8b5cf6">[6]</span></p>
813
- <p class="field" style="top:367px;left:204px;font-size:14px;font-weight:normal;" id="f9"><span style="color:#3b82f6">f9</span> - <span style="color:#8b5cf6">[3]</span></p>
814
- <p class="field" style="top:410px;left:204px;font-size:14px;font-weight:normal;" id="f10"><span style="color:#3b82f6">f10</span> - <span style="color:#8b5cf6">[10]</span></p>
 
 
815
  </div>
816
 
817
  <!-- Page 2 -->
818
- <div id="page2" class="page">
819
- <img id="bg2" class="bg" src="" alt="">
820
- <img id="qr2" class="qr" style="top:925px;left:120px;width:90px;height:90px;" src="" alt="">
821
- <p class="field" style="top:60px;left:640px;font-size:19px;font-weight:300;" id="f12"><span style="color:#3b82f6">f12</span> - <span style="color:#8b5cf6">[11]</span></p>
822
- <p class="field" style="top:227px;left:640px;font-size:19px;font-weight:300;" id="f13"><span style="color:#3b82f6">f13</span> - <span style="color:#8b5cf6">[12]</span></p>
823
- <p class="field" style="top:271px;left:180px;font-size:19px;font-weight:300;" id="f14"><span style="color:#3b82f6">f14</span> - <span style="color:#8b5cf6">[7]</span></p>
824
- <p class="field" style="top:310px;left:180px;font-size:19px;font-weight:300;" id="f15"><span style="color:#3b82f6">f15</span> - <span style="color:#8b5cf6">[1]</span></p>
825
- <p class="field" style="top:310px;left:520px;font-size:19px;font-weight:300;" id="f16"><span style="color:#3b82f6">f16</span> - <span style="color:#8b5cf6">[3]</span></p>
826
- <p class="field" style="top:354px;left:180px;font-size:19px;font-weight:300;" id="f17"><span style="color:#3b82f6">f17</span> - <span style="color:#8b5cf6">[10]</span></p>
827
- <p class="field" style="top:354px;left:640px;font-size:19px;font-weight:300;" id="f18"><span style="color:#3b82f6">f18</span> - <span style="color:#8b5cf6">[8]</span></p>
 
828
  </div>
829
  </div>
830
 
831
  <!-- PDF View -->
832
- <div class="pdf-viewer" id="pdf-view" style="display:none">
833
- <div id="pdf-content"></div>
834
  </div>
835
  </div>
836
  </div>
837
  </div>
838
 
839
  <!-- HUD Controls -->
840
- <div class="hud-controls">
841
- <span>หน้า <input type="number" id="current-pg" value="1" min="1" style="width:50px;padding:0.25rem;border-radius:0.25rem;border:none;text-align:center"> / <span id="total-pg">2</span></span>
842
- <i class="material-icons" id="zoom-out" title="ซูมออก">remove</i>
843
- <span id="zoom-text">100%</span>
844
- <i class="material-icons" id="zoom-in" title="ซูมเข้า">add</i>
 
 
 
845
  </div>
846
 
847
- <!-- Footer -->
848
- <footer>
849
- <span>© PDF Processing System</span>
850
- <span><span id="file-count">0</span> ไฟล์ในประวัติ</span>
851
- </footer>
852
  </div>
853
 
854
- <!-- Toast -->
855
- <div class="toast" id="toast"></div>
 
 
 
856
 
 
 
 
857
  <!-- History Modal -->
858
- <div class="modal-overlay" id="history-modal">
859
- <div class="modal-content">
860
- <div class="modal-header">
861
- <h3>ประวัติไฟล์</h3>
862
- <button class="btn btn-icon btn-secondary" onclick="closeModal('history-modal')">
863
- <i class="material-icons">close</i>
864
- </button>
865
  </div>
866
- <div class="modal-body" id="history-list">
867
- <p style="text-align:center;color:var(--text-tertiary)">ไม่มีประวัติไฟล์</p>
868
  </div>
869
  </div>
870
  </div>
871
-
872
  <!-- QR Code Modal -->
873
- <div class="modal-overlay" id="qr-modal">
874
- <div class="modal-content">
875
- <div class="modal-header">
876
- <h3>ตั้งค่า QR Code</h3>
877
- <button class="btn btn-icon btn-secondary" onclick="closeModal('qr-modal')">
878
- <i class="material-icons">close</i>
879
- </button>
880
  </div>
881
- <div class="modal-body">
882
- <div class="form-group">
883
- <label>URL สำหรับ QR Code:</label>
884
- <input type="text" id="qr-url" class="form-control" placeholder="https://...">
885
- <small style="color:var(--text-tertiary)">หากเว้นว่างจะใช้ URL ของหน้านี้</small>
886
  </div>
887
-
888
- <div class="qr-info">
889
- <strong>ขนาด QR Code: 15mm × 13.58mm</strong><br>
890
- <small>ตำแหน่ง: X=15.15mm, Y=292.45mm (จากมุมซ้ายล่าง)</small>
891
  </div>
892
-
893
- <div class="qr-preview">
894
- <h4 style="margin-bottom:0.5rem;font-size:0.875rem">ตัวอย่าง QR Code:</h4>
895
  <canvas id="qr-preview-canvas" width="150" height="136"></canvas>
896
- <div style="font-size:0.75rem;color:var(--text-tertiary);margin-top:0.5rem">ขนาดจริง: 15mm × 13.58mm</div>
897
  </div>
898
- </div>
899
- <div class="modal-footer">
900
- <button class="btn btn-secondary" onclick="closeModal('qr-modal')">ยกเลิก</button>
901
- <button class="btn btn-primary" onclick="applyQRAndDownload()">
902
- <i class="material-icons" style="font-size:18px">download</i>
903
- เพิ่ม QR และดาวน์โหลด
904
- </button>
905
  </div>
906
  </div>
907
  </div>
908
-
909
  <!-- Loading Overlay -->
910
- <div class="loading-overlay" id="loading-overlay">
911
- <div class="loading-spinner"></div>
912
- <div id="loading-text">กำลังประมวลผล...</div>
913
  </div>
914
 
915
- <script>
916
- 'use strict';
917
-
918
- // Initialize PDF.js
919
- const pdfjsLib = window['pdfjs-dist/build/pdf'];
920
- pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
921
-
922
- // Database Configuration
923
- const DB_NAME = 'PDFProcessingDB';
924
- const STORE = 'documents';
925
- let db;
926
-
927
- // State Variables
928
- let allData = [];
929
- let currentIndex = 0;
930
- let pdfDoc = null;
931
- let scale = 1;
932
- let originalPdfBytes = null;
933
- let currentFileName = '';
934
- let bg1Obj = '', bg1B64 = '', bg2Obj = '', bg2B64 = '';
935
- let currentView = 'template';
936
-
937
- // Field Mapping
938
- const fieldToKey = {
939
- 'photo': '4',
940
- 'f1': '7', 'f2': '1', 'f3': '2', 'f4': '8', 'f5': '9',
941
- 'f6': '1', 'f7': '5', 'f8': '6', 'f9': '3', 'f10': '10',
942
- 'f12': '11', 'f13': '12', 'f14': '7', 'f15': '1',
943
- 'f16': '3', 'f17': '10', 'f18': '8'
944
- };
945
-
946
- const fieldIds = ['f1','f2','f3','f4','f5','f6','f7','f8','f9','f10','f12','f13','f14','f15','f16','f17','f18'];
947
-
948
- const primaryFieldForKey = {};
949
- Object.keys(fieldToKey).forEach(id => {
950
- const key = fieldToKey[id];
951
- if (!primaryFieldForKey[key]) primaryFieldForKey[key] = id.replace('f', '');
952
- });
953
-
954
- // Utility Functions
955
- function toast(msg, type = 'info') {
956
- const t = document.getElementById('toast');
957
- t.textContent = msg;
958
- t.className = 'toast show ' + type;
959
- setTimeout(() => t.classList.remove('show'), 3000);
960
- }
961
-
962
- function loading(show, text) {
963
- const o = document.getElementById('loading-overlay');
964
- if (show) {
965
- document.getElementById('loading-text').textContent = text || 'กำลังประมวลผล...';
966
- o.classList.add('show');
967
- } else {
968
- o.classList.remove('show');
969
- }
970
- }
971
-
972
- function closeModal(id) {
973
- document.getElementById(id).classList.remove('show');
974
- }
975
-
976
- function sha256(buffer) {
977
- const bufferCopy = buffer.slice(0);
978
- const wordArray = CryptoJS.lib.WordArray.create(new Uint8Array(bufferCopy));
979
- return CryptoJS.SHA256(wordArray).toString();
980
- }
981
-
982
- function mmToPoints(mm) {
983
- return mm * 2.83465;
984
- }
985
-
986
- // Database Functions
987
- function initDB() {
988
- return new Promise((resolve, reject) => {
989
- const request = indexedDB.open(DB_NAME, 1);
990
- request.onupgradeneeded = (e) => {
991
- const database = e.target.result;
992
- if (!database.objectStoreNames.contains(STORE)) {
993
- const store = database.createObjectStore(STORE, { keyPath: 'hash' });
994
- store.createIndex('createdAt', 'createdAt');
995
- }
996
- };
997
- request.onsuccess = () => {
998
- db = request.result;
999
- resolve();
1000
- };
1001
- request.onerror = () => reject(request.error);
1002
- });
1003
- }
1004
-
1005
- function saveDoc(obj) {
1006
- return new Promise((resolve, reject) => {
1007
- const tx = db.transaction(STORE, 'readwrite');
1008
- const store = tx.objectStore(STORE);
1009
- const request = store.put(obj);
1010
- request.onsuccess = () => resolve();
1011
- request.onerror = () => reject(request.error);
1012
- });
1013
- }
1014
-
1015
- function getAllDocs() {
1016
- return new Promise((resolve, reject) => {
1017
- const tx = db.transaction(STORE, 'readonly');
1018
- const request = tx.objectStore(STORE).getAll();
1019
- request.onsuccess = () => resolve(request.result);
1020
- request.onerror = () => reject(request.error);
1021
- });
1022
- }
1023
-
1024
- // Theme Toggle
1025
- function toggleTheme() {
1026
- const html = document.documentElement;
1027
- const newTheme = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
1028
- html.setAttribute('data-theme', newTheme);
1029
- const icon = document.querySelector('#btn-theme .material-icons');
1030
- icon.textContent = newTheme === 'dark' ? 'light_mode' : 'dark_mode';
1031
- toast(`เปลี่ยนเป็นธีม${newTheme === 'dark' ? 'มืด' : 'สว่าง'}`, 'success');
1032
- }
1033
-
1034
- // Sidebar Toggle
1035
- function toggleSidebar() {
1036
- const sidebar = document.getElementById('sidebar');
1037
- const overlay = document.getElementById('sidebar-overlay');
1038
- sidebar.classList.toggle('open');
1039
- overlay.classList.toggle('show');
1040
- }
1041
-
1042
- // Tab Switching
1043
- function switchTab(tab) {
1044
- document.querySelectorAll('.tab-btn').forEach(btn => {
1045
- btn.classList.toggle('active', btn.dataset.tab === tab);
1046
- });
1047
-
1048
- const templateView = document.getElementById('template-view');
1049
- const pdfView = document.getElementById('pdf-view');
1050
-
1051
- if (tab === 'template') {
1052
- templateView.style.display = 'flex';
1053
- pdfView.style.display = 'none';
1054
- document.getElementById('total-pg').textContent = '2';
1055
- } else {
1056
- templateView.style.display = 'none';
1057
- pdfView.style.display = 'flex';
1058
- if (pdfDoc) {
1059
- document.getElementById('total-pg').textContent = pdfDoc.numPages;
1060
- }
1061
- }
1062
-
1063
- currentView = tab;
1064
- }
1065
-
1066
- // Background Handlers
1067
- function handleBgFile(page, file) {
1068
- if (!file) return;
1069
- const obj = URL.createObjectURL(file);
1070
- document.getElementById('bg' + page).src = obj;
1071
- document.getElementById('bg' + page + '-name').textContent = file.name;
1072
-
1073
- if (page === '1') {
1074
- if (bg1Obj) URL.revokeObjectURL(bg1Obj);
1075
- bg1Obj = obj;
1076
- } else {
1077
- if (bg2Obj) URL.revokeObjectURL(bg2Obj);
1078
- bg2Obj = obj;
1079
- }
1080
-
1081
- const reader = new FileReader();
1082
- reader.onload = e => {
1083
- if (page === '1') bg1B64 = e.target.result;
1084
- else bg2B64 = e.target.result;
1085
- };
1086
- reader.readAsDataURL(file);
1087
- }
1088
-
1089
- function applyBg() {
1090
- if (bg1Obj) document.getElementById('bg1').src = bg1Obj;
1091
- if (bg2Obj) document.getElementById('bg2').src = bg2Obj;
1092
- toast('ใช้พื้นหลังเรียบร้อย', 'success');
1093
- }
1094
-
1095
- // JSON Data Handlers
1096
- function populateDropdown() {
1097
- const select = document.getElementById('data-select');
1098
- select.innerHTML = '';
1099
-
1100
- if (allData.length === 1) {
1101
- const opt = document.createElement('option');
1102
- opt.value = 0;
1103
- opt.textContent = 'รายการเดียว';
1104
- select.appendChild(opt);
1105
- select.disabled = true;
1106
- } else {
1107
- allData.forEach((item, idx) => {
1108
- const opt = document.createElement('option');
1109
- opt.value = idx;
1110
- opt.textContent = item['1'] ? `รายการ ${idx + 1}: ${item['1']}` : `รายการ ${idx + 1}`;
1111
- select.appendChild(opt);
1112
- });
1113
- select.disabled = false;
1114
- }
1115
- select.value = currentIndex;
1116
- }
1117
-
1118
- function showData(idx) {
1119
- currentIndex = parseInt(idx);
1120
- if (currentIndex < 0 || currentIndex >= allData.length) return;
1121
-
1122
- const data = allData[currentIndex];
1123
-
1124
- fieldIds.forEach(id => {
1125
- const key = fieldToKey[id];
1126
- const val = data[key] || '';
1127
- const primaryNum = primaryFieldForKey[key];
1128
- let label = `<span style="color:#3b82f6">${id}</span> - <span style="color:#8b5cf6">[${key}]</span>`;
1129
-
1130
- if (primaryNum && primaryNum !== id.replace('f', '')) {
1131
- label = `<span style="color:#3b82f6">${id}</span> - <span style="color:#8b5cf6">[${key}: ซ้ำจาก ${primaryNum}]</span>`;
1132
- }
1133
-
1134
- const el = document.getElementById(id);
1135
- if (!el) return;
1136
- el.innerHTML = val ? val : label;
1137
- if (val) el.style.color = 'black';
1138
- });
1139
-
1140
- document.getElementById('photo').src = data[fieldToKey['photo']] || 'https://via.placeholder.com/110x138?text=No+Photo';
1141
-
1142
- const qrApi = 'https://api.qrserver.com/v1/create-qr-code/?size=300x300&ecc=H&data=';
1143
- document.getElementById('qr1').src = qrApi + encodeURIComponent(`${location.origin}/worker.html?id=${encodeURIComponent(data['12'] || '')}`);
1144
- document.getElementById('qr2').src = qrApi + encodeURIComponent(`${location.origin}/pdf.html?page=2&page1=1&id=${encodeURIComponent(data['12'] || '')}`);
1145
- }
1146
-
1147
- function loadSample() {
1148
- allData = [
1149
- { "1": "นายทดสอบ หนึ่ง", "2": "ตำแหน่งที่ 1", "3": "แผนก A", "4": "", "5": "01/01/2025", "6": "31/12/2025", "7": "บริษัท ทดสอบ จำกัด", "8": "กรุงเทพมหานคร", "9": "10110", "10": "ประเทศไทย", "11": "REF001", "12": "DOC001" },
1150
- { "1": "นางสาวทดสอบ สอง", "2": "ตำแหน่งที่ 2", "3": "แผนก B", "4": "", "5": "01/02/2025", "6": "28/02/2026", "7": "บริษัท ตัวอย่าง จำกัด", "8": "เชียงใหม่", "9": "50200", "10": "ประเทศไทย", "11": "REF002", "12": "DOC002" },
1151
- { "1": "นายทดสอบ สาม", "2": "ตำแหน่งที่ 3", "3": "แผนก C", "4": "", "5": "01/03/2025", "6": "31/03/2026", "7": "องค์กร ทดสอบ", "8": "ภูเก็ต", "9": "83000", "10": "ประเทศไทย", "11": "REF003", "12": "DOC003" }
1152
- ];
1153
- currentIndex = 0;
1154
- populateDropdown();
1155
- showData(currentIndex);
1156
- toast(`โหลดข้อมูลตัวอย่างสำเร็จ (${allData.length} รายการ)`, 'success');
1157
- }
1158
-
1159
- // PDF Viewer Functions
1160
- async function renderPDF() {
1161
- if (!pdfDoc) return;
1162
-
1163
- const container = document.getElementById('pdf-content');
1164
- container.innerHTML = '';
1165
-
1166
- const viewerWidth = document.getElementById('viewer-content').clientWidth;
1167
-
1168
- for (let i = 1; i <= pdfDoc.numPages; i++) {
1169
- const page = await pdfDoc.getPage(i);
1170
- const viewport = page.getViewport({ scale: 1 });
1171
- const pageScale = (viewerWidth * 0.9 / viewport.width) * scale;
1172
- const scaledViewport = page.getViewport({ scale: pageScale });
1173
-
1174
- const canvas = document.createElement('canvas');
1175
- canvas.width = scaledViewport.width;
1176
- canvas.height = scaledViewport.height;
1177
-
1178
- await page.render({
1179
- canvasContext: canvas.getContext('2d'),
1180
- viewport: scaledViewport
1181
- }).promise;
1182
-
1183
- const wrapper = document.createElement('div');
1184
- wrapper.className = 'pdf-page-wrapper';
1185
- wrapper.appendChild(canvas);
1186
- container.appendChild(wrapper);
1187
- }
1188
- }
1189
-
1190
- async function loadPDF(buffer, name) {
1191
- loading(true, 'กำลังโหลด PDF...');
1192
-
1193
- try {
1194
- const pdfBuffer = buffer.slice(0);
1195
- originalPdfBytes = buffer.slice(0);
1196
- currentFileName = name;
1197
-
1198
- pdfDoc = await pdfjsLib.getDocument({ data: pdfBuffer }).promise;
1199
- document.getElementById('total-pg').textContent = pdfDoc.numPages;
1200
- document.getElementById('pdf-name').textContent = name;
1201
-
1202
- switchTab('pdf');
1203
- await renderPDF();
1204
-
1205
- toast('โหลด PDF สำเร็จ', 'success');
1206
- } catch (error) {
1207
- console.error('Error loading PDF:', error);
1208
- toast('เกิดข้อผิดพลาดในการโหลด PDF', 'error');
1209
- } finally {
1210
- loading(false);
1211
- }
1212
- }
1213
-
1214
- // QR Code Functions
1215
- function generateQRCode15mm(text) {
1216
- try {
1217
- const typeNumber = 0;
1218
- const errorCorrectionLevel = 'L';
1219
- const qr = qrcode(typeNumber, errorCorrectionLevel);
1220
- qr.addData(text);
1221
- qr.make();
1222
-
1223
- const targetWidth = 150;
1224
- const targetHeight = 136;
1225
- const cellCount = qr.getModuleCount();
1226
- const cellSize = Math.min(
1227
- Math.floor(targetWidth / cellCount),
1228
- Math.floor(targetHeight / cellCount)
1229
- );
1230
-
1231
- const canvasWidth = cellCount * cellSize;
1232
- const canvasHeight = cellCount * cellSize;
1233
-
1234
- const canvas = document.createElement('canvas');
1235
- const ctx = canvas.getContext('2d');
1236
- canvas.width = canvasWidth;
1237
- canvas.height = canvasHeight;
1238
-
1239
- for (let row = 0; row < cellCount; row++) {
1240
- for (let col = 0; col < cellCount; col++) {
1241
- ctx.fillStyle = qr.isDark(row, col) ? '#000000' : '#ffffff';
1242
- ctx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
1243
- }
1244
- }
1245
-
1246
- const finalCanvas = document.createElement('canvas');
1247
- const finalCtx = finalCanvas.getContext('2d');
1248
- finalCanvas.width = targetWidth;
1249
- finalCanvas.height = targetHeight;
1250
- finalCtx.fillStyle = '#ffffff';
1251
- finalCtx.fillRect(0, 0, targetWidth, targetHeight);
1252
- finalCtx.drawImage(canvas, 0, 0, canvasWidth, canvasHeight, 0, 0, targetWidth, targetHeight);
1253
-
1254
- return finalCanvas.toDataURL('image/png');
1255
- } catch (error) {
1256
- console.error('Error generating QR code:', error);
1257
- return null;
1258
- }
1259
- }
1260
-
1261
- function updateQRPreview() {
1262
- const urlInput = document.getElementById('qr-url');
1263
- const previewCanvas = document.getElementById('qr-preview-canvas');
1264
- const ctx = previewCanvas.getContext('2d');
1265
-
1266
- ctx.fillStyle = '#ffffff';
1267
- ctx.fillRect(0, 0, previewCanvas.width, previewCanvas.height);
1268
-
1269
- const qrText = urlInput.value || window.location.href;
1270
-
1271
- try {
1272
- const typeNumber = 0;
1273
- const errorCorrectionLevel = 'L';
1274
- const qr = qrcode(typeNumber, errorCorrectionLevel);
1275
- qr.addData(qrText);
1276
- qr.make();
1277
-
1278
- const cellCount = qr.getModuleCount();
1279
- const cellSize = Math.min(
1280
- Math.floor(150 / cellCount),
1281
- Math.floor(136 / cellCount)
1282
- );
1283
-
1284
- const canvasWidth = cellCount * cellSize;
1285
- const canvasHeight = cellCount * cellSize;
1286
-
1287
- const tempCanvas = document.createElement('canvas');
1288
- const tempCtx = tempCanvas.getContext('2d');
1289
- tempCanvas.width = canvasWidth;
1290
- tempCanvas.height = canvasHeight;
1291
-
1292
- for (let row = 0; row < cellCount; row++) {
1293
- for (let col = 0; col < cellCount; col++) {
1294
- tempCtx.fillStyle = qr.isDark(row, col) ? '#000000' : '#ffffff';
1295
- tempCtx.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
1296
- }
1297
- }
1298
-
1299
- ctx.drawImage(tempCanvas, 0, 0, canvasWidth, canvasHeight, 0, 0, 150, 136);
1300
- } catch (error) {
1301
- console.error('Error previewing QR code:', error);
1302
- }
1303
- }
1304
-
1305
- function openQRModal() {
1306
- if (!originalPdfBytes && currentView === 'pdf') {
1307
- toast('กรุณาอัปโหลด PDF ก่อน', 'error');
1308
- return;
1309
- }
1310
-
1311
- document.getElementById('qr-url').value = window.location.href;
1312
- updateQRPreview();
1313
- document.getElementById('qr-modal').classList.add('show');
1314
- }
1315
-
1316
- async function addQRCodeToPDF(pdfBytes, qrDataURL) {
1317
- try {
1318
- const pdfDocument = await PDFLib.PDFDocument.load(pdfBytes);
1319
- const pages = pdfDocument.getPages();
1320
- const qrImage = await pdfDocument.embedPng(qrDataURL);
1321
-
1322
- const x_mm = 15.15;
1323
- const y_mm = 292.45;
1324
- const width_mm = 15;
1325
- const height_mm = 13.58;
1326
-
1327
- const x = mmToPoints(x_mm);
1328
- const y = mmToPoints(297 - y_mm - height_mm);
1329
- const width = mmToPoints(width_mm);
1330
- const height = mmToPoints(height_mm);
1331
-
1332
- if (pages.length > 0) {
1333
- pages[0].drawImage(qrImage, { x, y, width, height });
1334
- }
1335
-
1336
- return await pdfDocument.save();
1337
- } catch (error) {
1338
- console.error('Error adding QR code to PDF:', error);
1339
- throw error;
1340
- }
1341
- }
1342
-
1343
- async function applyQRAndDownload() {
1344
- try {
1345
- const qrUrl = document.getElementById('qr-url').value || window.location.href;
1346
-
1347
- if (currentView === 'pdf' && originalPdfBytes) {
1348
- loading(true, 'กำลังเพิ่ม QR Code...');
1349
-
1350
- const qrDataURL = generateQRCode15mm(qrUrl);
1351
- if (!qrDataURL) throw new Error('ไม่สามารถสร้าง QR Code ได้');
1352
-
1353
- const modifiedPdfBytes = await addQRCodeToPDF(originalPdfBytes, qrDataURL);
1354
- const newFileName = currentFileName.replace('.pdf', '_with_qr.pdf') || 'document_with_qr.pdf';
1355
-
1356
- const newHash = sha256(modifiedPdfBytes);
1357
- await saveDoc({
1358
- hash: newHash,
1359
- fileName: newFileName,
1360
- pdfData: modifiedPdfBytes,
1361
- createdAt: new Date().toISOString(),
1362
- hasQRCode: true,
1363
- qrUrl: qrUrl
1364
- });
1365
-
1366
- const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
1367
- const url = URL.createObjectURL(blob);
1368
- const a = document.createElement('a');
1369
- a.href = url;
1370
- a.download = newFileName;
1371
- document.body.appendChild(a);
1372
- a.click();
1373
- document.body.removeChild(a);
1374
- URL.revokeObjectURL(url);
1375
-
1376
- const docs = await getAllDocs();
1377
- document.getElementById('file-count').textContent = docs.length;
1378
-
1379
- closeModal('qr-modal');
1380
- toast('ดาวน์โหลดไฟล์พร้อม QR Code สำเร็จ', 'success');
1381
- } else {
1382
- await downloadPDFWithQR(qrUrl);
1383
- }
1384
- } catch (error) {
1385
- console.error('Error processing PDF with QR:', error);
1386
- toast('เกิดข้อผิดพลาดในการเพิ่ม QR Code', 'error');
1387
- } finally {
1388
- loading(false);
1389
- }
1390
- }
1391
-
1392
- // PDF Generation from Template
1393
- async function createPDF(data, filename, addQR = false, qrUrl = '') {
1394
- loading(true, 'กำลังสร้างไฟล์ PDF...');
1395
-
1396
- try {
1397
- document.querySelectorAll('.sidebar, .hud-controls, header, footer').forEach(c => c.style.display = 'none');
1398
-
1399
- if (bg1B64) document.getElementById('bg1').src = bg1B64;
1400
- if (bg2B64) document.getElementById('bg2').src = bg2B64;
1401
-
1402
- fieldIds.forEach(id => {
1403
- const key = fieldToKey[id];
1404
- const val = data[key] || '';
1405
- const el = document.getElementById(id);
1406
- if (!el) return;
1407
- if (id === 'f6') el.innerHTML = val;
1408
- else el.innerHTML = val ? `<b>${val}</b>` : '';
1409
- });
1410
-
1411
- document.getElementById('photo').src = data[fieldToKey['photo']] || 'https://via.placeholder.com/110x138?text=No+Photo';
1412
-
1413
- await new Promise(r => setTimeout(r, 600));
1414
-
1415
- const el = document.createElement('div');
1416
- el.style.width = '892px';
1417
- ['page1', 'page2'].forEach(id => {
1418
- const c = document.getElementById(id).cloneNode(true);
1419
- c.querySelectorAll('.label').forEach(l => l.remove());
1420
- el.appendChild(c);
1421
- });
1422
- document.body.appendChild(el);
1423
-
1424
- const pdfBlob = await html2pdf().set({
1425
- margin: 0,
1426
- filename,
1427
- image: { type: 'jpeg', quality: 0.98 },
1428
- html2canvas: { scale: 6, useCORS: true, width: 892, height: 2522 },
1429
- jsPDF: { unit: 'px', format: [892, 1261], orientation: 'portrait', compress: true }
1430
- }).from(el).outputPdf('blob');
1431
-
1432
- document.body.removeChild(el);
1433
-
1434
- let finalBlob = pdfBlob;
1435
-
1436
- if (addQR && qrUrl) {
1437
- const pdfArrayBuffer = await pdfBlob.arrayBuffer();
1438
- const qrDataURL = generateQRCode15mm(qrUrl);
1439
- if (qrDataURL) {
1440
- const modifiedPdfBytes = await addQRCodeToPDF(pdfArrayBuffer, qrDataURL);
1441
- finalBlob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
1442
- }
1443
- }
1444
-
1445
- const url = URL.createObjectURL(finalBlob);
1446
- const a = document.createElement('a');
1447
- a.href = url;
1448
- a.download = filename;
1449
- document.body.appendChild(a);
1450
- a.click();
1451
- document.body.removeChild(a);
1452
- URL.revokeObjectURL(url);
1453
-
1454
- document.querySelectorAll('.sidebar, .hud-controls, header, footer').forEach(c => c.style.display = '');
1455
- if (bg1Obj) document.getElementById('bg1').src = bg1Obj;
1456
- if (bg2Obj) document.getElementById('bg2').src = bg2Obj;
1457
-
1458
- showData(currentIndex);
1459
-
1460
- } catch (error) {
1461
- console.error('Error creating PDF:', error);
1462
- throw error;
1463
- } finally {
1464
- loading(false);
1465
- }
1466
- }
1467
-
1468
- async function downloadPDF() {
1469
- if (allData.length === 0) {
1470
- toast('กรุณานำเข้าข้อมูลก่อน', 'error');
1471
- return;
1472
- }
1473
-
1474
- try {
1475
- await createPDF(allData[currentIndex], `${allData[currentIndex]['1'] || 'document'}.pdf`);
1476
- toast('สร้างไฟล์ PDF สำเร็จ', 'success');
1477
- } catch (error) {
1478
- toast('เกิดข้อผิดพลาดในการสร้าง PDF', 'error');
1479
- }
1480
- }
1481
-
1482
- async function downloadPDFWithQR(qrUrl) {
1483
- if (allData.length === 0) {
1484
- toast('กรุณานำเข้าข้อมูลก่อน', 'error');
1485
- return;
1486
- }
1487
-
1488
- try {
1489
- closeModal('qr-modal');
1490
- await createPDF(allData[currentIndex], `${allData[currentIndex]['1'] || 'document'}_with_qr.pdf`, true, qrUrl);
1491
- toast('สร้างไฟล์ PDF พร้อม QR Code สำเร็จ', 'success');
1492
- } catch (error) {
1493
- toast('เกิดข้อผิดพลาดในการสร้าง PDF', 'error');
1494
- }
1495
- }
1496
-
1497
- async function downloadAllPDFs() {
1498
- if (allData.length === 0) {
1499
- toast('ไม่มีข้อมูล', 'error');
1500
- return;
1501
- }
1502
-
1503
- if (!confirm(`ต้องการดาวน์โหลดทั้งหมด ${allData.length} ไฟล์?`)) return;
1504
-
1505
- for (let i = 0; i < allData.length; i++) {
1506
- currentIndex = i;
1507
- document.getElementById('data-select').value = i;
1508
- await createPDF(allData[i], `${allData[i]['1'] || 'document'}_batch_${i + 1}.pdf`);
1509
- await new Promise(r => setTimeout(r, 800));
1510
- }
1511
-
1512
- toast('ดาวน์โหลดทั้งหมดสำเร็จ', 'success');
1513
- }
1514
-
1515
- // History Functions
1516
- async function showHistory() {
1517
- const list = document.getElementById('history-list');
1518
- list.innerHTML = '';
1519
-
1520
- try {
1521
- const docs = await getAllDocs();
1522
-
1523
- if (docs.length === 0) {
1524
- list.innerHTML = '<p style="text-align:center;color:var(--text-tertiary);padding:2rem">ไม่มีประวัติไฟล์</p>';
1525
- } else {
1526
- docs.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
1527
-
1528
- docs.forEach(doc => {
1529
- const item = document.createElement('div');
1530
- item.className = 'history-item';
1531
- item.innerHTML = `
1532
- <i class="material-icons">${doc.hasQRCode ? 'qr_code' : 'picture_as_pdf'}</i>
1533
- <div class="history-item-info">
1534
- <div class="history-item-name">${doc.fileName}</div>
1535
- <div class="history-item-date">${new Date(doc.createdAt).toLocaleString('th-TH')}</div>
1536
- </div>
1537
- `;
1538
- item.onclick = async () => {
1539
- await loadPDF(doc.pdfData, doc.fileName);
1540
- closeModal('history-modal');
1541
- };
1542
- list.appendChild(item);
1543
- });
1544
- }
1545
-
1546
- document.getElementById('history-modal').classList.add('show');
1547
- } catch (error) {
1548
- console.error('Error loading history:', error);
1549
- toast('เกิดข้อผิดพลาดในการโหลดประวัติ', 'error');
1550
- }
1551
- }
1552
-
1553
- // Zoom Functions
1554
- function zoomIn() {
1555
- scale = Math.min(3, scale + 0.1);
1556
- document.getElementById('zoom-text').textContent = Math.round(scale * 100) + '%';
1557
- if (currentView === 'pdf' && pdfDoc) renderPDF();
1558
- }
1559
-
1560
- function zoomOut() {
1561
- scale = Math.max(0.2, scale - 0.1);
1562
- document.getElementById('zoom-text').textContent = Math.round(scale * 100) + '%';
1563
- if (currentView === 'pdf' && pdfDoc) renderPDF();
1564
- }
1565
-
1566
- // Event Listeners
1567
- document.addEventListener('DOMContentLoaded', async () => {
1568
- try {
1569
- await initDB();
1570
- const docs = await getAllDocs();
1571
- document.getElementById('file-count').textContent = docs.length;
1572
- toast('ระบบพร้อมใช้งาน', 'success');
1573
- } catch (error) {
1574
- console.error('Error initializing:', error);
1575
- toast('เกิดข้อผิดพลาดในการเริ่มต้นระบบ', 'error');
1576
- }
1577
- });
1578
-
1579
- // Toggle Sidebar
1580
- document.getElementById('toggle-sidebar').addEventListener('click', toggleSidebar);
1581
- document.getElementById('sidebar-overlay').addEventListener('click', toggleSidebar);
1582
-
1583
- // Theme Toggle
1584
- document.getElementById('btn-theme').addEventListener('click', toggleTheme);
1585
-
1586
- // History Button
1587
- document.getElementById('btn-history').addEventListener('click', showHistory);
1588
-
1589
- // Tab Buttons
1590
- document.querySelectorAll('.tab-btn').forEach(btn => {
1591
- btn.addEventListener('click', () => switchTab(btn.dataset.tab));
1592
- });
1593
-
1594
- // Zoom Controls
1595
- document.getElementById('zoom-in').addEventListener('click', zoomIn);
1596
- document.getElementById('zoom-out').addEventListener('click', zoomOut);
1597
-
1598
- // Background File Inputs
1599
- document.getElementById('bg1-file').addEventListener('change', e => handleBgFile('1', e.target.files[0]));
1600
- document.getElementById('bg2-file').addEventListener('change', e => handleBgFile('2', e.target.files[0]));
1601
-
1602
- // JSON File Input
1603
- document.getElementById('json-file').addEventListener('change', e => {
1604
- const file = e.target.files[0];
1605
- if (!file) return;
1606
-
1607
- document.getElementById('json-name').textContent = file.name;
1608
- const reader = new FileReader();
1609
- reader.onload = ev => {
1610
- try {
1611
- const raw = JSON.parse(ev.target.result);
1612
- if (!Array.isArray(raw) || raw.length === 0) throw new Error('ข้อมูลว่างเปล่า');
1613
- allData = raw;
1614
- currentIndex = 0;
1615
- populateDropdown();
1616
- showData(currentIndex);
1617
- toast(`นำเข้าข้อมูลสำเร็จ (${allData.length} รายการ)`, 'success');
1618
- } catch (err) {
1619
- toast('รูปแบบ JSON ไม่ถูกต้อง: ' + err.message, 'error');
1620
- }
1621
- };
1622
- reader.readAsText(file);
1623
- });
1624
-
1625
- // PDF File Input
1626
- document.getElementById('pdf-upload').addEventListener('change', e => {
1627
- const file = e.target.files[0];
1628
- if (!file) return;
1629
-
1630
- const reader = new FileReader();
1631
- reader.onload = () => loadPDF(reader.result, file.name);
1632
- reader.readAsArrayBuffer(file);
1633
- });
1634
-
1635
- // QR URL Input
1636
- document.getElementById('qr-url').addEventListener('input', updateQRPreview);
1637
-
1638
- // Modal Close on Overlay Click
1639
- document.querySelectorAll('.modal-overlay').forEach(modal => {
1640
- modal.addEventListener('click', e => {
1641
- if (e.target === modal) {
1642
- modal.classList.remove('show');
1643
- }
1644
- });
1645
- });
1646
-
1647
- // Keyboard Shortcuts
1648
- document.addEventListener('keydown', e => {
1649
- if (e.ctrlKey || e.metaKey) {
1650
- if (e.key === '=') {
1651
- e.preventDefault();
1652
- zoomIn();
1653
- } else if (e.key === '-') {
1654
- e.preventDefault();
1655
- zoomOut();
1656
- } else if (e.key === 's') {
1657
- e.preventDefault();
1658
- downloadPDF();
1659
- }
1660
- }
1661
-
1662
- if (e.key === 'Escape') {
1663
- document.querySelectorAll('.modal-overlay.show').forEach(modal => {
1664
- modal.classList.remove('show');
1665
- });
1666
- }
1667
- });
1668
- </script>
1669
  </body>
1670
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
6
+ <meta name="description" content="ระบบจัดการเอกสาร PDF Professional พร้อมระบบปรับแต่ง Layout">
7
+ <title>PDF Layout Wizard</title>
8
 
9
  <!-- Fonts -->
10
+ <link rel="preconnect" href="https://fonts.googleapis.com">
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@300;400;500;600;700&display=swap" rel="stylesheet">
13
 
14
  <!-- Libraries -->
15
  <script src="https://cdn.tailwindcss.com"></script>
 
18
  <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
19
  <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
20
  <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcode-generator/1.4.4/qrcode.min.js"></script>
21
+ <script src="https://unpkg.com/feather-icons"></script>
22
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
23
 
24
+ <!-- Custom CSS -->
25
+ <link rel="stylesheet" href="style.css">
26
+ </head>
27
+ <body class="bg-slate-900 text-slate-800 font-sans antialiased overflow-hidden transition-colors duration-300">
28
+
29
+ <div class="flex flex-col h-screen w-screen">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ <!-- Web Component: Navbar -->
32
+ <app-navbar></app-navbar>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
+ <!-- Main Content -->
35
+ <div class="flex flex-1 overflow-hidden relative">
 
 
36
 
37
+ <!-- Web Component: Sidebar -->
38
+ <app-sidebar id="sidebar"></app-sidebar>
 
39
 
40
+ <!-- Sidebar Overlay for Mobile -->
41
+ <div id="sidebar-overlay" class="fixed inset-0 bg-black/50 z-40 hidden transition-opacity lg:hidden"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  <!-- Viewer Container -->
44
+ <main class="flex-1 flex flex-col overflow-hidden bg-slate-900 relative" id="viewer-container">
45
+
46
  <!-- Tabs -->
47
+ <div class="bg-slate-800 border-b border-slate-700 px-4 pt-2 flex gap-4">
48
+ <button class="tab-btn active px-4 py-2 rounded-t-lg text-sm font-medium text-slate-400 hover:text-white transition-colors flex items-center gap-2" data-tab="template">
49
+ <i data-feather="file-text" class="w-4 h-4"></i> Template
 
50
  </button>
51
+ <button class="tab-btn px-4 py-2 rounded-t-lg text-sm font-medium text-slate-400 hover:text-white transition-colors flex items-center gap-2" data-tab="pdf">
52
+ <i data-feather="book-open" class="w-4 h-4"></i> PDF Viewer
 
53
  </button>
54
  </div>
55
 
56
  <!-- Viewer Content -->
57
+ <div class="flex-1 overflow-auto relative bg-slate-900/50 p-4" id="viewer-content">
58
  <!-- Template View -->
59
+ <div class="template-viewer flex flex-col items-center gap-8 pb-20" id="template-view">
60
+
61
  <!-- Page 1 -->
62
+ <div id="page1" class="page bg-white shadow-2xl relative overflow-hidden origin-top transition-transform duration-200">
63
+ <img id="bg1" loading="lazy" class="absolute inset-0 w-full h-full object-cover pointer-events-none z-0" src="" alt="BG 1">
64
+ <img id="photo" loading="lazy" class="field absolute border border-slate-200 z-10 rounded object-cover bg-slate-100" style="top:46px;left:725px;width:110px;height:138px;" src="https://via.placeholder.com/110x138?text=Photo" alt="Photo">
65
+ <img id="qr1" loading="lazy" class="field absolute z-10 bg-white" style="top:977px;left:762.5px;width:69px;height:69px;" src="" alt="QR 1">
66
+
67
+ <!-- Dynamic Text Fields -->
68
+ <div id="f1" class="field absolute whitespace-nowrap z-20 font-sans font-bold cursor-default select-none border border-transparent" style="top:80px;left:230px;font-size:16px;">f1</div>
69
+ <div id="f2" class="field absolute whitespace-nowrap z-20 font-sans font-bold cursor-default select-none border border-transparent" style="top:110px;left:230px;font-size:16px;">f2</div>
70
+ <div id="f3" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:150px;left:230px;font-size:14px;">f3</div>
71
+ <div id="f4" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:180px;left:230px;font-size:14px;">f4</div>
72
+ <div id="f5" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:180px;left:580px;font-size:14px;">f5</div>
73
+ <div id="f6" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:210px;left:580px;font-size:14px;">f6</div>
74
+ <div id="f7" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:240px;left:230px;font-size:14px;">f7</div>
75
+ <div id="f8" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:240px;left:580px;font-size:14px;">f8</div>
76
+ <div id="f9" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:270px;left:230px;font-size:14px;">f9</div>
77
+ <div id="f10" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:300px;left:230px;font-size:14px;">f10</div>
78
  </div>
79
 
80
  <!-- Page 2 -->
81
+ <div id="page2" class="page bg-white shadow-2xl relative overflow-hidden origin-top transition-transform duration-200">
82
+ <img id="bg2" loading="lazy" class="absolute inset-0 w-full h-full object-cover pointer-events-none z-0" src="" alt="BG 2">
83
+ <img id="qr2" loading="lazy" class="field absolute z-10 bg-white" style="top:925px;left:120px;width:90px;height:90px;" src="" alt="QR 2">
84
+
85
+ <div id="f12" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:60px;left:200px;font-size:19px;">f12</div>
86
+ <div id="f13" class="field absolute whitespace-nowrap z-20 font-sans font-normal cursor-default select-none border border-transparent" style="top:100px;left:200px;font-size:19px;">f13</div>
87
+ <div id="f14" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:140px;left:200px;font-size:19px;">f14</div>
88
+ <div id="f15" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:180px;left:200px;font-size:19px;">f15</div>
89
+ <div id="f16" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:180px;left:550px;font-size:19px;">f16</div>
90
+ <div id="f17" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:220px;left:200px;font-size:19px;">f17</div>
91
+ <div id="f18" class="field absolute whitespace-nowrap z-20 font-sans font-light cursor-default select-none border border-transparent" style="top:220px;left:550px;font-size:19px;">f18</div>
92
  </div>
93
  </div>
94
 
95
  <!-- PDF View -->
96
+ <div class="pdf-viewer hidden flex-col items-center gap-6 pb-20" id="pdf-view">
97
+ <div id="pdf-content" class="flex flex-col items-center"></div>
98
  </div>
99
  </div>
100
  </div>
101
  </div>
102
 
103
  <!-- HUD Controls -->
104
+ <div class="fixed bottom-4 left-1/2 transform -translate-x-1/2 bg-slate-800/90 backdrop-blur-md px-6 py-3 rounded-full flex items-center gap-4 text-white shadow-lg z-50 border border-slate-700">
105
+ <span class="text-sm flex items-center gap-2">
106
+ Page <input type="number" id="current-pg" value="1" min="1" class="w-12 bg-slate-700 border-none rounded text-center text-sm focus:ring-2 focus:ring-blue-500"> / <span id="total-pg">2</span>
107
+ </span>
108
+ <div class="h-4 w-px bg-slate-600"></div>
109
+ <button id="zoom-out" class="hover:text-blue-400 transition-colors"><i data-feather="minus"></i></button>
110
+ <span id="zoom-text" class="text-sm font-mono w-12 text-center">100%</span>
111
+ <button id="zoom-in" class="hover:text-blue-400 transition-colors"><i data-feather="plus"></i></button>
112
  </div>
113
 
114
+ <!-- Web Component: Footer -->
115
+ <app-footer></app-footer>
 
 
 
116
  </div>
117
 
118
+ <!-- Toast Notification -->
119
+ <div id="toast" class="fixed top-20 left-1/2 transform -translate-x-1/2 -translate-y-24 bg-slate-800 text-white px-6 py-3 rounded-lg shadow-xl z-[1000] transition-all duration-300 border-l-4 border-transparent flex items-center gap-3 max-w-[90%] pointer-events-none opacity-0">
120
+ <i data-feather="info" class="text-blue-400"></i>
121
+ <span id="toast-message" class="text-sm font-medium">Notification</span>
122
+ </div>
123
 
124
+ <!-- Debug Console -->
125
+ <div id="debug-console" class="fixed bottom-0 left-0 right-0 h-40 bg-black/90 text-green-400 font-mono text-xs p-2 overflow-y-auto z-[9999] hidden border-t border-green-800"></div>
126
+
127
  <!-- History Modal -->
128
+ <div id="history-modal" class="fixed inset-0 bg-black/70 backdrop-blur-sm z-[200] hidden flex items-center justify-center">
129
+ <div class="bg-white dark:bg-slate-800 w-full max-w-lg rounded-xl shadow-2xl overflow-hidden transform transition-all scale-100">
130
+ <div class="px-6 py-4 border-b border-slate-200 dark:border-slate-700 flex justify-between items-center bg-slate-50 dark:bg-slate-900">
131
+ <h3 class="text-lg font-bold text-slate-800 dark:text-white flex items-center gap-2"><i data-feather="clock"></i> ประวัติไฟล์</h3>
132
+ <button class="close-modal text-slate-400 hover:text-red-500 transition-colors"><i data-feather="x"></i></button>
 
 
133
  </div>
134
+ <div id="history-list" class="max-h-[60vh] overflow-y-auto p-2">
135
+ <!-- History items injected here -->
136
  </div>
137
  </div>
138
  </div>
139
+
140
  <!-- QR Code Modal -->
141
+ <div id="qr-modal" class="fixed inset-0 bg-black/70 backdrop-blur-sm z-[200] hidden flex items-center justify-center">
142
+ <div class="bg-white dark:bg-slate-800 w-full max-w-md rounded-xl shadow-2xl overflow-hidden p-6">
143
+ <div class="flex justify-between items-center mb-6">
144
+ <h3 class="text-lg font-bold text-slate-800 dark:text-white flex items-center gap-2"><i data-feather="grid"></i> ตั้งค่า QR Code</h3>
145
+ <button class="close-modal text-slate-400 hover:text-red-500 transition-colors"><i data-feather="x"></i></button>
 
 
146
  </div>
147
+ <div class="space-y-4">
148
+ <div>
149
+ <label class="block text-xs font-bold text-slate-500 uppercase mb-1">URL สำหรับ QR Code</label>
150
+ <input type="text" id="qr-url" class="w-full px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-700 border-none focus:ring-2 focus:ring-blue-500 text-slate-800 dark:text-white" placeholder="https://...">
 
151
  </div>
152
+ <div class="bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 rounded-lg p-3 text-sm text-blue-800 dark:text-blue-200">
153
+ <strong>ขนาด QR Code:</strong> 15mm × 13.58mm<br>
154
+ <span class="text-xs opacity-75">ตำแหน่ง: X=15.15mm, Y=292.45mm</span>
 
155
  </div>
156
+ <div class="flex justify-center bg-white dark:bg-slate-900 p-4 rounded-lg border border-slate-200 dark:border-slate-700">
 
 
157
  <canvas id="qr-preview-canvas" width="150" height="136"></canvas>
 
158
  </div>
159
+ <div class="flex gap-3 pt-2">
160
+ <button id="btn-qr-cancel" class="flex-1 px-4 py-2 rounded-lg border border-slate-300 dark:border-slate-600 text-slate-600 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700 font-medium transition-colors">ยกเลิก</button>
161
+ <button id="btn-qr-apply" class="flex-1 px-4 py-2 rounded-lg bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold hover:shadow-lg hover:scale-[1.02] transition-all flex justify-center items-center gap-2">
162
+ <i data-feather="download"></i> เพิ่ม QR และดาวน์โหลด
163
+ </button>
164
+ </div>
 
165
  </div>
166
  </div>
167
  </div>
168
+
169
  <!-- Loading Overlay -->
170
+ <div id="loading-overlay" class="fixed inset-0 bg-black/80 z-[300] hidden flex-col items-center justify-center">
171
+ <div class="w-12 h-12 border-4 border-blue-500/30 border-t-blue-500 rounded-full animate-spin mb-4"></div>
172
+ <div id="loading-text" class="text-white font-medium animate-pulse">กำลังประมวลผล...</div>
173
  </div>
174
 
175
+ <!-- Components Scripts -->
176
+ <script src="components/navbar.js"></script>
177
+ <script src="components/sidebar.js"></script>
178
+ <script src="components/footer.js"></script>
179
+
180
+ <!-- Main Logic -->
181
+ <script src="script.js"></script>
182
+ <script>feather.replace();</script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  </body>
184
  </html>