alterzick commited on
Commit
d87a62c
·
verified ·
1 Parent(s): 3b5ff26

Add 3 files

Browse files
Files changed (3) hide show
  1. README.md +7 -5
  2. index.html +1705 -19
  3. prompts.txt +2 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Sop Creator V1
3
- emoji: 🐨
4
- colorFrom: green
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: sop-creator-v1
3
+ emoji: ⚛️
4
+ colorFrom: yellow
5
+ colorTo: gray
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - QwenSite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1705 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Document Procedure Creator</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/docx@7.0.0/build/index.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
10
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet" />
11
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
12
+ <style>
13
+ body {
14
+ font-family: 'Poppins', sans-serif;
15
+ }
16
+ .sortable-chosen {
17
+ background-color: #a5d8ff !important;
18
+ border: 2px dashed #0d6efd !important;
19
+ }
20
+ .procedure-item {
21
+ cursor: grab;
22
+ transition: all 0.2s ease;
23
+ }
24
+ .procedure-item:hover {
25
+ transform: scale(1.02);
26
+ box-shadow: 0 10px 20px rgba(0,0,0,0.1);
27
+ }
28
+ #procedure-list, #compiled-preview {
29
+ min-height: 200px;
30
+ }
31
+ .approval-badge {
32
+ font-size: 0.7rem;
33
+ padding: 0.25rem 0.5rem;
34
+ border-radius: 999px;
35
+ }
36
+ .custom-scrollbar {
37
+ scrollbar-width: thin;
38
+ scrollbar-color: #cbd5e1 #f1f5f9;
39
+ }
40
+ .custom-scrollbar::-webkit-scrollbar {
41
+ width: 6px;
42
+ }
43
+ .custom-scrollbar::-webkit-scrollbar-track {
44
+ background: #f1f5f9;
45
+ border-radius: 10px;
46
+ }
47
+ .custom-scrollbar::-webkit-scrollbar-thumb {
48
+ background-color: #cbd5e1;
49
+ border-radius: 10px;
50
+ }
51
+ .image-preview {
52
+ max-width: 100%;
53
+ max-height: 150px;
54
+ border-radius: 0.375rem;
55
+ }
56
+ .draggable-handle {
57
+ cursor: move;
58
+ }
59
+ .tag {
60
+ display: inline-block;
61
+ padding: 0.2rem 0.5rem;
62
+ border-radius: 999px;
63
+ font-size: 0.75rem;
64
+ margin-right: 0.5rem;
65
+ margin-bottom: 0.5rem;
66
+ }
67
+ </style>
68
+ </head>
69
+ <body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen">
70
+
71
+ <!-- Header -->
72
+ <header class="bg-white shadow-lg border-b-4 border-blue-500">
73
+ <div class="container mx-auto px-6 py-4">
74
+ <h1 class="text-3xl font-bold text-gray-800 flex items-center gap-3">
75
+ <i class="fas fa-cogs text-blue-500"></i>
76
+ Document Procedure Creator
77
+ </h1>
78
+ <p class="text-gray-600 mt-1">Build professional SOP documents like assembling LEGO blocks</p>
79
+ </div>
80
+ </header>
81
+
82
+ <div class="container mx-auto px-6 py-8 grid grid-cols-1 lg:grid-cols-3 gap-8">
83
+
84
+ <!-- Left Panel: Database & Input -->
85
+ <div class="lg:col-span-1 space-y-6">
86
+
87
+ <!-- Input Procedure Database -->
88
+ <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-green-500">
89
+ <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center gap-2">
90
+ <i class="fas fa-database text-green-500"></i>
91
+ Procedure Database
92
+ </h2>
93
+ <div class="space-y-4">
94
+ <div>
95
+ <label class="block text-sm font-medium text-gray-700 mb-1">Procedure Title</label>
96
+ <input type="text" id="proc-title" class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-500" placeholder="e.g., User Login Process" />
97
+ </div>
98
+
99
+ <div>
100
+ <label class="block text-sm font-medium text-gray-700 mb-1">Procedure Type</label>
101
+ <select id="proc-type" class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-500">
102
+ <option value="general">General Procedure</option>
103
+ <option value="safety">Safety Procedure</option>
104
+ <option value="quality">Quality Control</option>
105
+ <option value="maintenance">Maintenance</option>
106
+ <option value="operational">Operational</option>
107
+ <option value="training">Training</option>
108
+ </select>
109
+ </div>
110
+
111
+ <div>
112
+ <label class="block text-sm font-medium text-gray-700 mb-1">Procedure Description</label>
113
+ <textarea id="proc-desc" rows="3" class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-green-500 resize-none" placeholder="Describe the procedure step..."></textarea>
114
+ </div>
115
+
116
+ <div>
117
+ <label class="block text-sm font-medium text-gray-700 mb-1">Add Media</label>
118
+ <div class="flex items-center space-x-2">
119
+ <input type="file" id="proc-image" accept="image/*" class="hidden" />
120
+ <button id="upload-image-btn" class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-1 rounded text-sm font-medium transition">
121
+ <i class="fas fa-image mr-1"></i> Upload Image
122
+ </button>
123
+ <span id="image-filename" class="text-xs text-gray-500">No image selected</span>
124
+ </div>
125
+ <div id="image-preview-container" class="mt-2 hidden">
126
+ <img id="image-preview" class="image-preview mx-auto" src="" alt="Preview" />
127
+ <button id="remove-image" class="text-red-500 text-xs mt-1 hover:underline">Remove image</button>
128
+ </div>
129
+ </div>
130
+
131
+ <div>
132
+ <label class="block text-sm font-medium text-gray-700 mb-1">Additional Fields</label>
133
+ <div class="grid grid-cols-2 gap-2 mb-2">
134
+ <button id="add-time" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 px-2 py-1 rounded flex items-center justify-center">
135
+ <i class="fas fa-clock mr-1"></i> Time
136
+ </button>
137
+ <button id="add-equipment" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 px-2 py-1 rounded flex items-center justify-center">
138
+ <i class="fas fa-tools mr-1"></i> Equipment
139
+ </button>
140
+ <button id="add-material" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 px-2 py-1 rounded flex items-center justify-center">
141
+ <i class="fas fa-box mr-1"></i> Material
142
+ </button>
143
+ <button id="add-personnel" class="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 px-2 py-1 rounded flex items-center justify-center">
144
+ <i class="fas fa-users mr-1"></i> Personnel
145
+ </button>
146
+ </div>
147
+ <div id="additional-fields-container" class="space-y-2 text-xs">
148
+ <!-- Dynamic fields will be added here -->
149
+ </div>
150
+ </div>
151
+
152
+ <div class="flex gap-3">
153
+ <button id="add-proc" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-1">
154
+ <i class="fas fa-plus"></i> Add to Database
155
+ </button>
156
+ <button id="clear-proc" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-1">
157
+ <i class="fas fa-trash"></i> Clear
158
+ </button>
159
+ </div>
160
+ </div>
161
+ </div>
162
+
163
+ <!-- Standard Document Templates -->
164
+ <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-purple-500">
165
+ <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center gap-2">
166
+ <i class="fas fa-file-prescription text-purple-500"></i>
167
+ Standard Templates
168
+ </h2>
169
+ <div class="space-y-2">
170
+ <button class="w-full text-left p-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition flex items-center justify-between group">
171
+ <div>
172
+ <div class="font-medium text-sm">SOP-001: Equipment Operation</div>
173
+ <div class="text-xs text-gray-500">Standard operating procedure for machinery</div>
174
+ </div>
175
+ <i class="fas fa-plus text-blue-500 text-sm opacity-0 group-hover:opacity-100 transition"></i>
176
+ </button>
177
+ <button class="w-full text-left p-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition flex items-center justify-between group">
178
+ <div>
179
+ <div class="font-medium text-sm">SOP-002: Safety Protocol</div>
180
+ <div class="text-xs text-gray-500">Safety procedures and guidelines</div>
181
+ </div>
182
+ <i class="fas fa-plus text-blue-500 text-sm opacity-0 group-hover:opacity-100 transition"></i>
183
+ </button>
184
+ <button class="w-full text-left p-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition flex items-center justify-between group">
185
+ <div>
186
+ <div class="font-medium text-sm">SOP-003: Quality Control</div>
187
+ <div class="text-xs text-gray-500">Quality inspection procedures</div>
188
+ </div>
189
+ <i class="fas fa-plus text-blue-500 text-sm opacity-0 group-hover:opacity-100 transition"></i>
190
+ </button>
191
+ </div>
192
+ </div>
193
+
194
+ <!-- Available Procedures -->
195
+ <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-purple-500">
196
+ <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center gap-2">
197
+ <i class="fas fa-th-list text-purple-500"></i>
198
+ Available Procedures
199
+ </h2>
200
+ <div id="procedure-database" class="space-y-2 max-h-60 overflow-y-auto custom-scrollbar">
201
+ <!-- Procedures will be dynamically added here -->
202
+ <p class="text-gray-500 text-sm text-center py-4 bg-gray-50 rounded-lg">No procedures added yet</p>
203
+ </div>
204
+ </div>
205
+
206
+ </div>
207
+
208
+ <!-- Middle Panel: Procedure Builder -->
209
+ <div class="lg:col-span-1">
210
+ <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-blue-500">
211
+ <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center gap-2">
212
+ <i class="fas fa-puzzle-piece text-blue-500"></i>
213
+ Procedure Builder
214
+ </h2>
215
+ <p class="text-gray-600 text-sm mb-4">Drag and drop procedures from the database to build your document sequence</p>
216
+
217
+ <div id="procedure-list" class="min-h-32 border-2 border-dashed border-gray-300 rounded-lg p-4 space-y-2 bg-gray-50">
218
+ <p class="text-gray-400 text-center py-4">Drop procedures here</p>
219
+ </div>
220
+
221
+ <div class="flex gap-3 mt-4">
222
+ <button id="compile-doc" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg font-medium transition flex items-center gap-1">
223
+ <i class="fas fa-file-alt"></i> Compile Document
224
+ </button>
225
+ <button id="reset-builder" class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg font-medium transition flex items-center gap-1">
226
+ <i class="fas fa-undo"></i> Reset
227
+ </button>
228
+ </div>
229
+ </div>
230
+
231
+ <!-- Approval Settings -->
232
+ <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-orange-500 mt-6">
233
+ <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center gap-2">
234
+ <i class="fas fa-check-circle text-orange-500"></i>
235
+ Approval Workflow
236
+ </h2>
237
+ <div id="approval-list" class="space-y-3">
238
+ <!-- Approval entries will be added here -->
239
+ </div>
240
+ <div class="mt-4 pt-4 border-t border-gray-200">
241
+ <div class="flex space-x-2 mb-3">
242
+ <input type="text" id="approver-name" placeholder="Approver name" class="flex-1 border border-gray-300 rounded px-2 py-1 text-sm">
243
+ <select id="approver-position" class="border border-gray-300 rounded px-2 py-1 text-sm">
244
+ <option value="Quality Assurance Manager">Quality Assurancer</option>
245
+ <option value="Department Head">Department Head</option>
246
+ <option value="Safety Officer">Safety Officer</option>
247
+ <option value="Process Engineer">Process Engineer</option>
248
+ <option value="Production Manager">Production Manager</option>
249
+ </select>
250
+ </div>
251
+ <button id="add-approver" class="w-full bg-orange-500 hover:bg-orange-600 text-white text-sm py-1 rounded transition">
252
+ <i class="fas fa-plus mr-1"></i> Add Approver
253
+ </button>
254
+ </div>
255
+ </div>
256
+ </div>
257
+
258
+ <!-- Right Panel: Preview & Export -->
259
+ <div class="lg:col-span-1">
260
+ <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-red-500">
261
+ <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center gap-2">
262
+ <i class="fas fa-eye text-red-500"></i>
263
+ Document Preview
264
+ </h2>
265
+
266
+ <div id="compiled-preview" class="border rounded-lg p-4 bg-white min-h-60 mb-4 max-h-96 overflow-y-auto text-sm custom-scrollbar">
267
+ <p class="text-gray-500 text-center py-8">Compile your document to see preview</p>
268
+ </div>
269
+
270
+ <div class="space-y-3">
271
+ <button id="download-doc" class="w-full bg-red-500 hover:bg-red-600 text-white py-3 rounded-lg font-medium transition flex items-center justify-center gap-2">
272
+ <i class="fas fa-download"></i> Download as Word
273
+ </button>
274
+ <button id="print-doc" class="w-full bg-gray-500 hover:bg-gray-600 text-white py-3 rounded-lg font-medium transition flex items-center justify-center gap-2">
275
+ <i class="fas fa-print"></i> Print Document
276
+ </button>
277
+ </div>
278
+ </div>
279
+
280
+ <!-- Document Info -->
281
+ <div class="bg-white rounded-xl shadow-lg p-6 border-l-4 border-teal-500 mt-6">
282
+ <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center gap-2">
283
+ <i class="fas fa-info-circle text-teal-500"></i>
284
+ Document Info
285
+ </h2>
286
+ <div class="space-y-3 text-sm text-gray-600">
287
+ <div class="flex justify-between">
288
+ <span>Procedures Used:</span>
289
+ <span id="proc-count">0</span>
290
+ </div>
291
+ <div class="flex justify-between">
292
+ <span>Estimated Pages:</span>
293
+ <span id="est-pages">1</span>
294
+ </div>
295
+ <div class="flex justify-between">
296
+ <span>Document ID:</span>
297
+ <span id="doc-id">N/A</span>
298
+ </div>
299
+ <div class="flex justify-between">
300
+ <span>Last Compiled:</span>
301
+ <span id="last-compiled">Not yet</span>
302
+ </div>
303
+ </div>
304
+ </div>
305
+ </div>
306
+
307
+ </div>
308
+
309
+ <!-- Toast Notification -->
310
+ <div id="toast" class="fixed bottom-6 right-6 bg-gray-800 text-white px-4 py-3 rounded-lg shadow-lg flex items-center gap-2 opacity-0 transition-opacity duration-300">
311
+ <i class="fas fa-check"></i>
312
+ <span id="toast-message">Document compiled successfully!</span>
313
+ </div>
314
+
315
+ <script>
316
+ // Global variables
317
+ let procedures = [];
318
+ let currentDocument = [];
319
+ let documentCompiled = false;
320
+ let approvers = [];
321
+ let documentId = "DOC-" + new Date().getFullYear() + "-" + String(Math.floor(Math.random() * 10000)).padStart(4, '0');
322
+ let additionalFieldCounter = 0;
323
+
324
+ // Standard document templates
325
+ const standardTemplates = [
326
+ {
327
+ id: "SOP-001",
328
+ title: "Equipment Operation",
329
+ description: "Standard operating procedure for machinery",
330
+ procedures: [
331
+ { title: "Pre-Operation Inspection", desc: "Check all safety features, fluid levels, and mechanical components before starting the equipment." },
332
+ { title: "Startup Procedure", desc: "Follow the correct sequence for powering up the machine, including warm-up periods." },
333
+ { title: "Operation Guidelines", desc: "Operate the equipment within specified parameters and monitor performance indicators." },
334
+ { title: "Shutdown Procedure", desc: "Follow the proper shutdown sequence and perform post-operation checks." }
335
+ ]
336
+ },
337
+ {
338
+ id: "SOP-002",
339
+ title: "Safety Protocol",
340
+ description: "Safety procedures and guidelines",
341
+ procedures: [
342
+ { title: "Personal Protective Equipment (PPE)", desc: "Required PPE includes safety glasses, gloves, hard hat, and safety shoes." },
343
+ { title: "Emergency Response", desc: "In case of emergency, follow evacuation procedures and use appropriate emergency equipment." },
344
+ { title: "Hazard Identification", desc: "Identify potential hazards in the work area and report them immediately." },
345
+ { title: "Incident Reporting", desc: "Report all incidents, near misses, and safety concerns through the proper channels." }
346
+ ]
347
+ },
348
+ {
349
+ id: "SOP-003",
350
+ title: "Quality Control",
351
+ description: "Quality inspection procedures",
352
+ procedures: [
353
+ { title: "Incoming Material Inspection", desc: "Inspect all incoming materials for damage, quantity, and specification compliance." },
354
+ { title: "In-Process Checks", desc: "Perform regular quality checks during the production process at designated checkpoints." },
355
+ { title: "Final Product Verification", desc: "Verify final product meets all quality standards and customer requirements." },
356
+ { title: "Documentation and Record Keeping", desc: "Maintain accurate records of all quality inspections and test results." }
357
+ ]
358
+ }
359
+ ];
360
+
361
+ // DOM Elements
362
+ const procTitle = document.getElementById('proc-title');
363
+ const procType = document.getElementById('proc-type');
364
+ const procDesc = document.getElementById('proc-desc');
365
+ const procImage = document.getElementById('proc-image');
366
+ const uploadImageBtn = document.getElementById('upload-image-btn');
367
+ const imageFilename = document.getElementById('image-filename');
368
+ const imagePreviewContainer = document.getElementById('image-preview-container');
369
+ const imagePreview = document.getElementById('image-preview');
370
+ const removeImage = document.getElementById('remove-image');
371
+ const addTime = document.getElementById('add-time');
372
+ const addEquipment = document.getElementById('add-equipment');
373
+ const addMaterial = document.getElementById('add-material');
374
+ const addPersonnel = document.getElementById('add-personnel');
375
+ const additionalFieldsContainer = document.getElementById('additional-fields-container');
376
+ const addProcBtn = document.getElementById('add-proc');
377
+ const clearProcBtn = document.getElementById('clear-proc');
378
+ const procDatabase = document.getElementById('procedure-database');
379
+ const procList = document.getElementById('procedure-list');
380
+ const compileDocBtn = document.getElementById('compile-doc');
381
+ const resetBuilderBtn = document.getElementById('reset-builder');
382
+ const compiledPreview = document.getElementById('compiled-preview');
383
+ const downloadDocBtn = document.getElementById('download-doc');
384
+ const printDocBtn = document.getElementById('print-doc');
385
+ const procCount = document.getElementById('proc-count');
386
+ const estPages = document.getElementById('est-pages');
387
+ const docId = document.getElementById('doc-id');
388
+ const lastCompiled = document.getElementById('last-compiled');
389
+
390
+ // Approval elements
391
+ const approvalList = document.getElementById('approval-list');
392
+ const approverName = document.getElementById('approver-name');
393
+ const approverPosition = document.getElementById('approver-position');
394
+ const addApproverBtn = document.getElementById('add-approver');
395
+
396
+ // Toast elements
397
+ const toast = document.getElementById('toast');
398
+ const toastMessage = document.getElementById('toast-message');
399
+
400
+ // Show toast notification
401
+ function showToast(message, type = 'success') {
402
+ toastMessage.textContent = message;
403
+ if (type === 'error') {
404
+ toast.querySelector('i').className = 'fas fa-exclamation-triangle';
405
+ toast.classList.remove('bg-gray-800');
406
+ toast.classList.add('bg-red-600');
407
+ } else {
408
+ toast.querySelector('i').className = 'fas fa-check';
409
+ toast.classList.remove('bg-red-600');
410
+ toast.classList.add('bg-gray-800');
411
+ }
412
+
413
+ toast.classList.remove('opacity-0');
414
+ toast.classList.add('opacity-100');
415
+ setTimeout(() => {
416
+ toast.classList.remove('opacity-100');
417
+ toast.classList.add('opacity-0');
418
+ }, 3000);
419
+ }
420
+
421
+ // Add event listener for upload image button
422
+ uploadImageBtn.addEventListener('click', () => {
423
+ procImage.click();
424
+ });
425
+
426
+ // Handle image selection
427
+ procImage.addEventListener('change', (e) => {
428
+ const file = e.target.files[0];
429
+ if (file) {
430
+ imageFilename.textContent = file.name;
431
+ const reader = new FileReader();
432
+
433
+ reader.onload = (event) => {
434
+ imagePreview.src = event.target.result;
435
+ imagePreviewContainer.classList.remove('hidden');
436
+ };
437
+
438
+ reader.readAsDataURL(file);
439
+ }
440
+ });
441
+
442
+ // Remove image
443
+ removeImage.addEventListener('click', () => {
444
+ procImage.value = '';
445
+ imageFilename.textContent = 'No image selected';
446
+ imagePreviewContainer.classList.add('hidden');
447
+ imagePreview.src = '';
448
+ });
449
+
450
+ // Add dynamic fields
451
+ function addDynamicField(type, placeholder) {
452
+ const fieldId = `field-${type}-${additionalFieldCounter++}`;
453
+ const fieldEl = document.createElement('div');
454
+ fieldEl.className = 'flex items-center space-x-2';
455
+ fieldEl.innerHTML = `
456
+ <input type="text" id="${fieldId}" placeholder="${placeholder}"
457
+ class="flex-1 border border-gray-300 rounded px-2 py-1 text-xs" />
458
+ <button class="remove-field text-red-500 text-xs" data-id="${fieldId}">
459
+ <i class="fas fa-times"></i>
460
+ </button>
461
+ `;
462
+
463
+ additionalFieldsContainer.appendChild(fieldEl);
464
+
465
+ // Add event listener to remove button
466
+ fieldEl.querySelector('.remove-field').addEventListener('click', (e) => {
467
+ const fieldId = e.target.closest('.remove-field').dataset.id;
468
+ document.getElementById(fieldId).closest('div').remove();
469
+ });
470
+ }
471
+
472
+ addTime.addEventListener('click', () => {
473
+ addDynamicField('time', 'Estimated time (e.g., 5 minutes)');
474
+ });
475
+
476
+ addEquipment.addEventListener('click', () => {
477
+ addDynamicField('equipment', 'Required equipment');
478
+ });
479
+
480
+ addMaterial.addEventListener('click', () => {
481
+ addDynamicField('material', 'Required materials');
482
+ });
483
+
484
+ addPersonnel.addEventListener('click', () => {
485
+ addDynamicField('personnel', 'Required personnel');
486
+ });
487
+
488
+ // Add procedure to database
489
+ addProcBtn.addEventListener('click', () => {
490
+ const title = procTitle.value.trim();
491
+ const type = procType.value;
492
+ const desc = procDesc.value.trim();
493
+
494
+ if (!title || !desc) {
495
+ showToast('Please fill in both title and description', 'error');
496
+ return;
497
+ }
498
+
499
+ // Collect additional fields
500
+ const additionalFields = [];
501
+ document.querySelectorAll('#additional-fields-container input').forEach(input => {
502
+ if (input.value.trim()) {
503
+ // Extract field type from the id
504
+ const fieldParts = input.id.split('-');
505
+ const type = fieldParts[1];
506
+ additionalFields.push({
507
+ type: type,
508
+ value: input.value.trim()
509
+ });
510
+ }
511
+ });
512
+
513
+ // Get image data if exists
514
+ let imageData = null;
515
+ if (imagePreview.src) {
516
+ imageData = {
517
+ src: imagePreview.src,
518
+ filename: imageFilename.textContent
519
+ };
520
+ }
521
+
522
+ const newProc = {
523
+ id: Date.now(),
524
+ title,
525
+ type,
526
+ desc,
527
+ additionalFields,
528
+ imageData,
529
+ createdAt: new Date()
530
+ };
531
+
532
+ procedures.push(newProc);
533
+ // Reset form
534
+ procTitle.value = '';
535
+ procDesc.value = '';
536
+ procImage.value = '';
537
+ imageFilename.textContent = 'No image selected';
538
+ imagePreviewContainer.classList.add('hidden');
539
+ imagePreview.src = '';
540
+ additionalFieldsContainer.innerHTML = '';
541
+
542
+ renderProcedureDatabase();
543
+ showToast('Procedure added to database!');
544
+ });
545
+
546
+ // Clear procedure input
547
+ clearProcBtn.addEventListener('click', () => {
548
+ procTitle.value = '';
549
+ procDesc.value = '';
550
+ procImage.value = '';
551
+ imageFilename.textContent = 'No image selected';
552
+ imagePreviewContainer.classList.add('hidden');
553
+ imagePreview.src = '';
554
+ additionalFieldsContainer.innerHTML = '';
555
+ });
556
+
557
+ // Render procedure database
558
+ function renderProcedureDatabase() {
559
+ if (procedures.length === 0) {
560
+ procDatabase.innerHTML = '<p class="text-gray-500 text-sm text-center py-4 bg-gray-50 rounded-lg">No procedures added yet</p>';
561
+ return;
562
+ }
563
+
564
+ procDatabase.innerHTML = '';
565
+ procedures.forEach(proc => {
566
+ const procEl = document.createElement('div');
567
+
568
+ // Determine type color
569
+ let typeColor = 'gray';
570
+ switch(proc.type) {
571
+ case 'safety': typeColor = 'red'; break;
572
+ case 'quality': typeColor = 'blue'; break;
573
+ case 'maintenance': typeColor = 'yellow'; break;
574
+ case 'operational': typeColor = 'green'; break;
575
+ case 'training': typeColor = 'purple'; break;
576
+ default: typeColor = 'gray';
577
+ }
578
+
579
+ procEl.className = 'procedure-item p-3 border border-gray-200 rounded-lg bg-white flex flex-col hover:shadow-md transition';
580
+ procEl.innerHTML = `
581
+ <div class="flex justify-between items-start mb-2">
582
+ <div class="flex-1">
583
+ <h4 class="font-medium text-gray-800 text-sm">${proc.title}</h4>
584
+ <div class="mt-1">
585
+ <span class="tag bg-${typeColor}-100 text-${typeColor}-800">${capitalizeFirstLetter(proc.type)}</span>
586
+ </div>
587
+ </div>
588
+ <button data-id="${proc.id}" class="remove-proc ml-2 text-red-500 hover:text-red-700 text-xs">
589
+ <i class="fas fa-times"></i>
590
+ </button>
591
+ </div>
592
+ <p class="text-gray-600 text-xs mb-2">${truncateText(proc.desc, 80)}</p>
593
+ ${proc.imageData ? '<div class="text-xs text-blue-500 mb-1"><i class="fas fa-image mr-1"></i> Image attached</div>' : ''}
594
+ ${proc.additionalFields.length > 0 ?
595
+ `<div class="text-xs text-gray-500 mt-1">${proc.additionalFields.length} additional field(s)</div>` : ''}
596
+ `;
597
+ procDatabase.appendChild(procEl);
598
+ });
599
+
600
+ // Add drag event listeners
601
+ document.querySelectorAll('.procedure-item').forEach(item => {
602
+ item.addEventListener('dragstart', handleDragStart);
603
+ });
604
+
605
+ // Add remove event listeners
606
+ document.querySelectorAll('.remove-proc').forEach(btn => {
607
+ btn.addEventListener('click', (e) => {
608
+ const procId = parseInt(e.target.closest('.remove-proc').dataset.id);
609
+ procedures = procedures.filter(p => p.id !== procId);
610
+ renderProcedureDatabase();
611
+ showToast('Procedure removed from database');
612
+ });
613
+ });
614
+ }
615
+
616
+ // Add approver
617
+ addApproverBtn.addEventListener('click', () => {
618
+ const name = approverName.value.trim();
619
+ const position = approverPosition.value.trim();
620
+
621
+ if (!name || !position) {
622
+ showToast('Please enter both name and position', 'error');
623
+ return;
624
+ }
625
+
626
+ const approver = {
627
+ id: Date.now(),
628
+ name,
629
+ position
630
+ };
631
+
632
+ approvers.push(approver);
633
+ approverName.value = '';
634
+ renderApprovers();
635
+ showToast('Approver added to workflow');
636
+ });
637
+
638
+ // Render approvers list
639
+ function renderApprovers() {
640
+ if (approvers.length === 0) {
641
+ approvalList.innerHTML = '<p class="text-gray-500 text-sm text-center py-2 bg-gray-50 rounded">No approvers added yet</p>';
642
+ return;
643
+ }
644
+
645
+ approvalList.innerHTML = '';
646
+ approvers.forEach((approver, index) => {
647
+ const approverEl = document.createElement('div');
648
+ approverEl.className = 'flex items-center justify-between p-3 border border-gray-200 rounded-lg bg-white';
649
+ approverEl.innerHTML = `
650
+ <div class="flex-1">
651
+ <div class="font-medium text-sm">${approver.name}</div>
652
+ <div class="text-xs text-gray-500">${approver.position}</div>
653
+ </div>
654
+ <button data-id="${approver.id}" class="text-red-500 hover:text-red-700 text-xs">
655
+ <i class="fas fa-times"></i>
656
+ </button>
657
+ `;
658
+ approvalList.appendChild(approverEl);
659
+ });
660
+
661
+ // Add remove event listeners
662
+ document.querySelectorAll('#approval-list .text-red-500').forEach(btn => {
663
+ btn.addEventListener('click', (e) => {
664
+ const approverId = parseInt(e.target.closest('button').dataset.id);
665
+ approvers = approvers.filter(a => a.id !== approverId);
666
+ renderApprovers();
667
+ showToast('Approver removed from workflow');
668
+ });
669
+ });
670
+ }
671
+
672
+ // Truncate text
673
+ function truncateText(text, maxLength) {
674
+ return text.length > maxLength ? text.substr(0, maxLength) + '...' : text;
675
+ }
676
+
677
+ function capitalizeFirstLetter(string) {
678
+ return string.charAt(0).toUpperCase() + string.slice(1);
679
+ }
680
+
681
+ // Drag and drop functionality
682
+ let draggedItem = null;
683
+
684
+ function handleDragStart(e) {
685
+ draggedItem = this;
686
+ e.dataTransfer.effectAllowed = 'copyMove';
687
+ e.dataTransfer.setData('text/html', this.innerHTML);
688
+ this.classList.add('opacity-50');
689
+ }
690
+
691
+ procList.addEventListener('dragover', (e) => {
692
+ e.preventDefault();
693
+ e.dataTransfer.dropEffect = 'move';
694
+ const afterElement = getDragAfterElement(procList, e.clientY);
695
+ const draggable = document.createElement('div');
696
+ draggable.className = 'procedure-item p-3 border border-dashed border-blue-400 rounded-lg bg-blue-50 text-center text-blue-700 text-sm';
697
+ draggable.innerHTML = 'Drop to add procedure';
698
+
699
+ // Remove any existing placeholder
700
+ const existing = procList.querySelector('.procedure-item.p-3.border-dashed');
701
+ if (existing) {
702
+ existing.remove();
703
+ }
704
+
705
+ if (afterElement == null) {
706
+ procList.appendChild(draggable);
707
+ } else {
708
+ procList.insertBefore(draggable, afterElement);
709
+ }
710
+ });
711
+
712
+ procList.addEventListener('drop', (e) => {
713
+ e.preventDefault();
714
+
715
+ // Remove placeholder
716
+ const placeholders = procList.querySelectorAll('.procedure-item.p-3.border-dashed');
717
+ placeholders.forEach(p => p.remove());
718
+
719
+ // Check if we're dropping from database
720
+ if (draggedItem && draggedItem.closest('#procedure-database')) {
721
+ const procIdStr = draggedItem.querySelector('.remove-proc')?.dataset.id;
722
+ if (procIdStr) {
723
+ const procId = parseInt(procIdStr);
724
+ const proc = procedures.find(p => p.id === procId);
725
+
726
+ if (proc && !currentDocument.some(p => p.id === procId)) {
727
+ currentDocument.push(proc);
728
+ renderProcedureList();
729
+ showToast('Procedure added to document');
730
+ } else if (currentDocument.some(p => p.id === procId)) {
731
+ showToast('This procedure is already in the document', 'error');
732
+ }
733
+ }
734
+ }
735
+
736
+ // Handle reordering
737
+ if (draggedItem && draggedItem.closest('#procedure-list')) {
738
+ const procIdStr = draggedItem.querySelector('.remove-from-doc')?.dataset.id;
739
+ if (procIdStr) {
740
+ const procId = parseInt(procIdStr);
741
+ const procIndex = parseInt(draggedItem.querySelector('.remove-from-doc')?.dataset.index);
742
+ const proc = currentDocument[procIndex];
743
+
744
+ // Remove the temporary placeholder
745
+ const placeholder = procList.querySelector('.procedure-item.p-3.border-dashed');
746
+ if (placeholder) {
747
+ placeholder.remove();
748
+ }
749
+
750
+ // Find where to insert
751
+ const afterElement = getDragAfterElement(procList, e.clientY);
752
+ if (afterElement) {
753
+ const targetIndex = Array.from(procList.children).indexOf(afterElement) - 1;
754
+ // Remove from current position
755
+ currentDocument.splice(procIndex, 1);
756
+ // Insert at new position
757
+ currentDocument.splice(targetIndex, 0, proc);
758
+ renderProcedureList();
759
+ showToast('Procedure order updated');
760
+ }
761
+ }
762
+ }
763
+
764
+ if (draggedItem) {
765
+ draggedItem.classList.remove('opacity-50');
766
+ draggedItem = null;
767
+ }
768
+ });
769
+
770
+ function getDragAfterElement(container, y) {
771
+ const draggableElements = [...container.querySelectorAll('.original')];
772
+ return draggableElements.reduce((closest, child) => {
773
+ const box = child.getBoundingClientRect();
774
+ const offset = y - box.top - box.height / 2;
775
+ if (offset < 0 && offset > closest.offset) {
776
+ return { offset: offset, element: child };
777
+ } else {
778
+ return closest;
779
+ }
780
+ }, { offset: Number.NEGATIVE_INFINITY }).element;
781
+ }
782
+
783
+ // Render procedure list in builder
784
+ function renderProcedureList() {
785
+ if (currentDocument.length === 0) {
786
+ procList.innerHTML = '<p class="text-gray-400 text-center py-4">Drop procedures here</p>';
787
+ return;
788
+ }
789
+
790
+ procList.innerHTML = '';
791
+ currentDocument.forEach((proc, index) => {
792
+ const procEl = document.createElement('div');
793
+ procEl.className = 'original procedure-item p-3 border border-gray-200 rounded-lg bg-white mb-2';
794
+ procEl.draggable = true;
795
+
796
+ // Determine type color
797
+ let typeColor = 'gray';
798
+ switch(proc.type) {
799
+ case 'safety': typeColor = 'red'; break;
800
+ case 'quality': typeColor = 'blue'; break;
801
+ case 'maintenance': typeColor = 'yellow'; break;
802
+ case 'operational': typeColor = 'green'; break;
803
+ case 'training': typeColor = 'purple'; break;
804
+ default: typeColor = 'gray';
805
+ }
806
+
807
+ procEl.innerHTML = `
808
+ <div class="flex justify-between items-start">
809
+ <div class="flex-1">
810
+ <h4 class="font-medium text-gray-800 text-sm">${index + 1}. ${proc.title}</h4>
811
+ <div class="mt-1">
812
+ <span class="tag bg-${typeColor}-100 text-${typeColor}-800">${capitalizeFirstLetter(proc.type)}</span>
813
+ </div>
814
+ </div>
815
+ <div class="flex items-center">
816
+ <button data-id="${proc.id}" data-index="${index}" class="remove-from-doc ml-2 text-red-500 hover:text-red-700 text-xs">
817
+ <i class="fas fa-times"></i>
818
+ </button>
819
+ <i class="fas fa-grip-vertical text-gray-400 ml-2 draggable-handle"></i>
820
+ </div>
821
+ </div>
822
+ <p class="text-gray-600 text-xs mt-2">${truncateText(proc.desc, 100)}</p>
823
+ ${proc.imageData ? '<div class="text-xs text-blue-500 mt-1"><i class="fas fa-image mr-1"></i> Image attached</div>' : ''}
824
+ `;
825
+ procList.appendChild(procEl);
826
+ });
827
+
828
+ // Add event listeners for drag and drop reordering
829
+ makeSortable();
830
+ }
831
+
832
+ // Make the procedure list sortable
833
+ function makeSortable() {
834
+ // Add drag event listeners to items
835
+ document.querySelectorAll('.original').forEach(item => {
836
+ // Make the entire item draggable, but use the grip icon as visual indicator
837
+ item.addEventListener('dragstart', function(e) {
838
+ draggedItem = this;
839
+ e.dataTransfer.effectAllowed = 'move';
840
+
841
+ // Get the procedure data
842
+ const procId = parseInt(this.querySelector('.remove-from-doc').dataset.id);
843
+ const procIndex = parseInt(this.querySelector('.remove-from-doc').dataset.index);
844
+ const proc = currentDocument[procIndex];
845
+
846
+ // Store the data
847
+ e.dataTransfer.setData('text/plain', proc.id.toString());
848
+
849
+ // Visual feedback
850
+ this.classList.add('opacity-50');
851
+ });
852
+
853
+ item.addEventListener('dragend', function() {
854
+ this.classList.remove('opacity-50');
855
+ draggedItem = null;
856
+ });
857
+ });
858
+
859
+ // Allow to reorder within the procedure list
860
+ procList.addEventListener('dragover', function(e) {
861
+ e.preventDefault();
862
+ e.dataTransfer.dropEffect = 'move';
863
+
864
+ // Show a placeholder
865
+ const afterElement = getDragAfterElement(procList, e.clientY);
866
+ const placeholder = document.querySelector('.drag-placeholder');
867
+
868
+ if (!placeholder) {
869
+ const newPlaceholder = document.createElement('div');
870
+ newPlaceholder.className = 'drag-placeholder w-full h-8 border-2 border-dashed border-blue-400 my-1 rounded bg-blue-50';
871
+ if (afterElement) {
872
+ procList.insertBefore(newPlaceholder, afterElement);
873
+ } else {
874
+ procList.appendChild(newPlaceholder);
875
+ }
876
+ }
877
+ });
878
+
879
+ procList.addEventListener('drop', function(e) {
880
+ e.preventDefault();
881
+
882
+ // Remove placeholder
883
+ const placeholder = document.querySelector('.drag-placeholder');
884
+ if (placeholder) {
885
+ placeholder.remove();
886
+ }
887
+
888
+ // Get the dragged item and the target position
889
+ if (draggedItem) {
890
+ const procId = parseInt(draggedItem.querySelector('.remove-from-doc').dataset.id);
891
+ const procIndex = parseInt(draggedItem.querySelector('.remove-from-doc').dataset.index);
892
+ const proc = currentDocument[procIndex];
893
+
894
+ // Remove from current position
895
+ currentDocument.splice(procIndex, 1);
896
+
897
+ // Find the new position
898
+ const afterElement = getDragAfterElement(procList, e.clientY);
899
+ let newIndex = afterElement ? Array.from(procList.children).indexOf(afterElement) : procList.children.length;
900
+
901
+ // Adjust index if placeholder was in the way
902
+ if (newIndex > procIndex) {
903
+ newIndex--;
904
+ }
905
+
906
+ // Insert at new position
907
+ currentDocument.splice(newIndex, 0, proc);
908
+ renderProcedureList();
909
+ showToast('Procedure order updated');
910
+
911
+ draggedItem = null;
912
+ }
913
+ });
914
+ }
915
+
916
+ // Remove from document
917
+ function setupRemoveListeners() {
918
+ document.querySelectorAll('.remove-from-doc').forEach(btn => {
919
+ btn.addEventListener('click', (e) => {
920
+ const index = parseInt(e.target.closest('.remove-from-doc').dataset.index);
921
+ currentDocument.splice(index, 1);
922
+ renderProcedureList();
923
+ updateDocumentStats();
924
+ showToast('Procedure removed from document');
925
+ });
926
+ });
927
+ }
928
+
929
+ // Reset builder
930
+ resetBuilderBtn.addEventListener('click', () => {
931
+ if (currentDocument.length > 0) {
932
+ if (confirm('Are you sure you want to reset the procedure builder?')) {
933
+ currentDocument = [];
934
+ renderProcedureList();
935
+ showToast('Procedure builder reset');
936
+ }
937
+ }
938
+ });
939
+
940
+ // Apply standard template
941
+ document.querySelectorAll('.bg-white.rounded-lg.hover\\:bg-gray-50').forEach((btn, index) => {
942
+ btn.addEventListener('click', () => {
943
+ const template = standardTemplates[index];
944
+ if (confirm(`Load template "${template.title}"? This will replace your current document.`)) {
945
+ currentDocument = [];
946
+ template.procedures.forEach(proc => {
947
+ const newProc = {
948
+ id: Date.now() + Math.random(),
949
+ title: proc.title,
950
+ type: template.id === 'SOP-002' ? 'safety' : template.id === 'SOP-003' ? 'quality' : 'general',
951
+ desc: proc.desc,
952
+ additionalFields: [],
953
+ imageData: null,
954
+ createdAt: new Date()
955
+ };
956
+ currentDocument.push(newProc);
957
+ });
958
+ renderProcedureList();
959
+ showToast(`Template "${template.title}" loaded!`);
960
+ }
961
+ });
962
+ });
963
+
964
+ // Compile document
965
+ compileDocBtn.addEventListener('click', () => {
966
+ if (currentDocument.length === 0) {
967
+ showToast('Please add at least one procedure to compile the document', 'error');
968
+ return;
969
+ }
970
+
971
+ // Generate document content
972
+ const date = new Date();
973
+ const dateString = date.toLocaleDateString('en-US', {
974
+ year: 'numeric',
975
+ month: 'long',
976
+ day: 'numeric'
977
+ });
978
+
979
+ let content = `
980
+ <div class="compiled-document">
981
+ <!-- Cover Page -->
982
+ <div class="cover-page text-center py-20 bg-gradient-to-b from-blue-900 to-blue-700 text-white mb-8">
983
+ <h1 class="text-5xl font-bold mb-4">STANDARD OPERATING PROCEDURE</h1>
984
+ <div class="w-32 h-1 bg-yellow-400 mx-auto mb-6"></div>
985
+ <p class="text-2xl font-light mb-2">${documentId}</p>
986
+ <p class="text-xl">Document Procedure Creator</p>
987
+ <p class="mt-2 text-blue-200">Generated on ${dateString}</p>
988
+ </div>
989
+
990
+ <!-- Document Information -->
991
+ <div class="doc-info mb-8">
992
+ <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Document Information</h2>
993
+ <div class="bg-gray-50 p-4 rounded-lg border">
994
+ <div class="grid grid-cols-2 gap-4 text-sm">
995
+ <div><strong>Document ID:</strong> ${documentId}</div>
996
+ <div><strong>Version:</strong> 1.0</div>
997
+ <div><strong>Effective Date:</strong> ${dateString}</div>
998
+ <div><strong>Review Period:</strong> 12 months</div>
999
+ <div><strong>Prepared By:</strong> Digital System</div>
1000
+ <div><strong>Total Procedures:</strong> ${currentDocument.length}</div>
1001
+ </div>
1002
+ </div>
1003
+ </div>
1004
+
1005
+ <!-- Table of Contents -->
1006
+ <div class="table-of-contents mb-8">
1007
+ <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
1008
+ <i class="fas fa-list-alt mr-3 text-blue-500"></i>
1009
+ Table of Contents
1010
+ </h2>
1011
+ <div class="bg-gray-50 p-4 rounded-lg border">
1012
+ <ol class="list-decimal list-inside space-y-2 text-gray-700">
1013
+ `;
1014
+
1015
+ currentDocument.forEach((proc, index) => {
1016
+ content += `<li class="ml-4"><a href="#proc-${proc.id}" class="text-blue-600 hover:underline">${proc.title}</a></li>`;
1017
+ });
1018
+
1019
+ if (approvers.length > 0) {
1020
+ content += `<li class="ml-4"><a href="#approvals" class="text-blue-600 hover:underline">Approvals</a></li>`;
1021
+ }
1022
+
1023
+ content += `
1024
+ </ol>
1025
+ </div>
1026
+ </div>
1027
+
1028
+ <!-- Procedures -->
1029
+ <div class="procedures">
1030
+ <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
1031
+ <i class="fas fa-cogs mr-3 text-blue-500"></i>
1032
+ Procedures
1033
+ </h2>
1034
+ `;
1035
+
1036
+ currentDocument.forEach((proc, index) => {
1037
+ // Extract additional fields by type
1038
+ const timeFields = proc.additionalFields.filter(f => f.type === 'time');
1039
+ const equipmentFields = proc.additionalFields.filter(f => f.type === 'equipment');
1040
+ const materialFields = proc.additionalFields.filter(f => f.type === 'material');
1041
+ const personnelFields = proc.additionalFields.filter(f => f.type === 'personnel');
1042
+
1043
+ content += `
1044
+ <div id="proc-${proc.id}" class="procedure-section mb-8 bg-white p-6 rounded-lg border border-gray-200">
1045
+ <div class="flex justify-between items-start mb-4">
1046
+ <h3 class="text-xl font-semibold text-gray-800">
1047
+ <span class="bg-blue-100 text-blue-800 px-3 py-1 rounded-full mr-3 font-bold">${index + 1}</span>
1048
+ ${proc.title}
1049
+ </h3>
1050
+ <span class="tag bg-${proc.type === 'safety' ? 'red' : proc.type === 'quality' ? 'blue' : proc.type === 'maintenance' ? 'yellow' : proc.type === 'operational' ? 'green' : proc.type === 'training' ? 'purple' : 'gray'}-100 text-${proc.type === 'safety' ? 'red' : proc.type === 'quality' ? 'blue' : proc.type === 'maintenance' ? 'yellow' : proc.type === 'operational' ? 'green' : proc.type === 'training' ? 'purple' : 'gray'}-800">
1051
+ ${capitalizeFirstLetter(proc.type)}
1052
+ </span>
1053
+ </div>
1054
+ <p class="text-gray-700 leading-relaxed mb-4">${proc.desc}</p>
1055
+ `;
1056
+
1057
+ // Add additional fields if they exist
1058
+ if (timeFields.length > 0 || equipmentFields.length > 0 || materialFields.length > 0 || personnelFields.length > 0) {
1059
+ content += `<div class="additional-fields mb-4 grid grid-cols-1 md:grid-cols-2 gap-3">`;
1060
+
1061
+ if (timeFields.length > 0) {
1062
+ content += `<div><strong><i class="fas fa-clock mr-1 text-blue-500"></i> Estimated Time:</strong> ${timeFields[0].value}</div>`;
1063
+ }
1064
+
1065
+ if (equipmentFields.length > 0) {
1066
+ content += `<div><strong><i class="fas fa-tools mr-1 text-green-500"></i> Required Equipment:</strong> ${equipmentFields.map(f => f.value).join(', ')}</div>`;
1067
+ }
1068
+
1069
+ if (materialFields.length > 0) {
1070
+ content += `<div><strong><i class="fas fa-box mr-1 text-purple-500"></i> Required Materials:</strong> ${materialFields.map(f => f.value).join(', ')}</div>`;
1071
+ }
1072
+
1073
+ if (personnelFields.length > 0) {
1074
+ content += `<div><strong><i class="fas fa-users mr-1 text-orange-500"></i> Required Personnel:</strong> ${personnelFields.map(f => f.value).join(', ')}</div>`;
1075
+ }
1076
+
1077
+ content += `</div>`;
1078
+ }
1079
+
1080
+ // Add image if it exists
1081
+ if (proc.imageData) {
1082
+ content += `
1083
+ <div class="procedure-image mt-4 text-center">
1084
+ <img src="${proc.imageData.src}" alt="${proc.title}" class="max-w-full h-auto rounded-lg border mx-auto" style="max-height: 300px;">
1085
+ <p class="text-xs text-gray-500 mt-1">${proc.imageData.filename}</p>
1086
+ </div>
1087
+ `;
1088
+ }
1089
+
1090
+ content += `
1091
+ <div class="mt-4 text-xs text-gray-500 border-t pt-2">
1092
+ <strong>Procedure ID:</strong> PROC-${String(proc.id).slice(-6).toUpperCase()} |
1093
+ <strong>Created:</strong> ${proc.createdAt.toLocaleDateString()} |
1094
+ <strong>Type:</strong> ${capitalizeFirstLetter(proc.type)}
1095
+ </div>
1096
+ </div>
1097
+ `;
1098
+ });
1099
+
1100
+ // Approvals section
1101
+ if (approvers.length > 0) {
1102
+ content += `
1103
+ <div id="approvals" class="approvals-section mb-8">
1104
+ <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
1105
+ <i class="fas fa-check-circle mr-3 text-green-500"></i>
1106
+ Approval Signatures
1107
+ </h2>
1108
+ <div class="grid grid-cols-1 gap-6">
1109
+ `;
1110
+
1111
+ approvers.forEach((approver, index) => {
1112
+ content += `
1113
+ <div class="bg-white p-6 rounded-lg border border-gray-200">
1114
+ <div class="flex justify-between items-center mb-4">
1115
+ <h3 class="text-lg font-semibold text-gray-800">${approver.position}</h3>
1116
+ <span class="tag bg-blue-100 text-blue-800">Required</span>
1117
+ </div>
1118
+ <div class="grid grid-cols-1 md:grid-cols-5 gap-4">
1119
+ <div class="md:col-span-2">
1120
+ <div class="h-24 border-2 border-dashed border-gray-300 mb-2 flex items-center justify-center">
1121
+ <span class="text-gray-400 text-sm">Signature Area</span>
1122
+ </div>
1123
+ <p class="text-xs text-center text-gray-500">Signature</p>
1124
+ </div>
1125
+ <div class="md:col-span-3">
1126
+ <p><strong>Name:</strong> ${approver.name}</p>
1127
+ <p><strong>Position:</strong> ${approver.position}</p>
1128
+ <p class="mt-2"><strong>Date:</strong> __ / __ / ____</p>
1129
+ <p class="mt-2"><strong>Status:</strong> <span class="approval-badge bg-yellow-100 text-yellow-800">PENDING</span></p>
1130
+ </div>
1131
+ </div>
1132
+ </div>
1133
+ `;
1134
+ });
1135
+
1136
+ content += `
1137
+ </div>
1138
+ <p class="text-xs text-gray-500 mt-4">
1139
+ <i class="fas fa-info-circle mr-1"></i>
1140
+ This document requires approval from all designated approvers before implementation.
1141
+ All electronic signatures are considered valid and legally binding.
1142
+ </p>
1143
+ </div>
1144
+ `;
1145
+ }
1146
+
1147
+ // Appendices
1148
+ content += `
1149
+ <!-- Appendices -->
1150
+ <div class="appendices mt-12 pt-8 border-t">
1151
+ <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
1152
+ <i class="fas fa-paperclip mr-3 text-purple-500"></i>
1153
+ Appendices
1154
+ </h2>
1155
+ <div class="bg-gray-50 p-5 rounded-lg">
1156
+ <p class="text-gray-700 mb-3">
1157
+ <i class="fas fa-info-circle mr-2 text-blue-500"></i>
1158
+ This document was automatically generated using the Document Procedure Creator on ${dateString}.
1159
+ </p>
1160
+ <p class="text-gray-700 mb-3">
1161
+ <i class="fas fa-check-circle mr-2 text-green-500"></i>
1162
+ Total procedures included: ${currentDocument.length}. This document has been verified for completeness.
1163
+ </p>
1164
+ <p class="text-gray-700">
1165
+ <i class="fas fa-sync mr-2 text-purple-500"></i>
1166
+ This is a digital document and may be updated without notification. Always refer to the latest version in the document management system.
1167
+ </p>
1168
+ </div>
1169
+ </div>
1170
+ `;
1171
+
1172
+ // Document control information
1173
+ content += `
1174
+ <!-- Document Control -->
1175
+ <div class="document-control mt-12 pt-8 border-t">
1176
+ <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center">
1177
+ <i class="fas fa-shield-alt mr-3 text-indigo-500"></i>
1178
+ Document Control
1179
+ </h2>
1180
+ <div class="bg-white p-5 rounded-lg border">
1181
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6 text-sm">
1182
+ <div>
1183
+ <h3 class="font-semibold mb-2 text-gray-800">Version History</h3>
1184
+ <table class="w-full">
1185
+ <tr>
1186
+ <td class="py-1"><strong>Version</strong></td>
1187
+ <td class="py-1"><strong>Date</strong></td>
1188
+ <td class="py-1"><strong>Changes</strong></td>
1189
+ </tr>
1190
+ <tr>
1191
+ <td class="py-1">1.0</td>
1192
+ <td class="py-1">${dateString}</td>
1193
+ <td class="py-1">Initial Release</td>
1194
+ </tr>
1195
+ </table>
1196
+ </div>
1197
+ <div>
1198
+ <h3 class="font-semibold mb-2 text-gray-800">Distribution</h3>
1199
+ <p>Available to all authorized personnel through the document management system.</p>
1200
+ </div>
1201
+ <div>
1202
+ <h3 class="font-semibold mb-2 text-gray-800">Storage</h3>
1203
+ <p>Digital copy stored in the central document repository with controlled access.</p>
1204
+ </div>
1205
+ </div>
1206
+ </div>
1207
+ </div>
1208
+ `;
1209
+
1210
+ // Close the document
1211
+ content += `
1212
+ </div>
1213
+ </div>
1214
+ `;
1215
+
1216
+ compiledPreview.innerHTML = content;
1217
+ documentCompiled = true;
1218
+ updateDocumentStats();
1219
+ showToast('Document compiled successfully!');
1220
+ });
1221
+
1222
+ // Update document statistics
1223
+ function updateDocumentStats() {
1224
+ procCount.textContent = currentDocument.length;
1225
+ // Estimate pages based on procedures and approvers
1226
+ let pageEstimate = 2; // Cover + TOC
1227
+ pageEstimate += Math.ceil(currentDocument.length * 0.7); // Procedures
1228
+ if (approvers.length > 0) pageEstimate += 1; // Approvals
1229
+ if (currentDocument.some(p => p.imageData)) pageEstimate += 1; // Extra page for images
1230
+
1231
+ estPages.textContent = Math.max(2, pageEstimate);
1232
+ docId.textContent = documentId;
1233
+
1234
+ if (documentCompiled) {
1235
+ const date = new Date();
1236
+ lastCompiled.textContent = date.toLocaleTimeString();
1237
+ }
1238
+ }
1239
+
1240
+ // Initialize document ID
1241
+ docId.textContent = documentId;
1242
+
1243
+ // Initialize approvers list
1244
+ renderApprovers();
1245
+
1246
+ // Download document as Word
1247
+ downloadDocBtn.addEventListener('click', () => {
1248
+ if (!documentCompiled) {
1249
+ showToast('Please compile the document first', 'error');
1250
+ return;
1251
+ }
1252
+
1253
+ // Using docx library to create Word document
1254
+ const { Document, Paragraph, TextRun, HeadingLevel, AlignmentType, Table, TableRow, TableCell, WidthType, BorderStyle, ImageRun } = docx;
1255
+
1256
+ // Function to create image paragraph if image exists
1257
+ async function createImageParagraph(proc) {
1258
+ if (!proc.imageData) return [];
1259
+
1260
+ try {
1261
+ const response = await fetch(proc.imageData.src);
1262
+ const blob = await response.blob();
1263
+ const byteString = await blobToBase64(blob);
1264
+ const image = new ImageRun({
1265
+ data: byteString.split(',')[1],
1266
+ transformation: {
1267
+ width: 400,
1268
+ height: 250
1269
+ }
1270
+ });
1271
+ return [new Paragraph({ children: [image], alignment: AlignmentType.CENTER })];
1272
+ } catch (error) {
1273
+ console.error("Error processing image:", error);
1274
+ return [new Paragraph({ text: "[Image: " + proc.imageData.filename + "]" })];
1275
+ }
1276
+ }
1277
+
1278
+ // Helper function to convert blob to base64
1279
+ async function blobToBase64(blob) {
1280
+ return new Promise((resolve, reject) => {
1281
+ const reader = new FileReader();
1282
+ reader.onload = () => resolve(reader.result);
1283
+ reader.onerror = reject;
1284
+ reader.readAsDataURL(blob);
1285
+ });
1286
+ }
1287
+
1288
+ // Create the document
1289
+ const doc = new Document({
1290
+ sections: [{
1291
+ properties: {},
1292
+ children: [
1293
+ // Cover Page
1294
+ new Paragraph({
1295
+ text: "STANDARD OPERATING PROCEDURE",
1296
+ heading: HeadingLevel.HEADING_1,
1297
+ alignment: AlignmentType.CENTER,
1298
+ thematicBreak: true,
1299
+ }),
1300
+ new Paragraph({
1301
+ text: documentId,
1302
+ heading: HeadingLevel.HEADING_3,
1303
+ alignment: AlignmentType.CENTER,
1304
+ }),
1305
+ new Paragraph({
1306
+ text: "Document Procedure Creator",
1307
+ heading: HeadingLevel.HEADING_3,
1308
+ alignment: AlignmentType.CENTER,
1309
+ }),
1310
+ new Paragraph({
1311
+ text: `Generated on ${new Date().toLocaleDateString()}`,
1312
+ alignment: AlignmentType.CENTER,
1313
+ italics: true,
1314
+ }),
1315
+ new Paragraph({
1316
+ text: " ",
1317
+ }),
1318
+
1319
+ // Document Information
1320
+ new Paragraph({
1321
+ text: "Document Information",
1322
+ heading: HeadingLevel.HEADING_2,
1323
+ }),
1324
+ new Table({
1325
+ rows: [
1326
+ new TableRow({
1327
+ children: [
1328
+ new TableCell({
1329
+ children: [new Paragraph({ text: "Document ID", bold: true })],
1330
+ shading: { fill: "f0f0f0" },
1331
+ }),
1332
+ new TableCell({
1333
+ children: [new Paragraph(documentId)],
1334
+ }),
1335
+ ],
1336
+ }),
1337
+ new TableRow({
1338
+ children: [
1339
+ new TableCell({
1340
+ children: [new Paragraph({ text: "Version", bold: true })],
1341
+ shading: { fill: "f0f0f0" },
1342
+ }),
1343
+ new TableCell({
1344
+ children: [new Paragraph("1.0")],
1345
+ }),
1346
+ ],
1347
+ }),
1348
+ new TableRow({
1349
+ children: [
1350
+ new TableCell({
1351
+ children: [new Paragraph({ text: "Effective Date", bold: true })],
1352
+ shading: { fill: "f0f0f0" },
1353
+ }),
1354
+ new TableCell({
1355
+ children: [new Paragraph(new Date().toLocaleDateString())],
1356
+ }),
1357
+ ],
1358
+ }),
1359
+ new TableRow({
1360
+ children: [
1361
+ new TableCell({
1362
+ children: [new Paragraph({ text: "Review Period", bold: true })],
1363
+ shading: { fill: "f0f0f0" },
1364
+ }),
1365
+ new TableCell({
1366
+ children: [new Paragraph("12 months")],
1367
+ }),
1368
+ ],
1369
+ }),
1370
+ new TableRow({
1371
+ children: [
1372
+ new TableCell({
1373
+ children: [new Paragraph({ text: "Prepared By", bold: true })],
1374
+ shading: { fill: "f0f0f0" },
1375
+ }),
1376
+ new TableCell({
1377
+ children: [new Paragraph("Digital System")],
1378
+ }),
1379
+ ],
1380
+ }),
1381
+ new TableRow({
1382
+ children: [
1383
+ new TableCell({
1384
+ children: [new Paragraph({ text: "Total Procedures", bold: true })],
1385
+ shading: { fill: "f0f0f0" },
1386
+ }),
1387
+ new TableCell({
1388
+ children: [new Paragraph(currentDocument.length.toString())],
1389
+ }),
1390
+ ],
1391
+ }),
1392
+ ],
1393
+ }),
1394
+ new Paragraph({
1395
+ text: " ",
1396
+ }),
1397
+
1398
+ // Table of Contents
1399
+ new Paragraph({
1400
+ text: "Table of Contents",
1401
+ heading: HeadingLevel.HEADING_2,
1402
+ }),
1403
+ ...currentDocument.map((proc, index) =>
1404
+ new Paragraph({
1405
+ text: `${index + 1}. ${proc.title}`,
1406
+ bullet: { level: 0 },
1407
+ })
1408
+ ),
1409
+ ...(approvers.length > 0 ? [
1410
+ new Paragraph({
1411
+ text: "Approvals",
1412
+ bullet: { level: 0 },
1413
+ })
1414
+ ] : []),
1415
+ new Paragraph({
1416
+ text: " ",
1417
+ }),
1418
+
1419
+ // Procedures
1420
+ new Paragraph({
1421
+ text: "Procedures",
1422
+ heading: HeadingLevel.HEADING_2,
1423
+ }),
1424
+ ].concat(
1425
+ // Add procedures with additional fields
1426
+ currentDocument.flatMap(async (proc, index) => {
1427
+ const elements = [
1428
+ new Paragraph({
1429
+ text: `${index + 1}. ${proc.title}`,
1430
+ heading: HeadingLevel.HEADING_3,
1431
+ }),
1432
+ new Paragraph({
1433
+ text: proc.desc,
1434
+ }),
1435
+ ];
1436
+
1437
+ // Add additional fields
1438
+ const timeFields = proc.additionalFields.filter(f => f.type === 'time');
1439
+ const equipmentFields = proc.additionalFields.filter(f => f.type === 'equipment');
1440
+ const materialFields = proc.additionalFields.filter(f => f.type === 'material');
1441
+ const personnelFields = proc.additionalFields.filter(f => f.type === 'personnel');
1442
+
1443
+ if (timeFields.length > 0 || equipmentFields.length > 0 || materialFields.length > 0 || personnelFields.length > 0) {
1444
+ const tableRows = [];
1445
+
1446
+ if (timeFields.length > 0) {
1447
+ tableRows.push(
1448
+ new TableRow({
1449
+ children: [
1450
+ new TableCell({
1451
+ children: [new Paragraph({ text: "Estimated Time", bold: true })],
1452
+ width: { size: 30, type: WidthType.PERCENTAGE },
1453
+ shading: { fill: "f0f0f0" },
1454
+ }),
1455
+ new TableCell({
1456
+ children: [new Paragraph(timeFields[0].value)],
1457
+ width: { size: 70, type: WidthType.PERCENTAGE },
1458
+ }),
1459
+ ],
1460
+ })
1461
+ );
1462
+ }
1463
+
1464
+ if (equipmentFields.length > 0) {
1465
+ tableRows.push(
1466
+ new TableRow({
1467
+ children: [
1468
+ new TableCell({
1469
+ children: [new Paragraph({ text: "Required Equipment", bold: true })],
1470
+ shading: { fill: "f0f0f0" },
1471
+ }),
1472
+ new TableCell({
1473
+ children: [new Paragraph(equipmentFields.map(f => f.value).join(', '))],
1474
+ }),
1475
+ ],
1476
+ })
1477
+ );
1478
+ }
1479
+
1480
+ if (materialFields.length > 0) {
1481
+ tableRows.push(
1482
+ new TableRow({
1483
+ children: [
1484
+ new TableCell({
1485
+ children: [new Paragraph({ text: "Required Materials", bold: true })],
1486
+ shading: { fill: "f0f0f0" },
1487
+ }),
1488
+ new TableCell({
1489
+ children: [new Paragraph(materialFields.map(f => f.value).join(', '))],
1490
+ }),
1491
+ ],
1492
+ })
1493
+ );
1494
+ }
1495
+
1496
+ if (personnelFields.length > 0) {
1497
+ tableRows.push(
1498
+ new TableRow({
1499
+ children: [
1500
+ new TableCell({
1501
+ children: [new Paragraph({ text: "Required Personnel", bold: true })],
1502
+ shading: { fill: "f0f0f0" },
1503
+ }),
1504
+ new TableCell({
1505
+ children: [new Paragraph(personnelFields.map(f => f.value).join(', '))],
1506
+ }),
1507
+ ],
1508
+ })
1509
+ );
1510
+ }
1511
+
1512
+ elements.push(
1513
+ new Table({
1514
+ rows: tableRows,
1515
+ columnWidths: [30, 70],
1516
+ })
1517
+ );
1518
+ }
1519
+
1520
+ // Add image if exists
1521
+ if (proc.imageData) {
1522
+ const imageElements = await createImageParagraph(proc);
1523
+ elements.push(...imageElements);
1524
+ }
1525
+
1526
+ // Add procedure metadata
1527
+ elements.push(
1528
+ new Paragraph({
1529
+ text: `Procedure ID: PROC-${String(proc.id).slice(-6).toUpperCase()} | Created: ${proc.createdAt.toLocaleDateString()} | Type: ${capitalizeFirstLetter(proc.type)}`,
1530
+ italics: true,
1531
+ style: "Footer",
1532
+ }),
1533
+ new Paragraph({
1534
+ text: " ",
1535
+ })
1536
+ );
1537
+
1538
+ return elements;
1539
+ }).flat()
1540
+ ).concat(
1541
+ // Add approvals section
1542
+ (approvers.length > 0) ? [
1543
+ new Paragraph({
1544
+ text: "Approval Signatures",
1545
+ heading: HeadingLevel.HEADING_2,
1546
+ }),
1547
+ new Table({
1548
+ rows: [
1549
+ new TableRow({
1550
+ children: [
1551
+ new TableCell({ children: [new Paragraph({ text: "Approver", bold: true })] }),
1552
+ new TableCell({ children: [new Paragraph({ text: "Position", bold: true })] }),
1553
+ new TableCell({ children: [new Paragraph({ text: "Signature", bold: true })] }),
1554
+ new TableCell({ children: [new Paragraph({ text: "Date", bold: true })] }),
1555
+ new TableCell({ children: [new Paragraph({ text: "Status", bold: true })] }),
1556
+ ],
1557
+ }),
1558
+ ...approvers.map(approver =>
1559
+ new TableRow({
1560
+ children: [
1561
+ new TableCell({ children: [new Paragraph(approver.name)] }),
1562
+ new TableCell({ children: [new Paragraph(approver.position)] }),
1563
+ new TableCell({ children: [new Paragraph("_________________")] }),
1564
+ new TableCell({ children: [new Paragraph("____/____/____")] }),
1565
+ new TableCell({ children: [new Paragraph("PENDING")] }),
1566
+ ],
1567
+ })
1568
+ ),
1569
+ ],
1570
+ }),
1571
+ new Paragraph({ text: " " }),
1572
+ ] : []
1573
+ ).concat([
1574
+ // Appendices
1575
+ new Paragraph({
1576
+ text: "Appendices",
1577
+ heading: HeadingLevel.HEADING_2,
1578
+ }),
1579
+ new Paragraph({
1580
+ text: `This document was automatically generated using the Document Procedure Creator on ${new Date().toLocaleDateString()}. Total procedures included: ${currentDocument.length}.`,
1581
+ }),
1582
+ new Paragraph({
1583
+ text: "Always refer to the latest version in the document management system.",
1584
+ italics: true,
1585
+ }),
1586
+ new Paragraph({ text: " " }),
1587
+
1588
+ // Document Control
1589
+ new Paragraph({
1590
+ text: "Document Control",
1591
+ heading: HeadingLevel.HEADING_2,
1592
+ }),
1593
+ new Table({
1594
+ rows: [
1595
+ new TableRow({
1596
+ children: [
1597
+ new TableCell({
1598
+ children: [new Paragraph({ text: "Version History", bold: true })],
1599
+ gridSpan: 3,
1600
+ shading: { fill: "f0f0f0" },
1601
+ }),
1602
+ ],
1603
+ }),
1604
+ new TableRow({
1605
+ children: [
1606
+ new TableCell({ children: [new Paragraph({ text: "Version", bold: true })] }),
1607
+ new TableCell({ children: [new Paragraph({ text: "Date", bold: true })] }),
1608
+ new TableCell({ children: [new Paragraph({ text: "Changes", bold: true })] }),
1609
+ ],
1610
+ }),
1611
+ new TableRow({
1612
+ children: [
1613
+ new TableCell({ children: [new Paragraph("1.0")] }),
1614
+ new TableCell({ children: [new Paragraph(new Date().toLocaleDateString())] }),
1615
+ new TableCell({ children: [new Paragraph("Initial Release")] }),
1616
+ ],
1617
+ }),
1618
+ new TableRow({
1619
+ children: [
1620
+ new TableCell({
1621
+ children: [new Paragraph({ text: "Distribution", bold: true })],
1622
+ shading: { fill: "f0f0f0" },
1623
+ }),
1624
+ new TableCell({
1625
+ children: [new Paragraph("Available to all authorized personnel through the document management system.")],
1626
+ gridSpan: 2,
1627
+ }),
1628
+ ],
1629
+ }),
1630
+ new TableRow({
1631
+ children: [
1632
+ new TableCell({
1633
+ children: [new Paragraph({ text: "Storage", bold: true })],
1634
+ shading: { fill: "f0f0f0" },
1635
+ }),
1636
+ new TableCell({
1637
+ children: [new Paragraph("Digital copy stored in the central document repository with controlled access.")],
1638
+ gridSpan: 2,
1639
+ }),
1640
+ ],
1641
+ }),
1642
+ ],
1643
+ }),
1644
+ new Paragraph({ text: " ", }),
1645
+ ])
1646
+ }]
1647
+ });
1648
+
1649
+ // Create and download the file
1650
+ try {
1651
+ const blob = await docx.Packer.toBlob(doc);
1652
+ saveAs(blob, `SOP_Document_${documentId}_${Date.now()}.docx`);
1653
+ showToast('Document downloaded as Word file!');
1654
+ } catch (err) {
1655
+ showToast('Error generating document: ' + err.message, 'error');
1656
+ }
1657
+ });
1658
+
1659
+ // Print document
1660
+ printDocBtn.addEventListener('click', () => {
1661
+ if (!documentCompiled) {
1662
+ showToast('Please compile the document first', 'error');
1663
+ return;
1664
+ }
1665
+
1666
+ const printWindow = window.open('', '', 'height=800,width=1000');
1667
+ printWindow.document.write(`
1668
+ <html>
1669
+ <head>
1670
+ <title>Print SOP Document</title>
1671
+ <script src="https://cdn.tailwindcss.com"></script>
1672
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet" />
1673
+ <style>
1674
+ @media print {
1675
+ @page { margin: 0.5in; size: letter; }
1676
+ body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
1677
+ .no-print { display: none !important; }
1678
+ .bg-gradient-to-b, .from-blue-900, .to-blue-700 {
1679
+ background: #1e3a8a !important;
1680
+ background-image: none !important;
1681
+ }
1682
+ .cover-page {
1683
+ page-break-after: avoid;
1684
+ }
1685
+ .procedure-section {
1686
+ page-break-inside: avoid;
1687
+ }
1688
+ img {
1689
+ max-height: 5in;
1690
+ width: auto;
1691
+ height: auto;
1692
+ }
1693
+ }
1694
+ body {
1695
+ font-family: Arial, sans-serif;
1696
+ }
1697
+ </style>
1698
+ </head>
1699
+ <body class="p-8 bg-white text-gray-800">
1700
+ ${compiledPreview.innerHTML}
1701
+ <div class="no-print mt-8 text-center text-gray-500 text-sm">
1702
+ <p>Generated by Document Procedure Creator — ${new Date().toLocaleString()}</p>
1703
+ </div>
1704
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-qwensite.hf.space/logo.svg" alt="qwensite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-qwensite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >QwenSite</a> - 🧬 <a href="https://enzostvs-qwensite.hf.space?remix=alterzick/sop-creator-v1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1705
+ </html>
prompts.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ buatkan document procedure creator dengan metode ada fitur inputing procedure sebagai database procedure , ada penyusunan prosedure yang di lakukan seperti lego dengan penyusunan prosedure berdasarkan input database, setelah itu ada fitur compile semua data dengan menerapkan bagian cover document , daftar isi document , isi dokument dan lampiran document , setelah di buat document makan document dapat di download dapat bentuk word document . untuk di proses selanjutnya dan ada fitur approval untuk approval document yang sudah di buat sehingga saat di print sudah ada approval document dan di import oleh user
2
+ untuk prosedure database pastikan setelah di save muncul di available procedure, dan procedure builder dapat menyusun mana yang lebih dahulu dan terakhir , approval workflow agar ada title dan position untuk approval , di prosedure database dapat menyimpan gambar dan ada picklist apa yang di tambahkan selain text, buatkan database untuk set document yang standard