oceanhelm 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/oceanhelm.es.js +1802 -934
- package/dist/oceanhelm.es.js.map +1 -1
- package/dist/oceanhelm.umd.js +1 -1
- package/dist/oceanhelm.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActivityLogs.vue +1 -2
- package/src/components/CrewManagement.vue +2 -3
- package/src/components/OceanHelmMaintenance.vue +19 -9
- package/src/components/RequisitionSystem.vue +1727 -0
- package/src/index.js +4 -1
|
@@ -0,0 +1,1727 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="s-container container">
|
|
3
|
+
<div class="header">
|
|
4
|
+
<h1>OceanHelm Requisition System</h1>
|
|
5
|
+
<p>Streamlined Material Request & Ordering Process</p>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<!-- Navigation Tabs -->
|
|
9
|
+
<div class="nav-tabs">
|
|
10
|
+
<button v-for="tab in visibleTabs" :key="tab.name" :class="['nav-tab', { active: activeTab === tab.name }]"
|
|
11
|
+
@click="setActiveTab(tab.name)">
|
|
12
|
+
{{ tab.label }}
|
|
13
|
+
</button>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- New Requisition Tab -->
|
|
17
|
+
<div v-if="activeTab === 'new-requisition'" class="tab-content active">
|
|
18
|
+
<form @submit.prevent="handleSubmitRequisition">
|
|
19
|
+
<div class="form-grid">
|
|
20
|
+
<div class="form-group">
|
|
21
|
+
<label>Requestor Name *</label>
|
|
22
|
+
<input type="text" :value="userProfile.full_name" readonly required class="form-control" />
|
|
23
|
+
</div>
|
|
24
|
+
<div class="form-group">
|
|
25
|
+
<label>Department *</label>
|
|
26
|
+
<select v-model="form.department" required>
|
|
27
|
+
<option value="">Select Department</option>
|
|
28
|
+
<option v-for="dept in departments" :key="dept" :value="dept">{{ dept }}</option>
|
|
29
|
+
</select>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="form-group">
|
|
32
|
+
<label>Vessel *</label>
|
|
33
|
+
<select v-model="form.project" class="form-control" required>
|
|
34
|
+
<option disabled value="">Select a vessel</option>
|
|
35
|
+
<option v-for="v in vessels" :key="v.id" :value="v.name">
|
|
36
|
+
{{ v.name }}
|
|
37
|
+
</option>
|
|
38
|
+
</select>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="form-group">
|
|
41
|
+
<label>Date Needed *</label>
|
|
42
|
+
<input type="date" v-model="form.neededDate" required />
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="form-group">
|
|
47
|
+
<label>Business Justification *</label>
|
|
48
|
+
<textarea v-model="form.justification" placeholder="Explain why these materials are needed..."
|
|
49
|
+
required></textarea>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="items-section">
|
|
53
|
+
<div class="items-header">
|
|
54
|
+
<h3>Requested Items</h3>
|
|
55
|
+
<button type="button" class="add-item-btn" @click="addItem">+ Add Item</button>
|
|
56
|
+
</div>
|
|
57
|
+
<div>
|
|
58
|
+
<div v-for="(item, index) in form.items" :key="index" class="item-row">
|
|
59
|
+
<div class="form-group">
|
|
60
|
+
<label>Item Code *</label>
|
|
61
|
+
<input type="text" v-model="item.id" required />
|
|
62
|
+
</div>
|
|
63
|
+
<div class="form-group">
|
|
64
|
+
<label>Item Description *</label>
|
|
65
|
+
<input type="text" v-model="item.desc" required />
|
|
66
|
+
</div>
|
|
67
|
+
<div class="form-group">
|
|
68
|
+
<label>Quantity *</label>
|
|
69
|
+
<input type="number" v-model.number="item.qty" min="1" required />
|
|
70
|
+
</div>
|
|
71
|
+
<div class="form-group">
|
|
72
|
+
<label>Unit *</label>
|
|
73
|
+
<select v-model="item.unit" required>
|
|
74
|
+
<option v-for="unit in units" :key="unit" :value="unit">{{ unit }}</option>
|
|
75
|
+
</select>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="form-group">
|
|
78
|
+
<label>Est. Unit Cost *</label>
|
|
79
|
+
<input type="number" v-model.number="item.cost" step="0.01" placeholder="0.00" required />
|
|
80
|
+
</div>
|
|
81
|
+
<button type="button" class="remove-item-btn" @click="removeItem(index)">Remove</button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="action-buttons">
|
|
87
|
+
<button type="submit" class="btn btn-primary-req">Submit Requisition</button>
|
|
88
|
+
</div>
|
|
89
|
+
</form>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<!-- All Requisitions Tab -->
|
|
93
|
+
<div v-if="activeTab === 'all-requisitions'" class="tab-content active">
|
|
94
|
+
<div class="requisition-list">
|
|
95
|
+
<div v-for="req in requisitions" :key="req.id" class="requisition-card">
|
|
96
|
+
<div class="requisition-header">
|
|
97
|
+
<div class="requisition-id">{{ req.id }}</div>
|
|
98
|
+
<div :class="['status-badge', `status-${req.status}`]">{{ req.status }}</div>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="requisition-details">
|
|
101
|
+
<div class="detail-item" v-for="field in getRequisitionFields(req)" :key="field.label">
|
|
102
|
+
<div class="detail-label">{{ field.label }}</div>
|
|
103
|
+
<div class="detail-value">{{ field.value(req) }}</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<div v-if="req.status === 'po-created' || req.status === 'delivered'"
|
|
107
|
+
class="form-group comments-section">
|
|
108
|
+
<button @click="handleOpenPO(req.id)" class="add-item-btn comments-section">
|
|
109
|
+
Print PO
|
|
110
|
+
</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<!-- My Requisitions Tab -->
|
|
117
|
+
<div v-if="activeTab === 'my-requisitions'" class="tab-content active">
|
|
118
|
+
<div class="requisition-list">
|
|
119
|
+
<div v-for="req in myRequisitions" :key="req.id" class="requisition-card">
|
|
120
|
+
<div class="requisition-header">
|
|
121
|
+
<div class="requisition-id">{{ req.id }}</div>
|
|
122
|
+
<div :class="['status-badge', `status-${req.status}`]">{{ req.status }}</div>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="requisition-details">
|
|
125
|
+
<div class="detail-item" v-for="field in getRequisitionFields(req)" :key="field.label">
|
|
126
|
+
<div class="detail-label">{{ field.label }}</div>
|
|
127
|
+
<div class="detail-value">{{ field.value(req) }}</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
<div v-if="req.status === 'info-requested'" class="form-group comments-section">
|
|
131
|
+
<label class="detail-label">Your Response</label>
|
|
132
|
+
<textarea v-model="infoResponse" class="response-textarea"
|
|
133
|
+
placeholder="Submit more info..."></textarea>
|
|
134
|
+
<button @click="handleSubmitInfoResponse(req)" class="add-item-btn comments-section">
|
|
135
|
+
Submit Info
|
|
136
|
+
</button>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<!-- Pending Approval Tab -->
|
|
143
|
+
<div v-if="activeTab === 'approvals'" class="tab-content active">
|
|
144
|
+
<div style="margin-bottom: 20px;">
|
|
145
|
+
<h3 style="color: #1e40af; margin-bottom: 15px;">Department Supervisor Dashboard</h3>
|
|
146
|
+
<div style="background: #fef3c7; padding: 15px; border-radius: 10px; border-left: 4px solid #f59e0b;">
|
|
147
|
+
<strong>Role:</strong> Department Supervisor - Review requests for accuracy, necessity, and budget
|
|
148
|
+
compliance
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="requisition-list" id="approvalsQueue">
|
|
152
|
+
<div v-for="req in reviewRequisitions" :key="req.id" class="requisition-card">
|
|
153
|
+
<div class="requisition-header">
|
|
154
|
+
<div class="requisition-id">{{ req.id }}</div>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="requisition-details">
|
|
157
|
+
<div class="detail-item" v-for="field in requisitionFields" :key="field.label">
|
|
158
|
+
<div class="detail-label">{{ field.label }}</div>
|
|
159
|
+
<div class="detail-value">{{ field.value(req) }}</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
<!-- Justification -->
|
|
163
|
+
<div class="detail-item">
|
|
164
|
+
<div class="detail-label">Justification</div>
|
|
165
|
+
<div class="detail-value">{{ req.justification || 'N/A' }}</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<!-- Item list -->
|
|
169
|
+
<div class="detail-item">
|
|
170
|
+
<div class="detail-label">Items</div>
|
|
171
|
+
<ul class="item-list">
|
|
172
|
+
<li v-for="(item, index) in req.items" :key="index">
|
|
173
|
+
{{ item.desc }} - {{ item.qty }} {{ item.unit }} @ ₦{{ item.cost.toFixed(2) }} each
|
|
174
|
+
</li>
|
|
175
|
+
</ul>
|
|
176
|
+
</div>
|
|
177
|
+
<button type="button" class="add-item-btn" @click="handleApproveRequisition(req.id)">Approve</button>
|
|
178
|
+
<button type="button" class="marginbox btn-reject"
|
|
179
|
+
@click="handleDeclineRequisition(req.id)">Decline</button>
|
|
180
|
+
<button type="button" class="marginbox btn-request" @click="handleInfoRequisition(req.id)">Request
|
|
181
|
+
Info</button>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<!-- Purchasing Queue Tab -->
|
|
187
|
+
<div v-if="activeTab === 'purchasing'" class="tab-content active">
|
|
188
|
+
<div style="margin-bottom: 20px;">
|
|
189
|
+
<h3 style="color: #1e40af; margin-bottom: 15px;">Purchasing Department Dashboard</h3>
|
|
190
|
+
<div style="background: #dbeafe; padding: 15px; border-radius: 10px; border-left: 4px solid #3b82f6;">
|
|
191
|
+
<strong>Role:</strong> Purchasing Team - Convert approved requisitions to Purchase Orders
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="requisition-list" id="purchasingQueue">
|
|
195
|
+
<div v-for="req in poRequisitions" :key="req.id" class="requisition-card">
|
|
196
|
+
<div class="requisition-header">
|
|
197
|
+
<div class="requisition-id">{{ req.id }}</div>
|
|
198
|
+
</div>
|
|
199
|
+
<div class="requisition-details">
|
|
200
|
+
<div class="detail-item" v-for="field in requisitionFields" :key="field.label">
|
|
201
|
+
<div class="detail-label">{{ field.label }}</div>
|
|
202
|
+
<div class="detail-value">{{ field.value(req) }}</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
<!-- Justification -->
|
|
206
|
+
<div class="detail-item">
|
|
207
|
+
<div class="detail-label">Justification</div>
|
|
208
|
+
<div class="detail-value">{{ req.justification || 'N/A' }}</div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<!-- Item list -->
|
|
212
|
+
<div class="detail-item">
|
|
213
|
+
<div class="detail-label">Items</div>
|
|
214
|
+
<ul class="item-list">
|
|
215
|
+
<li v-for="(item, index) in req.items" :key="index">
|
|
216
|
+
{{ item.desc }} - {{ item.qty }} {{ item.unit }} @ ₦{{ item.cost.toFixed(2) }} each
|
|
217
|
+
</li>
|
|
218
|
+
</ul>
|
|
219
|
+
</div>
|
|
220
|
+
<button type="button" class="add-item-btn" @click="handleCreatePO(req.id)">Create PO</button>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<!-- Receiving Tab -->
|
|
226
|
+
<div v-if="activeTab === 'receiving'" class="tab-content active">
|
|
227
|
+
<div style="margin-bottom: 20px;">
|
|
228
|
+
<h3 style="color: #1e40af; margin-bottom: 15px;">Receiving & Inventory Dashboard</h3>
|
|
229
|
+
<div style="background: #dcfce7; padding: 15px; border-radius: 10px; border-left: 4px solid #10b981;">
|
|
230
|
+
<strong>Role:</strong> Warehouse Team - Process incoming deliveries and update inventory
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
<div class="requisition-list" id="receivingQueue">
|
|
234
|
+
<div v-for="req in awaitingDelivery" :key="req.id" class="requisition-card">
|
|
235
|
+
<div class="requisition-header">
|
|
236
|
+
<div class="requisition-id">{{ req.id }}</div>
|
|
237
|
+
</div>
|
|
238
|
+
<div class="requisition-details">
|
|
239
|
+
<div class="detail-item" v-for="field in requisitionFields" :key="field.label">
|
|
240
|
+
<div class="detail-label">{{ field.label }}</div>
|
|
241
|
+
<div class="detail-value">{{ field.value(req) }}</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
<!-- Justification -->
|
|
245
|
+
<div class="detail-item">
|
|
246
|
+
<div class="detail-label">Justification</div>
|
|
247
|
+
<div class="detail-value">{{ req.justification || 'N/A' }}</div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<!-- Item list -->
|
|
251
|
+
<div class="detail-item">
|
|
252
|
+
<div class="detail-label">Items</div>
|
|
253
|
+
<ul class="item-list">
|
|
254
|
+
<li v-for="(item, index) in req.items" :key="index">
|
|
255
|
+
{{ item.desc }} - {{ item.qty }} {{ item.unit }} @ ₦{{ item.cost.toFixed(2) }} each
|
|
256
|
+
</li>
|
|
257
|
+
</ul>
|
|
258
|
+
</div>
|
|
259
|
+
<button type="button" class="add-item-btn" @click="handleAcceptDelivery(req.id)">Accept
|
|
260
|
+
Delivery</button>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<!-- PO Tab -->
|
|
266
|
+
<div v-if="activeTab === 'po'" class="tab-content active">
|
|
267
|
+
<div class="po-content" id="po-content">
|
|
268
|
+
<div class="po-header">
|
|
269
|
+
<div class="company-info">
|
|
270
|
+
<h3>Vendor Information</h3>
|
|
271
|
+
<div class="info-row">
|
|
272
|
+
<span class="info-label">Company:</span>
|
|
273
|
+
<span class="info-value">{{ vendorInfo.company || poDetails.vendorInfo?.company }} </span>
|
|
274
|
+
</div>
|
|
275
|
+
<div class="info-row">
|
|
276
|
+
<span class="info-label">Contact:</span>
|
|
277
|
+
<span class="info-value">{{ vendorInfo.contact || poDetails.vendorInfo?.contact }}</span>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="info-row">
|
|
280
|
+
<span class="info-label">Email:</span>
|
|
281
|
+
<span class="info-value">{{ vendorInfo.email || poDetails.vendorInfo?.email }}</span>
|
|
282
|
+
</div>
|
|
283
|
+
<div class="info-row">
|
|
284
|
+
<span class="info-label">Phone:</span>
|
|
285
|
+
<span class="info-value">{{ vendorInfo.phone || poDetails.vendorInfo?.phone }}</span>
|
|
286
|
+
</div>
|
|
287
|
+
<div class="info-row">
|
|
288
|
+
<span class="info-label">Address:</span>
|
|
289
|
+
<span class="info-value">{{ vendorInfo.address || poDetails.vendorInfo?.address }}</span>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
|
|
293
|
+
<div class="company-info">
|
|
294
|
+
<h3>Purchase Order Details</h3>
|
|
295
|
+
<div class="info-row">
|
|
296
|
+
<span class="info-label">PO Number:</span>
|
|
297
|
+
<span class="info-value">{{ poDetails.id }}</span>
|
|
298
|
+
</div>
|
|
299
|
+
<div class="info-row">
|
|
300
|
+
<span class="info-label">Date:</span>
|
|
301
|
+
<span class="info-value">{{ vendorInfo.poDate || poDetails.vendorInfo?.poDate }}</span>
|
|
302
|
+
</div>
|
|
303
|
+
<div class="info-row">
|
|
304
|
+
<span class="info-label">Requested By:</span>
|
|
305
|
+
<span class="info-value">{{ vendorInfo.poApproved || poDetails.vendorInfo?.poApproved }}</span>
|
|
306
|
+
</div>
|
|
307
|
+
<div class="info-row">
|
|
308
|
+
<span class="info-label">Department:</span>
|
|
309
|
+
<span class="info-value">PO-{{ poDetails.department }}</span>
|
|
310
|
+
</div>
|
|
311
|
+
<div class="info-row">
|
|
312
|
+
<span class="info-label">Delivery Date:</span>
|
|
313
|
+
<span class="info-value">{{ poDetails.neededDate }}</span>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<div class="items-section">
|
|
319
|
+
<h2 class="section-title">Order Items</h2>
|
|
320
|
+
<table class="items-table">
|
|
321
|
+
<thead>
|
|
322
|
+
<tr>
|
|
323
|
+
<th>Item #</th>
|
|
324
|
+
<th>Description</th>
|
|
325
|
+
<th>Quantity</th>
|
|
326
|
+
<th>Unit Price</th>
|
|
327
|
+
<th>Total</th>
|
|
328
|
+
<th v-if="!isPrinting">Actions</th>
|
|
329
|
+
</tr>
|
|
330
|
+
</thead>
|
|
331
|
+
<tbody>
|
|
332
|
+
<tr v-for="(item, index) in poDetails.items || []" :key="index">
|
|
333
|
+
<td>{{ item.itemNumber }}</td>
|
|
334
|
+
<td>{{ item.desc }}</td>
|
|
335
|
+
<td>{{ item.qty }}</td>
|
|
336
|
+
<td>
|
|
337
|
+
<span v-if="!item.editing">
|
|
338
|
+
${{ item.cost.toFixed(2) }}
|
|
339
|
+
<span v-if="item.cost !== item.unitPrice" class="price-change-indicator">!</span>
|
|
340
|
+
</span>
|
|
341
|
+
<input v-else v-model.number="item.tempPrice" type="number" step="0.01"
|
|
342
|
+
class="price-input" :class="{ 'price-changed': item.cost !== item.tempPrice }">
|
|
343
|
+
</td>
|
|
344
|
+
<td>${{ (item.unitPrice * item.qty).toFixed(2) }}</td>
|
|
345
|
+
<td v-if="!isPrinting">
|
|
346
|
+
<button v-if="!item.editing" @click="startEdit(index)" class="edit-btn">
|
|
347
|
+
Edit Price
|
|
348
|
+
</button>
|
|
349
|
+
<div v-else>
|
|
350
|
+
<button @click="savePrice(index)" class="save-btn">Save</button>
|
|
351
|
+
<button @click="cancelEdit(index)" class="cancel-btn">Cancel</button>
|
|
352
|
+
</div>
|
|
353
|
+
</td>
|
|
354
|
+
</tr>
|
|
355
|
+
</tbody>
|
|
356
|
+
</table>
|
|
357
|
+
|
|
358
|
+
<div v-for="(item, index) in poDetails.items || []" :key="'note-' + index">
|
|
359
|
+
<div v-if="item.justification" class="justification-note">
|
|
360
|
+
<strong>Price Change Justification (Item {{ item.itemNumber }}):</strong> {{ item.justification
|
|
361
|
+
}}
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
|
|
365
|
+
<div class="totals">
|
|
366
|
+
<div class="total-row">
|
|
367
|
+
<span>Subtotal:</span>
|
|
368
|
+
<span>${{ subTotal.toFixed(2) }}</span>
|
|
369
|
+
</div>
|
|
370
|
+
<div class="total-row">
|
|
371
|
+
<span>Tax (%):</span>
|
|
372
|
+
<span>${{ getOptional(vendorInfo.tax) }}</span>
|
|
373
|
+
</div>
|
|
374
|
+
<div class="total-row">
|
|
375
|
+
<span>Shipping:</span>
|
|
376
|
+
<span>${{ getOptional(vendorInfo.shipping) }}</span>
|
|
377
|
+
</div>
|
|
378
|
+
<div class="total-row grand-total">
|
|
379
|
+
<span>Grand Total:</span>
|
|
380
|
+
<span>${{ (subTotal + getOptional(vendorInfo.tax) + getOptional(vendorInfo.shipping)).toFixed(2)
|
|
381
|
+
}}</span>
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
<!-- Justification Modal -->
|
|
387
|
+
<div v-if="showJustificationModal" class="justification-modal" @click.self="closeJustificationModal">
|
|
388
|
+
<div class="modal-content">
|
|
389
|
+
<h3>Price Change Justification Required</h3>
|
|
390
|
+
<p style="margin-bottom: 15px; color: #666;">
|
|
391
|
+
Please provide a justification for changing the price from
|
|
392
|
+
<strong>${{ currentItem?.cost?.toFixed(2) }}</strong> to
|
|
393
|
+
<strong>${{ currentItem?.tempPrice?.toFixed(2) }}</strong>
|
|
394
|
+
</p>
|
|
395
|
+
<textarea v-model="justificationText" class="justification-textarea"
|
|
396
|
+
placeholder="Enter justification for price change..." required></textarea>
|
|
397
|
+
<div class="modal-buttons">
|
|
398
|
+
<button @click="closeJustificationModal" class="cancel-btn">Cancel</button>
|
|
399
|
+
<button @click="confirmPriceChange" class="save-btn" :disabled="!justificationText.trim()">
|
|
400
|
+
Confirm Change
|
|
401
|
+
</button>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
<button v-if="!isPrinting" type="button" class="add-item-btn" @click="handleFinishPO(poDetails.id)">Approve
|
|
406
|
+
PO</button>
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
<!-- Workflow Guide Tab -->
|
|
410
|
+
<div v-if="activeTab === 'workflow'" class="tab-content active">
|
|
411
|
+
<div class="workflow-steps">
|
|
412
|
+
<div class="workflow-step">
|
|
413
|
+
<div class="step-icon completed">1</div>
|
|
414
|
+
<div class="step-title">Request Submitted</div>
|
|
415
|
+
</div>
|
|
416
|
+
<div class="workflow-step">
|
|
417
|
+
<div class="step-icon active">2</div>
|
|
418
|
+
<div class="step-title">Department Review</div>
|
|
419
|
+
</div>
|
|
420
|
+
<div class="workflow-step">
|
|
421
|
+
<div class="step-icon">3</div>
|
|
422
|
+
<div class="step-title">Management Approval</div>
|
|
423
|
+
</div>
|
|
424
|
+
<div class="workflow-step">
|
|
425
|
+
<div class="step-icon">4</div>
|
|
426
|
+
<div class="step-title">Purchase Order Created</div>
|
|
427
|
+
</div>
|
|
428
|
+
<div class="workflow-step">
|
|
429
|
+
<div class="step-icon">5</div>
|
|
430
|
+
<div class="step-title">Order Fulfilled</div>
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
<div style="background: white; padding: 30px; border-radius: 15px; margin-top: 20px;">
|
|
435
|
+
<h3 style="color: #1e40af; margin-bottom: 20px;">Requisition Workflow Process</h3>
|
|
436
|
+
<div style="line-height: 1.8; color: #374151;">
|
|
437
|
+
<p><strong>Step 1: Request Identification</strong><br>
|
|
438
|
+
Department or individual identifies need for materials, equipment, or services.</p>
|
|
439
|
+
|
|
440
|
+
<p><strong>Step 2: Requisition Creation</strong><br>
|
|
441
|
+
Complete requisition form with detailed specifications, quantities, and business justification.</p>
|
|
442
|
+
|
|
443
|
+
<p><strong>Step 3: Department Review</strong><br>
|
|
444
|
+
Department supervisor reviews request for accuracy, necessity, and budget compliance.</p>
|
|
445
|
+
|
|
446
|
+
<p><strong>Step 4: Management Approval</strong><br>
|
|
447
|
+
Based on cost thresholds, appropriate management level provides approval.</p>
|
|
448
|
+
|
|
449
|
+
<p><strong>Step 5: Purchase Order Generation</strong><br>
|
|
450
|
+
Purchasing team converts approved requisition into formal Purchase Order (PO).</p>
|
|
451
|
+
|
|
452
|
+
<p><strong>Step 6: Vendor Processing</strong><br>
|
|
453
|
+
PO sent to vendor, order processed, and delivery scheduled.</p>
|
|
454
|
+
|
|
455
|
+
<p><strong>Step 7: Receipt & Inventory</strong><br>
|
|
456
|
+
Goods received, inspected, and entered into inventory system.</p>
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
</template>
|
|
462
|
+
|
|
463
|
+
<script>
|
|
464
|
+
export default {
|
|
465
|
+
name: 'RequisitionSystem',
|
|
466
|
+
props: {
|
|
467
|
+
userProfile: {
|
|
468
|
+
type: Object,
|
|
469
|
+
required: true
|
|
470
|
+
},
|
|
471
|
+
userRole: {
|
|
472
|
+
type: String,
|
|
473
|
+
required: true
|
|
474
|
+
},
|
|
475
|
+
requisitions: {
|
|
476
|
+
type: Array,
|
|
477
|
+
default: () => []
|
|
478
|
+
},
|
|
479
|
+
vessels: {
|
|
480
|
+
type: Array,
|
|
481
|
+
default: () => []
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
emits: [
|
|
486
|
+
'submit-requisition',
|
|
487
|
+
'submit-info-response',
|
|
488
|
+
'approve-requisition',
|
|
489
|
+
'decline-requisition',
|
|
490
|
+
'info-requisition',
|
|
491
|
+
'create-po',
|
|
492
|
+
'open-po',
|
|
493
|
+
'finish-po',
|
|
494
|
+
'accept-delivery'
|
|
495
|
+
],
|
|
496
|
+
|
|
497
|
+
data() {
|
|
498
|
+
return {
|
|
499
|
+
activeTab: 'workflow',
|
|
500
|
+
isPrinting: false,
|
|
501
|
+
infoResponse: '',
|
|
502
|
+
|
|
503
|
+
// Tabs with visibility rules
|
|
504
|
+
tabs: [
|
|
505
|
+
{ name: 'new-requisition', label: 'New Requisition', roles: ['requisitor'] },
|
|
506
|
+
{ name: 'my-requisitions', label: 'My Requisitions', roles: ['requisitor'] },
|
|
507
|
+
{ name: 'all-requisitions', label: 'All Requisitions', roles: ['requisitor', 'supervisor', 'captain', 'owner', 'purchaser'] },
|
|
508
|
+
{ name: 'approvals', label: 'Pending Approvals', roles: ['owner', 'supervisor', 'captain'] },
|
|
509
|
+
{ name: 'purchasing', label: 'Purchasing Queue', roles: ['purchaser'] },
|
|
510
|
+
{ name: 'receiving', label: 'Receiving', roles: ['requisitor'] },
|
|
511
|
+
{ name: 'workflow', label: 'Workflow Guide', roles: ['requisitor', 'supervisor', 'owner', 'purchaser', 'captain'] }
|
|
512
|
+
],
|
|
513
|
+
|
|
514
|
+
// Form State
|
|
515
|
+
form: {
|
|
516
|
+
requestor: '',
|
|
517
|
+
department: '',
|
|
518
|
+
project: '',
|
|
519
|
+
neededDate: '',
|
|
520
|
+
justification: '',
|
|
521
|
+
items: []
|
|
522
|
+
},
|
|
523
|
+
|
|
524
|
+
poDetails: {
|
|
525
|
+
editing: false,
|
|
526
|
+
items: []
|
|
527
|
+
},
|
|
528
|
+
vendorInfo: {},
|
|
529
|
+
justificationText: '',
|
|
530
|
+
currentItemIndex: null,
|
|
531
|
+
showJustificationModal: false,
|
|
532
|
+
|
|
533
|
+
// common fields
|
|
534
|
+
requisitionFields: [
|
|
535
|
+
{ label: 'Requestor', value: req => req.requestor },
|
|
536
|
+
{ label: 'Department', value: req => req.department },
|
|
537
|
+
{ label: 'Project', value: req => req.project || 'N/A' },
|
|
538
|
+
{ label: 'Submitted', value: req => req.submittedDate },
|
|
539
|
+
{ label: 'Items', value: req => `${req.items.length} item(s)` }
|
|
540
|
+
],
|
|
541
|
+
|
|
542
|
+
// Fields for My Requisitions display
|
|
543
|
+
requisitionFieldsMap: {
|
|
544
|
+
approved: [
|
|
545
|
+
{ label: 'Approved By', value: req => req.approvedBy },
|
|
546
|
+
],
|
|
547
|
+
declined: [
|
|
548
|
+
{ label: 'Declined By', value: req => req.declinedBy },
|
|
549
|
+
{ label: 'Rejection Reason', value: req => req.rejectionReason },
|
|
550
|
+
],
|
|
551
|
+
'info-requested': [
|
|
552
|
+
{ label: 'Info Requester', value: req => req.infoRequestedBy },
|
|
553
|
+
{ label: 'Requested Info', value: req => req.requestedInfo }
|
|
554
|
+
]
|
|
555
|
+
},
|
|
556
|
+
|
|
557
|
+
// Options
|
|
558
|
+
departments: [
|
|
559
|
+
'Marine Operations', 'Engineering', 'Maintenance',
|
|
560
|
+
'Safety & Compliance', 'Logistics', 'Administration',
|
|
561
|
+
'Engine Room'
|
|
562
|
+
],
|
|
563
|
+
|
|
564
|
+
priorities: [
|
|
565
|
+
'Urgent - Same Day',
|
|
566
|
+
'High - Within 3 Days',
|
|
567
|
+
'Normal - Within 1 Week',
|
|
568
|
+
'Low - Within 2 Weeks'
|
|
569
|
+
],
|
|
570
|
+
|
|
571
|
+
units: ['Pieces', 'Kilograms', 'Liters', 'Meters', 'Sets', 'Boxes']
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
computed: {
|
|
576
|
+
visibleTabs() {
|
|
577
|
+
return this.tabs.filter(tab => tab.roles.includes(this.userRole))
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
subTotal() {
|
|
581
|
+
return (this.poDetails.items || []).reduce((sum, item) => {
|
|
582
|
+
return sum + (item.unitPrice * item.qty);
|
|
583
|
+
}, 0);
|
|
584
|
+
},
|
|
585
|
+
|
|
586
|
+
reviewRequisitions() {
|
|
587
|
+
return this.requisitions.filter(r => r.status === 'under-review');
|
|
588
|
+
},
|
|
589
|
+
|
|
590
|
+
poRequisitions() {
|
|
591
|
+
return this.requisitions.filter(r => r.status === 'approved');
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
awaitingDelivery() {
|
|
595
|
+
return this.requisitions.filter(r => r.status === 'po-created');
|
|
596
|
+
},
|
|
597
|
+
|
|
598
|
+
currentItem() {
|
|
599
|
+
return this.currentItemIndex !== null ? this.poDetails.items[this.currentItemIndex] : null;
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
myRequisitions() {
|
|
603
|
+
const userId = this.userProfile.id || this.userProfile.profile_id;
|
|
604
|
+
return this.requisitions.filter(req => req.profile_id == userId);
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
methods: {
|
|
609
|
+
setActiveTab(tabName) {
|
|
610
|
+
this.activeTab = tabName;
|
|
611
|
+
},
|
|
612
|
+
|
|
613
|
+
getOptional(value) {
|
|
614
|
+
return typeof value === 'number' ? value.toFixed(2) : '0.00';
|
|
615
|
+
},
|
|
616
|
+
|
|
617
|
+
getRequisitionFields(req) {
|
|
618
|
+
const statusFields = this.requisitionFieldsMap[req.status] || [];
|
|
619
|
+
return [...(this.requisitionFields || []), ...statusFields];
|
|
620
|
+
},
|
|
621
|
+
|
|
622
|
+
addItem() {
|
|
623
|
+
this.form.items.push({
|
|
624
|
+
id: '',
|
|
625
|
+
desc: '',
|
|
626
|
+
qty: 1,
|
|
627
|
+
unit: 'Pieces',
|
|
628
|
+
cost: 0
|
|
629
|
+
})
|
|
630
|
+
},
|
|
631
|
+
|
|
632
|
+
removeItem(index) {
|
|
633
|
+
this.form.items.splice(index, 1)
|
|
634
|
+
},
|
|
635
|
+
|
|
636
|
+
startEdit(index) {
|
|
637
|
+
this.poDetails.items[index].editing = true;
|
|
638
|
+
this.poDetails.items[index].tempPrice = this.poDetails.items[index].unitPrice;
|
|
639
|
+
},
|
|
640
|
+
|
|
641
|
+
cancelEdit(index) {
|
|
642
|
+
this.poDetails.items[index].editing = false;
|
|
643
|
+
this.poDetails.items[index].tempPrice = this.poDetails.items[index].unitPrice;
|
|
644
|
+
},
|
|
645
|
+
|
|
646
|
+
savePrice(index) {
|
|
647
|
+
const item = this.poDetails.items[index];
|
|
648
|
+
if (item.tempPrice !== item.cost) {
|
|
649
|
+
// Price changed, require justification
|
|
650
|
+
this.currentItemIndex = index;
|
|
651
|
+
this.showJustificationModal = true;
|
|
652
|
+
} else {
|
|
653
|
+
// No change, just save
|
|
654
|
+
item.unitPrice = item.tempPrice;
|
|
655
|
+
item.editing = false;
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
confirmPriceChange() {
|
|
660
|
+
if (!this.justificationText.trim()) {
|
|
661
|
+
alert('Please provide a justification for the price change.');
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const item = this.poDetails.items[this.currentItemIndex];
|
|
666
|
+
item.unitPrice = item.tempPrice;
|
|
667
|
+
item.cost = item.tempPrice;
|
|
668
|
+
item.justification = this.justificationText.trim();
|
|
669
|
+
item.editing = false;
|
|
670
|
+
|
|
671
|
+
this.closeJustificationModal();
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
closeJustificationModal() {
|
|
675
|
+
this.showJustificationModal = false;
|
|
676
|
+
this.currentItemIndex = null;
|
|
677
|
+
this.justificationText = '';
|
|
678
|
+
},
|
|
679
|
+
|
|
680
|
+
// Event handlers that emit to parent
|
|
681
|
+
handleSubmitRequisition() {
|
|
682
|
+
const requisition = this.collectFormData('under-review');
|
|
683
|
+
this.$emit('submit-requisition', requisition);
|
|
684
|
+
this.resetForm();
|
|
685
|
+
},
|
|
686
|
+
|
|
687
|
+
handleSubmitInfoResponse(req) {
|
|
688
|
+
if (!this.infoResponse || this.infoResponse.trim() === '') {
|
|
689
|
+
alert('Please enter a response before submitting.');
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
this.$emit('submit-info-response', { req, response: this.infoResponse });
|
|
693
|
+
this.infoResponse = '';
|
|
694
|
+
},
|
|
695
|
+
|
|
696
|
+
handleApproveRequisition(id) {
|
|
697
|
+
this.$emit('approve-requisition', id);
|
|
698
|
+
},
|
|
699
|
+
|
|
700
|
+
handleDeclineRequisition(id) {
|
|
701
|
+
this.$emit('decline-requisition', id);
|
|
702
|
+
},
|
|
703
|
+
|
|
704
|
+
handleInfoRequisition(id) {
|
|
705
|
+
this.$emit('info-requisition', id);
|
|
706
|
+
},
|
|
707
|
+
|
|
708
|
+
handleCreatePO(id) {
|
|
709
|
+
this.$emit('create-po', id);
|
|
710
|
+
},
|
|
711
|
+
|
|
712
|
+
handleOpenPO(id) {
|
|
713
|
+
this.$emit('open-po', id);
|
|
714
|
+
},
|
|
715
|
+
|
|
716
|
+
handleFinishPO(id) {
|
|
717
|
+
this.$emit('finish-po', id);
|
|
718
|
+
},
|
|
719
|
+
|
|
720
|
+
handleAcceptDelivery(id) {
|
|
721
|
+
this.$emit('accept-delivery', id);
|
|
722
|
+
},
|
|
723
|
+
|
|
724
|
+
collectFormData(status) {
|
|
725
|
+
return {
|
|
726
|
+
id: 'REQ-' + Date.now(),
|
|
727
|
+
requestor: this.userProfile.full_name,
|
|
728
|
+
department: this.form.department,
|
|
729
|
+
project: this.form.project,
|
|
730
|
+
neededDate: this.form.neededDate,
|
|
731
|
+
justification: this.form.justification,
|
|
732
|
+
items: this.form.items.map(item => ({ ...item })),
|
|
733
|
+
status,
|
|
734
|
+
submittedDate: new Date().toLocaleDateString(),
|
|
735
|
+
profile_id: this.userProfile.id || this.userProfile.profile_id
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
|
|
739
|
+
resetForm() {
|
|
740
|
+
this.form = {
|
|
741
|
+
requestor: '',
|
|
742
|
+
department: '',
|
|
743
|
+
project: '',
|
|
744
|
+
neededDate: '',
|
|
745
|
+
justification: '',
|
|
746
|
+
items: []
|
|
747
|
+
}
|
|
748
|
+
// Add initial item row again
|
|
749
|
+
this.addItem()
|
|
750
|
+
},
|
|
751
|
+
|
|
752
|
+
// Methods for updating PO details from parent
|
|
753
|
+
updatePODetails(details) {
|
|
754
|
+
this.poDetails = details;
|
|
755
|
+
this.getNumber();
|
|
756
|
+
for (let item of this.poDetails.items) {
|
|
757
|
+
item.tempPrice = item.cost;
|
|
758
|
+
item.unitPrice = item.cost;
|
|
759
|
+
item.subTotal = item.unitPrice * item.qty;
|
|
760
|
+
}
|
|
761
|
+
},
|
|
762
|
+
|
|
763
|
+
updateVendorInfo(info) {
|
|
764
|
+
this.vendorInfo = info;
|
|
765
|
+
},
|
|
766
|
+
|
|
767
|
+
setPrintingMode(isPrinting) {
|
|
768
|
+
this.isPrinting = isPrinting;
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
getNumber() {
|
|
772
|
+
return (this.poDetails.items || []).map((item, index) => {
|
|
773
|
+
return item.itemNumber = index + 1;
|
|
774
|
+
})
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
|
|
778
|
+
created() {
|
|
779
|
+
// Initialize form with one item row
|
|
780
|
+
this.addItem()
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
</script>
|
|
784
|
+
|
|
785
|
+
<style>
|
|
786
|
+
.s-container {
|
|
787
|
+
max-width: 1200px;
|
|
788
|
+
margin: 0 auto;
|
|
789
|
+
background: rgba(255, 255, 255, 0.95);
|
|
790
|
+
border-radius: 20px;
|
|
791
|
+
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25);
|
|
792
|
+
overflow: hidden;
|
|
793
|
+
backdrop-filter: blur(10px);
|
|
794
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
795
|
+
padding: 20px;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.header h1 {
|
|
799
|
+
font-size: 2.5rem;
|
|
800
|
+
margin-bottom: 10px;
|
|
801
|
+
position: relative;
|
|
802
|
+
z-index: 1;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
.header p {
|
|
806
|
+
font-size: 1.1rem;
|
|
807
|
+
opacity: 0.9;
|
|
808
|
+
position: relative;
|
|
809
|
+
z-index: 1;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.nav-tabs {
|
|
813
|
+
display: flex;
|
|
814
|
+
background: #f8fafc;
|
|
815
|
+
border-bottom: 2px solid #e2e8f0;
|
|
816
|
+
overflow-x: auto;
|
|
817
|
+
/* enable horizontal scroll */
|
|
818
|
+
white-space: nowrap;
|
|
819
|
+
/* prevent wrapping */
|
|
820
|
+
-webkit-overflow-scrolling: touch;
|
|
821
|
+
/* smooth scrolling on mobile */
|
|
822
|
+
scrollbar-width: thin;
|
|
823
|
+
/* optional: thinner scrollbar in Firefox */
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/* Optional: style the scrollbar (Webkit browsers only) */
|
|
827
|
+
.nav-tabs::-webkit-scrollbar {
|
|
828
|
+
height: 6px;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.nav-tabs::-webkit-scrollbar-thumb {
|
|
832
|
+
background: #cbd5e1;
|
|
833
|
+
border-radius: 3px;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.nav-tabs::-webkit-scrollbar-track {
|
|
837
|
+
background: transparent;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
.nav-tab {
|
|
842
|
+
flex: 1;
|
|
843
|
+
padding: 15px 20px;
|
|
844
|
+
background: none;
|
|
845
|
+
border: none;
|
|
846
|
+
cursor: pointer;
|
|
847
|
+
font-size: 1rem;
|
|
848
|
+
font-weight: 600;
|
|
849
|
+
color: #64748b;
|
|
850
|
+
transition: all 0.3s ease;
|
|
851
|
+
position: relative;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.nav-tab:hover {
|
|
855
|
+
background: #e2e8f0;
|
|
856
|
+
color: #1e40af;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.nav-tab.active {
|
|
860
|
+
color: #1e40af;
|
|
861
|
+
background: white;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
.nav-tab.active::after {
|
|
865
|
+
content: '';
|
|
866
|
+
position: absolute;
|
|
867
|
+
bottom: -2px;
|
|
868
|
+
left: 0;
|
|
869
|
+
right: 0;
|
|
870
|
+
height: 3px;
|
|
871
|
+
background: #1e40af;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.tab-content {
|
|
875
|
+
display: none;
|
|
876
|
+
padding: 30px;
|
|
877
|
+
animation: fadeIn 0.5s ease-in;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.tab-content.active {
|
|
881
|
+
display: block;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
@keyframes fadeIn {
|
|
885
|
+
from {
|
|
886
|
+
opacity: 0;
|
|
887
|
+
transform: translateY(20px);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
to {
|
|
891
|
+
opacity: 1;
|
|
892
|
+
transform: translateY(0);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.form-grid {
|
|
897
|
+
display: grid;
|
|
898
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
899
|
+
gap: 25px;
|
|
900
|
+
margin-bottom: 30px;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.form-group {
|
|
904
|
+
display: flex;
|
|
905
|
+
flex-direction: column;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
.form-group label {
|
|
909
|
+
font-weight: 600;
|
|
910
|
+
margin-bottom: 8px;
|
|
911
|
+
color: #374151;
|
|
912
|
+
font-size: 0.95rem;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
.form-group input,
|
|
916
|
+
.form-group select,
|
|
917
|
+
.form-group textarea {
|
|
918
|
+
padding: 12px 15px;
|
|
919
|
+
border: 2px solid #e5e7eb;
|
|
920
|
+
border-radius: 10px;
|
|
921
|
+
font-size: 1rem;
|
|
922
|
+
transition: all 0.3s ease;
|
|
923
|
+
background: white;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
.form-group input:focus,
|
|
927
|
+
.form-group select:focus,
|
|
928
|
+
.form-group textarea:focus {
|
|
929
|
+
outline: none;
|
|
930
|
+
border-color: #3b82f6;
|
|
931
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
.form-group textarea {
|
|
935
|
+
min-height: 100px;
|
|
936
|
+
resize: vertical;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.items-section {
|
|
940
|
+
background: #f8fafc;
|
|
941
|
+
border-radius: 15px;
|
|
942
|
+
padding: 25px;
|
|
943
|
+
margin: 25px 0;
|
|
944
|
+
border: 2px solid #e2e8f0;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.items-header {
|
|
948
|
+
display: flex;
|
|
949
|
+
justify-content: space-between;
|
|
950
|
+
align-items: center;
|
|
951
|
+
margin-bottom: 20px;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
.items-header h3 {
|
|
955
|
+
color: #1e40af;
|
|
956
|
+
font-size: 1.3rem;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
.add-item-btn {
|
|
960
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
961
|
+
color: white;
|
|
962
|
+
border: none;
|
|
963
|
+
padding: 10px 20px;
|
|
964
|
+
border-radius: 8px;
|
|
965
|
+
cursor: pointer;
|
|
966
|
+
font-weight: 600;
|
|
967
|
+
transition: all 0.3s ease;
|
|
968
|
+
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.add-item-btn:hover {
|
|
972
|
+
transform: translateY(-2px);
|
|
973
|
+
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.item-row {
|
|
977
|
+
display: grid;
|
|
978
|
+
grid-template-columns: 2fr 1fr 1fr 1fr auto;
|
|
979
|
+
gap: 15px;
|
|
980
|
+
align-items: end;
|
|
981
|
+
margin-bottom: 15px;
|
|
982
|
+
padding: 15px;
|
|
983
|
+
background: white;
|
|
984
|
+
border-radius: 10px;
|
|
985
|
+
border: 1px solid #e5e7eb;
|
|
986
|
+
transition: all 0.3s ease;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.item-row:hover {
|
|
990
|
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
.marginbox {
|
|
994
|
+
margin: 10px
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.item-list {
|
|
998
|
+
list-style: none;
|
|
999
|
+
padding: 0;
|
|
1000
|
+
margin: 0.5rem 0;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
.item-list li {
|
|
1004
|
+
background: #f1f5f9;
|
|
1005
|
+
/* Light blue-gray background */
|
|
1006
|
+
margin-bottom: 0.5rem;
|
|
1007
|
+
padding: 0.5rem 0.75rem;
|
|
1008
|
+
border-radius: 0.375rem;
|
|
1009
|
+
/* Rounded corners */
|
|
1010
|
+
font-size: 0.95rem;
|
|
1011
|
+
color: #1e293b;
|
|
1012
|
+
/* Dark text */
|
|
1013
|
+
display: flex;
|
|
1014
|
+
justify-content: space-between;
|
|
1015
|
+
align-items: center;
|
|
1016
|
+
content: '•';
|
|
1017
|
+
color: #3b82f6;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
|
|
1021
|
+
.remove-item-btn {
|
|
1022
|
+
background: #ef4444;
|
|
1023
|
+
color: white;
|
|
1024
|
+
border: none;
|
|
1025
|
+
padding: 8px 12px;
|
|
1026
|
+
border-radius: 6px;
|
|
1027
|
+
cursor: pointer;
|
|
1028
|
+
font-size: 0.9rem;
|
|
1029
|
+
transition: all 0.3s ease;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
.remove-item-btn:hover {
|
|
1033
|
+
background: #dc2626;
|
|
1034
|
+
transform: scale(1.05);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
.approval-actions {
|
|
1038
|
+
display: flex;
|
|
1039
|
+
gap: 10px;
|
|
1040
|
+
margin-top: 15px;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
.btn-approve {
|
|
1044
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
1045
|
+
color: white;
|
|
1046
|
+
border: none;
|
|
1047
|
+
padding: 8px 16px;
|
|
1048
|
+
border-radius: 6px;
|
|
1049
|
+
cursor: pointer;
|
|
1050
|
+
font-size: 0.9rem;
|
|
1051
|
+
font-weight: 600;
|
|
1052
|
+
transition: all 0.3s ease;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
.btn-approve:hover {
|
|
1056
|
+
transform: translateY(-1px);
|
|
1057
|
+
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
.btn-reject {
|
|
1061
|
+
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
1062
|
+
color: white;
|
|
1063
|
+
border: none;
|
|
1064
|
+
padding: 8px 16px;
|
|
1065
|
+
border-radius: 6px;
|
|
1066
|
+
cursor: pointer;
|
|
1067
|
+
font-size: 0.9rem;
|
|
1068
|
+
font-weight: 600;
|
|
1069
|
+
transition: all 0.3s ease;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
.btn-reject:hover {
|
|
1073
|
+
transform: translateY(-1px);
|
|
1074
|
+
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.btn-request {
|
|
1078
|
+
background: linear-gradient(135deg, #ef9a44 0%, #fa920a 100%);
|
|
1079
|
+
color: white;
|
|
1080
|
+
border: none;
|
|
1081
|
+
padding: 8px 16px;
|
|
1082
|
+
border-radius: 6px;
|
|
1083
|
+
cursor: pointer;
|
|
1084
|
+
font-size: 0.9rem;
|
|
1085
|
+
font-weight: 600;
|
|
1086
|
+
transition: all 0.3s ease;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
.btn-request:hover {
|
|
1090
|
+
transform: translateY(-1px);
|
|
1091
|
+
box-shadow: 0 4px 12px rgba(240, 162, 52, 0.3);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
.btn-create-po {
|
|
1096
|
+
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
|
|
1097
|
+
color: white;
|
|
1098
|
+
border: none;
|
|
1099
|
+
padding: 8px 16px;
|
|
1100
|
+
border-radius: 6px;
|
|
1101
|
+
cursor: pointer;
|
|
1102
|
+
font-size: 0.9rem;
|
|
1103
|
+
font-weight: 600;
|
|
1104
|
+
transition: all 0.3s ease;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
.btn-create-po:hover {
|
|
1108
|
+
transform: translateY(-1px);
|
|
1109
|
+
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
.btn-mark-delivered {
|
|
1113
|
+
background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%);
|
|
1114
|
+
color: white;
|
|
1115
|
+
border: none;
|
|
1116
|
+
padding: 8px 16px;
|
|
1117
|
+
border-radius: 6px;
|
|
1118
|
+
cursor: pointer;
|
|
1119
|
+
font-size: 0.9rem;
|
|
1120
|
+
font-weight: 600;
|
|
1121
|
+
transition: all 0.3s ease;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
.btn-mark-delivered:hover {
|
|
1125
|
+
transform: translateY(-1px);
|
|
1126
|
+
box-shadow: 0 4px 12px rgba(6, 182, 212, 0.3);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
.po-info {
|
|
1130
|
+
background: #f8fafc;
|
|
1131
|
+
padding: 15px;
|
|
1132
|
+
border-radius: 8px;
|
|
1133
|
+
margin: 10px 0;
|
|
1134
|
+
border: 1px solid #e2e8f0;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
.po-info h4 {
|
|
1138
|
+
color: #1e40af;
|
|
1139
|
+
margin-bottom: 10px;
|
|
1140
|
+
font-size: 1rem;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
.po-details {
|
|
1144
|
+
display: grid;
|
|
1145
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
1146
|
+
gap: 10px;
|
|
1147
|
+
font-size: 0.9rem;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
.items-list {
|
|
1151
|
+
background: #f8fafc;
|
|
1152
|
+
padding: 15px;
|
|
1153
|
+
border-radius: 8px;
|
|
1154
|
+
margin: 15px 0;
|
|
1155
|
+
border: 1px solid #e2e8f0;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
.items-list h4 {
|
|
1159
|
+
color: #374151;
|
|
1160
|
+
margin-bottom: 10px;
|
|
1161
|
+
font-size: 1rem;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
.item-detail {
|
|
1165
|
+
display: flex;
|
|
1166
|
+
justify-content: space-between;
|
|
1167
|
+
padding: 8px 0;
|
|
1168
|
+
border-bottom: 1px solid #e5e7eb;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
.item-detail:last-child {
|
|
1172
|
+
border-bottom: none;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
.comments-section {
|
|
1176
|
+
margin-top: 15px;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
.comments-section textarea {
|
|
1180
|
+
width: 100%;
|
|
1181
|
+
min-height: 80px;
|
|
1182
|
+
padding: 10px;
|
|
1183
|
+
border: 1px solid #d1d5db;
|
|
1184
|
+
border-radius: 6px;
|
|
1185
|
+
font-size: 0.9rem;
|
|
1186
|
+
resize: vertical;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
.role-badge {
|
|
1190
|
+
display: inline-block;
|
|
1191
|
+
padding: 4px 8px;
|
|
1192
|
+
border-radius: 12px;
|
|
1193
|
+
font-size: 0.75rem;
|
|
1194
|
+
font-weight: 600;
|
|
1195
|
+
text-transform: uppercase;
|
|
1196
|
+
letter-spacing: 0.5px;
|
|
1197
|
+
margin-left: 10px;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
.role-supervisor {
|
|
1201
|
+
background: #fef3c7;
|
|
1202
|
+
color: #92400e;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
.role-purchasing {
|
|
1206
|
+
background: #dbeafe;
|
|
1207
|
+
color: #1e40af;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
.role-receiving {
|
|
1211
|
+
background: #dcfce7;
|
|
1212
|
+
color: #166534;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
.status-badge {
|
|
1216
|
+
display: inline-block;
|
|
1217
|
+
padding: 6px 12px;
|
|
1218
|
+
border-radius: 20px;
|
|
1219
|
+
font-size: 0.85rem;
|
|
1220
|
+
font-weight: 600;
|
|
1221
|
+
text-transform: uppercase;
|
|
1222
|
+
letter-spacing: 0.5px;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
.status-draft {
|
|
1226
|
+
background: #fef3c7;
|
|
1227
|
+
color: #92400e;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.status-under-review {
|
|
1231
|
+
background: #dbeafe;
|
|
1232
|
+
color: #1e40af;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
.status-approved {
|
|
1236
|
+
background: #dcfce7;
|
|
1237
|
+
color: #166534;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
.status-declined {
|
|
1241
|
+
background: #fee2e2;
|
|
1242
|
+
color: #dc2626;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
.status-info-requested {
|
|
1246
|
+
background: #e8cf12;
|
|
1247
|
+
color: #da7212;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
.status-po-created {
|
|
1251
|
+
background: #e0e7ff;
|
|
1252
|
+
color: #4bca38;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
.status-pending-supply {
|
|
1256
|
+
background: #113fd8;
|
|
1257
|
+
color: #4bca38;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.status-delivered {
|
|
1261
|
+
background: #f3e8ff;
|
|
1262
|
+
color: #7c3aed;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.status-received {
|
|
1266
|
+
background: #ecfdf5;
|
|
1267
|
+
color: #065f46;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
.action-buttons {
|
|
1271
|
+
display: flex;
|
|
1272
|
+
gap: 15px;
|
|
1273
|
+
justify-content: flex-end;
|
|
1274
|
+
margin-top: 30px;
|
|
1275
|
+
padding-top: 25px;
|
|
1276
|
+
border-top: 2px solid #e5e7eb;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/*.btn {
|
|
1280
|
+
padding: 12px 25px;
|
|
1281
|
+
border: none;
|
|
1282
|
+
border-radius: 10px;
|
|
1283
|
+
font-size: 1rem;
|
|
1284
|
+
font-weight: 600;
|
|
1285
|
+
cursor: pointer;
|
|
1286
|
+
transition: all 0.3s ease;
|
|
1287
|
+
min-width: 120px;
|
|
1288
|
+
}*/
|
|
1289
|
+
|
|
1290
|
+
.btn-primary-req {
|
|
1291
|
+
background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
|
|
1292
|
+
color: white;
|
|
1293
|
+
box-shadow: 0 4px 15px rgba(30, 64, 175, 0.3);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
.btn-primary-req:hover {
|
|
1297
|
+
transform: translateY(-2px);
|
|
1298
|
+
box-shadow: 0 6px 20px rgba(30, 64, 175, 0.4);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
.btn-secondary {
|
|
1302
|
+
background: #6b7280;
|
|
1303
|
+
color: white;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
.btn-secondary:hover {
|
|
1307
|
+
background: #4b5563;
|
|
1308
|
+
transform: translateY(-2px);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
.requisition-list {
|
|
1312
|
+
display: grid;
|
|
1313
|
+
gap: 20px;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
.requisition-card {
|
|
1317
|
+
background: white;
|
|
1318
|
+
border-radius: 15px;
|
|
1319
|
+
padding: 25px;
|
|
1320
|
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
|
1321
|
+
border: 1px solid #e5e7eb;
|
|
1322
|
+
transition: all 0.3s ease;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
.requisition-card:hover {
|
|
1326
|
+
transform: translateY(-5px);
|
|
1327
|
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
.requisition-header {
|
|
1331
|
+
display: flex;
|
|
1332
|
+
justify-content: space-between;
|
|
1333
|
+
align-items: center;
|
|
1334
|
+
margin-bottom: 15px;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
.requisition-id {
|
|
1338
|
+
font-size: 1.2rem;
|
|
1339
|
+
font-weight: 700;
|
|
1340
|
+
color: #1e40af;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
.requisition-details {
|
|
1344
|
+
display: grid;
|
|
1345
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
1346
|
+
gap: 15px;
|
|
1347
|
+
margin-top: 15px;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
.detail-item {
|
|
1351
|
+
display: flex;
|
|
1352
|
+
flex-direction: column;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
.detail-label {
|
|
1356
|
+
font-size: 0.85rem;
|
|
1357
|
+
font-weight: 600;
|
|
1358
|
+
color: #6b7280;
|
|
1359
|
+
text-transform: uppercase;
|
|
1360
|
+
letter-spacing: 0.5px;
|
|
1361
|
+
margin-bottom: 4px;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
.detail-value {
|
|
1365
|
+
font-size: 1rem;
|
|
1366
|
+
color: #374151;
|
|
1367
|
+
font-weight: 500;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
.workflow-steps {
|
|
1371
|
+
display: flex;
|
|
1372
|
+
justify-content: space-between;
|
|
1373
|
+
align-items: center;
|
|
1374
|
+
margin: 30px 0;
|
|
1375
|
+
padding: 20px;
|
|
1376
|
+
background: #f8fafc;
|
|
1377
|
+
border-radius: 15px;
|
|
1378
|
+
border: 2px solid #e2e8f0;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
.workflow-step {
|
|
1382
|
+
display: flex;
|
|
1383
|
+
flex-direction: column;
|
|
1384
|
+
align-items: center;
|
|
1385
|
+
flex: 1;
|
|
1386
|
+
position: relative;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
.workflow-step:not(:last-child)::after {
|
|
1390
|
+
content: '';
|
|
1391
|
+
position: absolute;
|
|
1392
|
+
top: 20px;
|
|
1393
|
+
right: -50%;
|
|
1394
|
+
width: 100%;
|
|
1395
|
+
height: 2px;
|
|
1396
|
+
background: #e5e7eb;
|
|
1397
|
+
z-index: 1;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
.step-icon {
|
|
1401
|
+
width: 40px;
|
|
1402
|
+
height: 40px;
|
|
1403
|
+
border-radius: 50%;
|
|
1404
|
+
background: #e5e7eb;
|
|
1405
|
+
display: flex;
|
|
1406
|
+
align-items: center;
|
|
1407
|
+
justify-content: center;
|
|
1408
|
+
font-weight: bold;
|
|
1409
|
+
color: #6b7280;
|
|
1410
|
+
margin-bottom: 10px;
|
|
1411
|
+
position: relative;
|
|
1412
|
+
z-index: 2;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
.step-icon.active {
|
|
1416
|
+
background: #3b82f6;
|
|
1417
|
+
color: white;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
.step-icon.completed {
|
|
1421
|
+
background: #10b981;
|
|
1422
|
+
color: white;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
.step-title {
|
|
1426
|
+
font-size: 0.9rem;
|
|
1427
|
+
font-weight: 600;
|
|
1428
|
+
color: #374151;
|
|
1429
|
+
text-align: center;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
@media (max-width: 768px) {
|
|
1433
|
+
.form-grid {
|
|
1434
|
+
grid-template-columns: 1fr;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
.item-row {
|
|
1438
|
+
grid-template-columns: 1fr;
|
|
1439
|
+
gap: 10px;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
.action-buttons {
|
|
1443
|
+
flex-direction: column;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
.workflow-steps {
|
|
1447
|
+
flex-direction: column;
|
|
1448
|
+
gap: 20px;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
.workflow-step::after {
|
|
1452
|
+
display: none;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
#content {
|
|
1457
|
+
width: 100%;
|
|
1458
|
+
min-height: 100vh;
|
|
1459
|
+
transition: all 0.3s;
|
|
1460
|
+
position: absolute;
|
|
1461
|
+
padding: 20px;
|
|
1462
|
+
padding-left: 40px;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
#content.active {
|
|
1466
|
+
margin-left: var(--sidebar-width);
|
|
1467
|
+
width: calc(100% - var(--sidebar-width));
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
.po-content {
|
|
1471
|
+
padding: 40px;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
.po-header {
|
|
1475
|
+
display: grid;
|
|
1476
|
+
grid-template-columns: 1fr 1fr;
|
|
1477
|
+
gap: 40px;
|
|
1478
|
+
margin-bottom: 40px;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
.company-info,
|
|
1482
|
+
.po-details {
|
|
1483
|
+
background: #f8f9fa;
|
|
1484
|
+
padding: 25px;
|
|
1485
|
+
border-radius: 15px;
|
|
1486
|
+
border-left: 5px solid #3498db;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
.company-info h3,
|
|
1490
|
+
.po-details h3 {
|
|
1491
|
+
color: #2c3e50;
|
|
1492
|
+
margin-bottom: 15px;
|
|
1493
|
+
font-size: 1.3rem;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
.info-row {
|
|
1497
|
+
margin-bottom: 8px;
|
|
1498
|
+
display: flex;
|
|
1499
|
+
justify-content: space-between;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
.info-label {
|
|
1503
|
+
font-weight: 600;
|
|
1504
|
+
color: #555;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
.info-value {
|
|
1508
|
+
color: #2c3e50;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
.items-section {
|
|
1512
|
+
margin-top: 30px;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
.section-title {
|
|
1516
|
+
color: #2c3e50;
|
|
1517
|
+
font-size: 1.5rem;
|
|
1518
|
+
margin-bottom: 20px;
|
|
1519
|
+
border-bottom: 2px solid #3498db;
|
|
1520
|
+
padding-bottom: 10px;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
.items-table {
|
|
1524
|
+
width: 100%;
|
|
1525
|
+
border-collapse: collapse;
|
|
1526
|
+
margin-bottom: 30px;
|
|
1527
|
+
background: white;
|
|
1528
|
+
border-radius: 10px;
|
|
1529
|
+
overflow: hidden;
|
|
1530
|
+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
.items-table th {
|
|
1534
|
+
background: linear-gradient(135deg, #34495e 0%, #2c3e50 100%);
|
|
1535
|
+
color: white;
|
|
1536
|
+
padding: 15px;
|
|
1537
|
+
text-align: left;
|
|
1538
|
+
font-weight: 600;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
.items-table td {
|
|
1542
|
+
padding: 15px;
|
|
1543
|
+
border-bottom: 1px solid #eee;
|
|
1544
|
+
vertical-align: middle;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
.items-table tr:hover {
|
|
1548
|
+
background: #f8f9fa;
|
|
1549
|
+
transform: translateY(-1px);
|
|
1550
|
+
transition: all 0.3s ease;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
.price-input {
|
|
1554
|
+
padding: 8px 12px;
|
|
1555
|
+
border: 2px solid #ddd;
|
|
1556
|
+
border-radius: 8px;
|
|
1557
|
+
font-size: 14px;
|
|
1558
|
+
width: 100px;
|
|
1559
|
+
transition: all 0.3s ease;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
.price-input:focus {
|
|
1563
|
+
outline: none;
|
|
1564
|
+
border-color: #3498db;
|
|
1565
|
+
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
.price-changed {
|
|
1569
|
+
background: #fff3cd;
|
|
1570
|
+
border-color: #ffc107;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
.edit-btn {
|
|
1574
|
+
background: #3498db;
|
|
1575
|
+
color: white;
|
|
1576
|
+
border: none;
|
|
1577
|
+
padding: 8px 16px;
|
|
1578
|
+
border-radius: 6px;
|
|
1579
|
+
cursor: pointer;
|
|
1580
|
+
font-size: 12px;
|
|
1581
|
+
transition: all 0.3s ease;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
.edit-btn:hover {
|
|
1585
|
+
background: #2980b9;
|
|
1586
|
+
transform: translateY(-2px);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
.save-btn {
|
|
1590
|
+
background: #27ae60;
|
|
1591
|
+
color: white;
|
|
1592
|
+
border: none;
|
|
1593
|
+
padding: 8px 16px;
|
|
1594
|
+
border-radius: 6px;
|
|
1595
|
+
cursor: pointer;
|
|
1596
|
+
font-size: 12px;
|
|
1597
|
+
margin-right: 5px;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
.cancel-btn {
|
|
1601
|
+
background: #e74c3c;
|
|
1602
|
+
color: white;
|
|
1603
|
+
border: none;
|
|
1604
|
+
padding: 8px 16px;
|
|
1605
|
+
border-radius: 6px;
|
|
1606
|
+
cursor: pointer;
|
|
1607
|
+
font-size: 12px;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
.justification-modal {
|
|
1611
|
+
position: fixed;
|
|
1612
|
+
top: 0;
|
|
1613
|
+
left: 0;
|
|
1614
|
+
right: 0;
|
|
1615
|
+
bottom: 0;
|
|
1616
|
+
background: rgba(0, 0, 0, 0.7);
|
|
1617
|
+
display: flex;
|
|
1618
|
+
align-items: center;
|
|
1619
|
+
justify-content: center;
|
|
1620
|
+
z-index: 1000;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
.modal-content {
|
|
1624
|
+
background: white;
|
|
1625
|
+
padding: 30px;
|
|
1626
|
+
border-radius: 15px;
|
|
1627
|
+
max-width: 500px;
|
|
1628
|
+
width: 90%;
|
|
1629
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
.modal-content h3 {
|
|
1633
|
+
color: #2c3e50;
|
|
1634
|
+
margin-bottom: 20px;
|
|
1635
|
+
font-size: 1.3rem;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
.justification-textarea {
|
|
1639
|
+
width: 100%;
|
|
1640
|
+
padding: 15px;
|
|
1641
|
+
border: 2px solid #ddd;
|
|
1642
|
+
border-radius: 8px;
|
|
1643
|
+
font-family: inherit;
|
|
1644
|
+
font-size: 14px;
|
|
1645
|
+
resize: vertical;
|
|
1646
|
+
min-height: 100px;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
.justification-textarea:focus {
|
|
1650
|
+
outline: none;
|
|
1651
|
+
border-color: #3498db;
|
|
1652
|
+
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
.modal-buttons {
|
|
1656
|
+
margin-top: 20px;
|
|
1657
|
+
display: flex;
|
|
1658
|
+
gap: 10px;
|
|
1659
|
+
justify-content: flex-end;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
.totals {
|
|
1663
|
+
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
|
1664
|
+
padding: 25px;
|
|
1665
|
+
border-radius: 15px;
|
|
1666
|
+
margin-top: 20px;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
.total-row {
|
|
1670
|
+
display: flex;
|
|
1671
|
+
justify-content: space-between;
|
|
1672
|
+
margin-bottom: 10px;
|
|
1673
|
+
font-size: 1.1rem;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
.total-row.grand-total {
|
|
1677
|
+
border-top: 2px solid #3498db;
|
|
1678
|
+
padding-top: 15px;
|
|
1679
|
+
margin-top: 15px;
|
|
1680
|
+
font-weight: bold;
|
|
1681
|
+
font-size: 1.3rem;
|
|
1682
|
+
color: #2c3e50;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
.justification-note {
|
|
1686
|
+
background: #fff3cd;
|
|
1687
|
+
border: 1px solid #ffc107;
|
|
1688
|
+
border-radius: 8px;
|
|
1689
|
+
padding: 10px;
|
|
1690
|
+
margin-top: 10px;
|
|
1691
|
+
font-size: 12px;
|
|
1692
|
+
color: #856404;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
.price-change-indicator {
|
|
1696
|
+
background: #dc3545;
|
|
1697
|
+
color: white;
|
|
1698
|
+
border-radius: 50%;
|
|
1699
|
+
width: 20px;
|
|
1700
|
+
height: 20px;
|
|
1701
|
+
font-size: 12px;
|
|
1702
|
+
display: inline-flex;
|
|
1703
|
+
align-items: center;
|
|
1704
|
+
justify-content: center;
|
|
1705
|
+
margin-left: 5px;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
@media (max-width: 768px) {
|
|
1709
|
+
.po-header {
|
|
1710
|
+
grid-template-columns: 1fr;
|
|
1711
|
+
gap: 20px;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
.items-table {
|
|
1715
|
+
font-size: 14px;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
.items-table th,
|
|
1719
|
+
.items-table td {
|
|
1720
|
+
padding: 10px 8px;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
.price-input {
|
|
1724
|
+
width: 80px;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
</style>
|