@dilipod/ui 0.4.5 → 0.4.6
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/components/impact-metrics-form.d.ts.map +1 -1
- package/dist/index.js +97 -130
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +97 -130
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/impact-metrics-form.tsx +88 -133
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react'
|
|
4
|
-
import { Card, CardContent
|
|
4
|
+
import { Card, CardContent } from './card'
|
|
5
5
|
import { Button } from './button'
|
|
6
|
-
import { IconBox } from './icon-box'
|
|
7
6
|
import { toast } from './use-toast'
|
|
8
7
|
import { cn } from '../lib/utils'
|
|
9
8
|
|
|
@@ -152,18 +151,11 @@ export function ImpactMetricsForm({
|
|
|
152
151
|
const netAnnualSavings = laborSavingsPerYear - workerCostPerYear
|
|
153
152
|
|
|
154
153
|
return (
|
|
155
|
-
<Card className={cn("
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
{/* ChartLineUp icon */}
|
|
161
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 256 256">
|
|
162
|
-
<path d="M232,208a8,8,0,0,1-8,8H32a8,8,0,0,1-8-8V48a8,8,0,0,1,16,0V156.69l50.34-50.35a8,8,0,0,1,11.32,0L128,132.69l58.34-58.35a8,8,0,0,1,11.32,11.32l-64,64a8,8,0,0,1-11.32,0L96,123.31,40,179.31V200H224A8,8,0,0,1,232,208Z"/>
|
|
163
|
-
</svg>
|
|
164
|
-
</IconBox>
|
|
165
|
-
Impact Metrics (ROI)
|
|
166
|
-
</CardTitle>
|
|
154
|
+
<Card className={cn("", className)}>
|
|
155
|
+
<CardContent className="p-5">
|
|
156
|
+
{/* Header */}
|
|
157
|
+
<div className="flex items-center justify-between mb-4">
|
|
158
|
+
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">Impact Metrics (ROI)</p>
|
|
167
159
|
<div className="flex items-center gap-2">
|
|
168
160
|
{isEditing ? (
|
|
169
161
|
<>
|
|
@@ -195,140 +187,103 @@ export function ImpactMetricsForm({
|
|
|
195
187
|
)}
|
|
196
188
|
</div>
|
|
197
189
|
</div>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
<div className="grid
|
|
190
|
+
|
|
191
|
+
{/* Metrics Grid */}
|
|
192
|
+
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6">
|
|
201
193
|
{/* Time per task */}
|
|
202
|
-
<div
|
|
203
|
-
<
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
</div>
|
|
224
|
-
) : (
|
|
225
|
-
<p className="text-2xl font-bold">
|
|
226
|
-
{metrics.time_saved_minutes_per_run} <span className="text-base font-normal text-muted-foreground">min</span>
|
|
227
|
-
</p>
|
|
228
|
-
)}
|
|
229
|
-
<p className="text-xs text-muted-foreground mt-1">How long manually</p>
|
|
230
|
-
</div>
|
|
194
|
+
<div>
|
|
195
|
+
<p className="text-xs text-muted-foreground uppercase tracking-wide mb-1">Time per Task</p>
|
|
196
|
+
{isEditing ? (
|
|
197
|
+
<div className="flex items-baseline gap-1">
|
|
198
|
+
<input
|
|
199
|
+
type="number"
|
|
200
|
+
value={metrics.time_saved_minutes_per_run}
|
|
201
|
+
onChange={(e) => setMetrics(prev => ({
|
|
202
|
+
...prev,
|
|
203
|
+
time_saved_minutes_per_run: parseInt(e.target.value) || 0
|
|
204
|
+
}))}
|
|
205
|
+
className="w-16 px-2 py-1 text-2xl font-bold border border-border rounded-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan)] bg-background"
|
|
206
|
+
min="0"
|
|
207
|
+
/>
|
|
208
|
+
<span className="text-sm text-muted-foreground">min</span>
|
|
209
|
+
</div>
|
|
210
|
+
) : (
|
|
211
|
+
<p className="text-2xl font-bold">
|
|
212
|
+
{metrics.time_saved_minutes_per_run}<span className="text-sm font-normal text-muted-foreground ml-1">min</span>
|
|
213
|
+
</p>
|
|
214
|
+
)}
|
|
231
215
|
</div>
|
|
232
216
|
|
|
233
217
|
{/* Manual cost */}
|
|
234
|
-
<div
|
|
235
|
-
<
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
</div>
|
|
258
|
-
) : (
|
|
259
|
-
<p className="text-2xl font-bold">
|
|
260
|
-
€{metrics.hourly_rate_euros} <span className="text-base font-normal text-muted-foreground">/hr</span>
|
|
261
|
-
</p>
|
|
262
|
-
)}
|
|
263
|
-
<p className="text-xs text-muted-foreground mt-1">Employee hourly cost</p>
|
|
264
|
-
</div>
|
|
218
|
+
<div>
|
|
219
|
+
<p className="text-xs text-muted-foreground uppercase tracking-wide mb-1">Manual Cost</p>
|
|
220
|
+
{isEditing ? (
|
|
221
|
+
<div className="flex items-baseline gap-1">
|
|
222
|
+
<span className="text-sm text-muted-foreground">€</span>
|
|
223
|
+
<input
|
|
224
|
+
type="number"
|
|
225
|
+
value={metrics.hourly_rate_euros}
|
|
226
|
+
onChange={(e) => setMetrics(prev => ({
|
|
227
|
+
...prev,
|
|
228
|
+
hourly_rate_euros: parseFloat(e.target.value) || 0
|
|
229
|
+
}))}
|
|
230
|
+
className="w-16 px-2 py-1 text-2xl font-bold border border-border rounded-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan)] bg-background"
|
|
231
|
+
min="0"
|
|
232
|
+
step="0.5"
|
|
233
|
+
/>
|
|
234
|
+
<span className="text-sm text-muted-foreground">/hr</span>
|
|
235
|
+
</div>
|
|
236
|
+
) : (
|
|
237
|
+
<p className="text-2xl font-bold">
|
|
238
|
+
€{metrics.hourly_rate_euros}<span className="text-sm font-normal text-muted-foreground ml-1">/hr</span>
|
|
239
|
+
</p>
|
|
240
|
+
)}
|
|
265
241
|
</div>
|
|
266
242
|
|
|
267
243
|
{/* Job portion */}
|
|
268
|
-
<div
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
) : (
|
|
293
|
-
<p className="text-2xl font-bold">
|
|
294
|
-
{Math.round(metrics.fte_equivalent * 100)} <span className="text-base font-normal text-muted-foreground">%</span>
|
|
295
|
-
</p>
|
|
296
|
-
)}
|
|
297
|
-
<p className="text-xs text-muted-foreground mt-1">% of FTE ({hoursSavedPerYear}h/year)</p>
|
|
298
|
-
</div>
|
|
244
|
+
<div>
|
|
245
|
+
<p className="text-xs text-muted-foreground uppercase tracking-wide mb-1">Job Portion</p>
|
|
246
|
+
{isEditing ? (
|
|
247
|
+
<div className="flex items-baseline gap-1">
|
|
248
|
+
<input
|
|
249
|
+
type="number"
|
|
250
|
+
value={Math.round(metrics.fte_equivalent * 100)}
|
|
251
|
+
onChange={(e) => setMetrics(prev => ({
|
|
252
|
+
...prev,
|
|
253
|
+
fte_equivalent: (parseFloat(e.target.value) || 0) / 100
|
|
254
|
+
}))}
|
|
255
|
+
className="w-16 px-2 py-1 text-2xl font-bold border border-border rounded-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan)] bg-background"
|
|
256
|
+
min="0"
|
|
257
|
+
max="1000"
|
|
258
|
+
step="5"
|
|
259
|
+
/>
|
|
260
|
+
<span className="text-sm text-muted-foreground">%</span>
|
|
261
|
+
</div>
|
|
262
|
+
) : (
|
|
263
|
+
<p className="text-2xl font-bold">
|
|
264
|
+
{Math.round(metrics.fte_equivalent * 100)}<span className="text-sm font-normal text-muted-foreground ml-1">%</span>
|
|
265
|
+
</p>
|
|
266
|
+
)}
|
|
267
|
+
<p className="text-xs text-muted-foreground mt-0.5">{hoursSavedPerYear}h/year</p>
|
|
299
268
|
</div>
|
|
300
269
|
|
|
301
270
|
{/* Net Annual Savings */}
|
|
302
|
-
<div
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
<label className="text-sm text-muted-foreground block mb-1">Net Annual Savings</label>
|
|
311
|
-
<p className={cn("text-2xl font-bold", netAnnualSavings >= 0 ? "text-[var(--cyan)]" : "text-red-500")}>
|
|
312
|
-
€{netAnnualSavings.toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
|
313
|
-
</p>
|
|
314
|
-
<p className="text-xs text-muted-foreground mt-1">
|
|
315
|
-
€{laborSavingsPerYear.toLocaleString(undefined, { maximumFractionDigits: 0 })} labor − €{workerCostPerYear} worker
|
|
316
|
-
</p>
|
|
317
|
-
</div>
|
|
271
|
+
<div>
|
|
272
|
+
<p className="text-xs text-muted-foreground uppercase tracking-wide mb-1">Net Annual Savings</p>
|
|
273
|
+
<p className={cn("text-2xl font-bold", netAnnualSavings >= 0 ? "text-[var(--cyan)]" : "text-red-500")}>
|
|
274
|
+
€{netAnnualSavings.toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
|
275
|
+
</p>
|
|
276
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
277
|
+
€{laborSavingsPerYear.toLocaleString(undefined, { maximumFractionDigits: 0 })} − €{workerCostPerYear}
|
|
278
|
+
</p>
|
|
318
279
|
</div>
|
|
319
280
|
</div>
|
|
320
281
|
|
|
321
282
|
{/* Implied frequency indicator */}
|
|
322
283
|
{impliedFrequencyPerYear > 0 && (
|
|
323
|
-
<
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
~{impliedFrequencyPerMonth}/month ({impliedFrequencyPerYear}/year)
|
|
327
|
-
<span className="text-xs ml-2">
|
|
328
|
-
based on {metrics.time_saved_minutes_per_run} min/task × {Math.round(metrics.fte_equivalent * 100)}% FTE
|
|
329
|
-
</span>
|
|
330
|
-
</p>
|
|
331
|
-
</div>
|
|
284
|
+
<p className="text-xs text-muted-foreground mt-4 pt-3 border-t border-border/50">
|
|
285
|
+
Implied: ~{impliedFrequencyPerMonth}×/month ({impliedFrequencyPerYear}×/year)
|
|
286
|
+
</p>
|
|
332
287
|
)}
|
|
333
288
|
</CardContent>
|
|
334
289
|
</Card>
|