@agenticmail/enterprise 0.5.88 → 0.5.89

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.
@@ -1,5 +1,6 @@
1
1
  import { h, useState, useEffect, useCallback, Fragment, useApp, apiCall, engineCall, formatUptime, buildAgentDataMap, renderAgentBadge, showConfirm, getOrgId } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
+ import { TimezoneSelect } from '../components/timezones.js';
3
4
  import { DetailModal } from '../components/modal.js';
4
5
  import { CULTURES, LANGUAGES, DEFAULT_TRAITS, computeAge, PersonaForm } from '../components/persona-fields.js';
5
6
  import { TagInput } from '../components/tag-input.js';
@@ -2239,6 +2240,22 @@ function WorkforceSection(props) {
2239
2240
  var showAddTask = _showAddTask[0]; var setShowAddTask = _showAddTask[1];
2240
2241
  var _taskForm = useState({ title: '', description: '', priority: 'normal', type: 'general' });
2241
2242
  var taskForm = _taskForm[0]; var setTaskForm = _taskForm[1];
2243
+ var _editing = useState(false);
2244
+ var editing = _editing[0]; var setEditing = _editing[1];
2245
+ var _saving = useState(false);
2246
+ var saving = _saving[0]; var setSaving = _saving[1];
2247
+
2248
+ var dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
2249
+
2250
+ var defaultSchedForm = {
2251
+ agentId: agentId, timezone: 'UTC', scheduleType: 'standard',
2252
+ config: { standardHours: { start: '09:00', end: '17:00', daysOfWeek: [1, 2, 3, 4, 5] } },
2253
+ enforceClockIn: true, enforceClockOut: true, autoWakeEnabled: true,
2254
+ offHoursAction: 'pause', gracePeriodMinutes: 5, enabled: true
2255
+ };
2256
+
2257
+ var _schedForm = useState(defaultSchedForm);
2258
+ var schedForm = _schedForm[0]; var setSchedForm = _schedForm[1];
2242
2259
 
2243
2260
  var loadAll = function() {
2244
2261
  setLoading(true);
@@ -2248,7 +2265,8 @@ function WorkforceSection(props) {
2248
2265
  engineCall('/workforce/tasks/' + agentId).catch(function() { return []; }),
2249
2266
  engineCall('/workforce/clock-records/' + agentId).catch(function() { return []; })
2250
2267
  ]).then(function(results) {
2251
- setSchedule(results[0]);
2268
+ var sched = results[0]?.schedule || results[0];
2269
+ setSchedule(sched);
2252
2270
  setStatus(results[1]);
2253
2271
  setTasks(results[2]?.tasks || results[2] || []);
2254
2272
  setClockRecords(results[3]?.records || results[3] || []);
@@ -2258,6 +2276,69 @@ function WorkforceSection(props) {
2258
2276
 
2259
2277
  useEffect(function() { loadAll(); }, [agentId]);
2260
2278
 
2279
+ var startEdit = function() {
2280
+ if (schedule) {
2281
+ setSchedForm({
2282
+ agentId: agentId,
2283
+ timezone: schedule.timezone || 'UTC',
2284
+ scheduleType: schedule.scheduleType || 'standard',
2285
+ config: schedule.config || { standardHours: { start: '09:00', end: '17:00', daysOfWeek: [1, 2, 3, 4, 5] } },
2286
+ enforceClockIn: schedule.enforceClockIn ?? true,
2287
+ enforceClockOut: schedule.enforceClockOut ?? true,
2288
+ autoWakeEnabled: schedule.autoWakeEnabled ?? true,
2289
+ offHoursAction: schedule.offHoursAction || 'pause',
2290
+ gracePeriodMinutes: schedule.gracePeriodMinutes ?? 5,
2291
+ enabled: schedule.enabled ?? true
2292
+ });
2293
+ } else {
2294
+ setSchedForm(Object.assign({}, defaultSchedForm, { agentId: agentId }));
2295
+ }
2296
+ setEditing(true);
2297
+ };
2298
+
2299
+ var saveSchedule = function() {
2300
+ setSaving(true);
2301
+ var isUpdate = schedule && schedule.id;
2302
+ var method = isUpdate ? 'PUT' : 'POST';
2303
+ var url = isUpdate ? '/workforce/schedules/' + schedule.id : '/workforce/schedules';
2304
+ engineCall(url, { method: method, body: JSON.stringify(schedForm) })
2305
+ .then(function() { toast('Schedule saved', 'success'); setEditing(false); loadAll(); })
2306
+ .catch(function(err) { toast(err.message, 'error'); })
2307
+ .finally(function() { setSaving(false); });
2308
+ };
2309
+
2310
+ var deleteSchedule = function() {
2311
+ if (!schedule) return;
2312
+ engineCall('/workforce/schedules/' + agentId, { method: 'DELETE' })
2313
+ .then(function() { toast('Schedule removed', 'success'); setEditing(false); loadAll(); })
2314
+ .catch(function(err) { toast(err.message, 'error'); });
2315
+ };
2316
+
2317
+ var toggleDay = function(d) {
2318
+ var days = (schedForm.config?.standardHours?.daysOfWeek || []).slice();
2319
+ var idx = days.indexOf(d);
2320
+ if (idx >= 0) days.splice(idx, 1); else days.push(d);
2321
+ days.sort();
2322
+ setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { standardHours: Object.assign({}, schedForm.config?.standardHours, { daysOfWeek: days }) }) }));
2323
+ };
2324
+
2325
+ var addShift = function() {
2326
+ var shifts = (schedForm.config?.shifts || []).concat([{ name: '', start: '09:00', end: '17:00' }]);
2327
+ setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { shifts: shifts }) }));
2328
+ };
2329
+
2330
+ var updateShift = function(idx, key, val) {
2331
+ var shifts = (schedForm.config?.shifts || []).slice();
2332
+ shifts[idx] = Object.assign({}, shifts[idx], (function() { var o = {}; o[key] = val; return o; })());
2333
+ setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { shifts: shifts }) }));
2334
+ };
2335
+
2336
+ var removeShift = function(idx) {
2337
+ var shifts = (schedForm.config?.shifts || []).slice();
2338
+ shifts.splice(idx, 1);
2339
+ setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { shifts: shifts }) }));
2340
+ };
2341
+
2261
2342
  var clockIn = function() {
2262
2343
  engineCall('/workforce/clock-in/' + agentId, { method: 'POST' })
2263
2344
  .then(function() { toast('Agent clocked in', 'success'); loadAll(); })
@@ -2294,6 +2375,20 @@ function WorkforceSection(props) {
2294
2375
  .catch(function(err) { toast(err.message, 'error'); });
2295
2376
  };
2296
2377
 
2378
+ // Helper: format schedule hours display
2379
+ var formatHours = function(s) {
2380
+ if (!s) return '-';
2381
+ if (s.scheduleType === 'standard' && s.config?.standardHours) {
2382
+ return (s.config.standardHours.start || '09:00') + ' - ' + (s.config.standardHours.end || '17:00');
2383
+ }
2384
+ if (s.scheduleType === 'shift' && s.config?.shifts?.length) {
2385
+ return s.config.shifts.map(function(sh) { return (sh.name ? sh.name + ': ' : '') + sh.start + '-' + sh.end; }).join(', ');
2386
+ }
2387
+ return '-';
2388
+ };
2389
+
2390
+ var formatDays = function(days) { return days?.map(function(d) { return dayNames[d]; }).join(', ') || '-'; };
2391
+
2297
2392
  if (loading) {
2298
2393
  return h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } }, 'Loading workforce data...');
2299
2394
  }
@@ -2321,32 +2416,151 @@ function WorkforceSection(props) {
2321
2416
 
2322
2417
  // ─── Schedule Card ──────────────────────────────────
2323
2418
  h('div', { className: 'card', style: { marginBottom: 20 } },
2324
- h('div', { className: 'card-header' }, h('span', null, 'Schedule')),
2419
+ h('div', { className: 'card-header', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
2420
+ h('span', null, 'Schedule'),
2421
+ !editing && h('button', { className: 'btn btn-ghost btn-sm', onClick: startEdit }, I.edit(), schedule ? ' Edit' : ' Configure')
2422
+ ),
2325
2423
  h('div', { className: 'card-body' },
2326
- schedule
2327
- ? h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 } },
2328
- h('div', null,
2329
- h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Schedule Type'),
2330
- h('div', { style: { fontSize: 14, fontWeight: 600 } }, schedule.type || schedule.scheduleType || 'Standard')
2424
+ editing
2425
+ ? h(Fragment, null,
2426
+ // Schedule Type
2427
+ h('div', { className: 'form-group' },
2428
+ h('label', { className: 'form-label' }, 'Schedule Type'),
2429
+ h('div', { style: { display: 'flex', gap: 16 } },
2430
+ ['standard', 'shift', 'custom'].map(function(t) {
2431
+ return h('label', { key: t, style: { display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' } },
2432
+ h('input', { type: 'radio', name: 'schedTypeDetail', checked: schedForm.scheduleType === t, onChange: function() { setSchedForm(Object.assign({}, schedForm, { scheduleType: t })); } }),
2433
+ t.charAt(0).toUpperCase() + t.slice(1)
2434
+ );
2435
+ })
2436
+ )
2331
2437
  ),
2332
- h('div', null,
2333
- h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Hours'),
2334
- h('div', { style: { fontSize: 14, fontWeight: 600 } }, schedule.hours || schedule.workHours || '-')
2438
+ // Standard fields
2439
+ schedForm.scheduleType === 'standard' && h(Fragment, null,
2440
+ h('div', { style: { display: 'flex', gap: 12 } },
2441
+ h('div', { className: 'form-group', style: { flex: 1 } },
2442
+ h('label', { className: 'form-label' }, 'Start Time'),
2443
+ h('input', { className: 'input', type: 'time', value: schedForm.config?.standardHours?.start || '09:00', onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { standardHours: Object.assign({}, schedForm.config?.standardHours, { start: e.target.value }) }) })); } })
2444
+ ),
2445
+ h('div', { className: 'form-group', style: { flex: 1 } },
2446
+ h('label', { className: 'form-label' }, 'End Time'),
2447
+ h('input', { className: 'input', type: 'time', value: schedForm.config?.standardHours?.end || '17:00', onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { standardHours: Object.assign({}, schedForm.config?.standardHours, { end: e.target.value }) }) })); } })
2448
+ )
2449
+ ),
2450
+ h('div', { className: 'form-group' },
2451
+ h('label', { className: 'form-label' }, 'Days of Week'),
2452
+ h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap' } },
2453
+ [0, 1, 2, 3, 4, 5, 6].map(function(d) {
2454
+ return h('button', {
2455
+ key: d, type: 'button',
2456
+ className: 'btn btn-sm ' + ((schedForm.config?.standardHours?.daysOfWeek || []).includes(d) ? 'btn-primary' : 'btn-ghost'),
2457
+ onClick: function() { toggleDay(d); }
2458
+ }, dayNames[d]);
2459
+ })
2460
+ )
2461
+ )
2335
2462
  ),
2336
- h('div', null,
2337
- h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Timezone'),
2338
- h('div', { style: { fontSize: 14, fontWeight: 600 } }, schedule.timezone || 'UTC')
2463
+ // Shift fields
2464
+ schedForm.scheduleType === 'shift' && h(Fragment, null,
2465
+ h('div', { className: 'form-group' },
2466
+ h('label', { className: 'form-label' }, 'Shifts'),
2467
+ (schedForm.config?.shifts || []).map(function(sh, idx) {
2468
+ return h('div', { key: idx, style: { display: 'flex', gap: 8, alignItems: 'center', marginBottom: 8, padding: 8, background: 'var(--bg-tertiary)', borderRadius: 6 } },
2469
+ h('input', { className: 'input', style: { flex: 1 }, placeholder: 'Shift name', value: sh.name, onChange: function(e) { updateShift(idx, 'name', e.target.value); } }),
2470
+ h('input', { className: 'input', type: 'time', style: { width: 110 }, value: sh.start, onChange: function(e) { updateShift(idx, 'start', e.target.value); } }),
2471
+ h('input', { className: 'input', type: 'time', style: { width: 110 }, value: sh.end, onChange: function(e) { updateShift(idx, 'end', e.target.value); } }),
2472
+ h('button', { className: 'btn btn-ghost btn-icon btn-sm', onClick: function() { removeShift(idx); } }, I.x())
2473
+ );
2474
+ }),
2475
+ h('button', { className: 'btn btn-ghost btn-sm', onClick: addShift }, I.plus(), ' Add Shift')
2476
+ )
2339
2477
  ),
2340
- h('div', null,
2341
- h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Enforcement'),
2342
- h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap' } },
2343
- schedule.enforceStart != null && h('span', { className: schedule.enforceStart ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enforceStart ? 'Enforce Start' : 'Flexible Start'),
2344
- schedule.enforceEnd != null && h('span', { className: schedule.enforceEnd ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enforceEnd ? 'Enforce End' : 'Flexible End'),
2345
- schedule.enforceBreaks != null && h('span', { className: schedule.enforceBreaks ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enforceBreaks ? 'Enforce Breaks' : 'Flexible Breaks')
2478
+ // Timezone
2479
+ h('div', { className: 'form-group' },
2480
+ h('label', { className: 'form-label' }, 'Timezone'),
2481
+ TimezoneSelect(h, schedForm.timezone, function(e) { setSchedForm(Object.assign({}, schedForm, { timezone: e.target.value })); })
2482
+ ),
2483
+ // Toggles
2484
+ h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginTop: 8 } },
2485
+ h('label', { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 13 } },
2486
+ h('input', { type: 'checkbox', checked: schedForm.enforceClockIn, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { enforceClockIn: e.target.checked })); } }),
2487
+ 'Enforce Clock-In'
2488
+ ),
2489
+ h('label', { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 13 } },
2490
+ h('input', { type: 'checkbox', checked: schedForm.enforceClockOut, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { enforceClockOut: e.target.checked })); } }),
2491
+ 'Enforce Clock-Out'
2492
+ ),
2493
+ h('label', { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 13 } },
2494
+ h('input', { type: 'checkbox', checked: schedForm.autoWakeEnabled, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { autoWakeEnabled: e.target.checked })); } }),
2495
+ 'Auto-Wake'
2496
+ ),
2497
+ h('label', { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 13 } },
2498
+ h('input', { type: 'checkbox', checked: schedForm.enabled, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { enabled: e.target.checked })); } }),
2499
+ 'Enabled'
2500
+ )
2501
+ ),
2502
+ // Off-hours + grace
2503
+ h('div', { style: { display: 'flex', gap: 12, marginTop: 12 } },
2504
+ h('div', { className: 'form-group', style: { flex: 1 } },
2505
+ h('label', { className: 'form-label' }, 'Off-Hours Action'),
2506
+ h('select', { className: 'input', value: schedForm.offHoursAction, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { offHoursAction: e.target.value })); } },
2507
+ h('option', { value: 'pause' }, 'Pause'), h('option', { value: 'stop' }, 'Stop'), h('option', { value: 'queue' }, 'Queue'))
2508
+ ),
2509
+ h('div', { className: 'form-group', style: { flex: 1 } },
2510
+ h('label', { className: 'form-label' }, 'Grace Period (min)'),
2511
+ h('input', { className: 'input', type: 'number', value: schedForm.gracePeriodMinutes, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { gracePeriodMinutes: parseInt(e.target.value) || 0 })); } })
2346
2512
  )
2513
+ ),
2514
+ // Actions
2515
+ h('div', { style: { display: 'flex', gap: 8, marginTop: 16, justifyContent: 'space-between' } },
2516
+ h('div', { style: { display: 'flex', gap: 8 } },
2517
+ h('button', { className: 'btn btn-primary', disabled: saving, onClick: saveSchedule }, saving ? 'Saving...' : 'Save Schedule'),
2518
+ h('button', { className: 'btn btn-ghost', onClick: function() { setEditing(false); } }, 'Cancel')
2519
+ ),
2520
+ schedule && h('button', { className: 'btn btn-ghost', style: { color: 'var(--danger)' }, onClick: deleteSchedule }, I.x(), ' Remove Schedule')
2347
2521
  )
2348
2522
  )
2349
- : h('div', { style: { textAlign: 'center', padding: 20, color: 'var(--text-muted)', fontSize: 13 } }, 'No schedule configured.')
2523
+ : schedule
2524
+ ? h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 } },
2525
+ h('div', null,
2526
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Schedule Type'),
2527
+ h('div', { style: { fontSize: 14, fontWeight: 600 } }, (schedule.scheduleType || 'standard').charAt(0).toUpperCase() + (schedule.scheduleType || 'standard').slice(1))
2528
+ ),
2529
+ h('div', null,
2530
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Hours'),
2531
+ h('div', { style: { fontSize: 14, fontWeight: 600 } }, formatHours(schedule))
2532
+ ),
2533
+ h('div', null,
2534
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Timezone'),
2535
+ h('div', { style: { fontSize: 14, fontWeight: 600 } }, schedule.timezone || 'UTC')
2536
+ ),
2537
+ h('div', null,
2538
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Days'),
2539
+ h('div', { style: { fontSize: 14, fontWeight: 600 } }, formatDays(schedule.config?.standardHours?.daysOfWeek))
2540
+ ),
2541
+ h('div', null,
2542
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Enforcement'),
2543
+ h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap' } },
2544
+ h('span', { className: schedule.enforceClockIn ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enforceClockIn ? 'Clock-In Enforced' : 'Clock-In Flexible'),
2545
+ h('span', { className: schedule.enforceClockOut ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enforceClockOut ? 'Clock-Out Enforced' : 'Clock-Out Flexible')
2546
+ )
2547
+ ),
2548
+ h('div', null,
2549
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Off-Hours'),
2550
+ h('div', { style: { fontSize: 14, fontWeight: 600 } }, (schedule.offHoursAction || 'pause').charAt(0).toUpperCase() + (schedule.offHoursAction || 'pause').slice(1) + (schedule.gracePeriodMinutes ? ' (' + schedule.gracePeriodMinutes + 'min grace)' : ''))
2551
+ ),
2552
+ h('div', null,
2553
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Status'),
2554
+ h('div', { style: { display: 'flex', gap: 6 } },
2555
+ h('span', { className: schedule.enabled ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enabled ? 'Enabled' : 'Disabled'),
2556
+ schedule.autoWakeEnabled && h('span', { className: 'badge badge-info' }, 'Auto-Wake')
2557
+ )
2558
+ )
2559
+ )
2560
+ : h('div', { style: { textAlign: 'center', padding: 20, color: 'var(--text-muted)', fontSize: 13 } },
2561
+ 'No schedule configured. ',
2562
+ h('button', { className: 'btn btn-ghost btn-sm', onClick: startEdit }, 'Configure now')
2563
+ )
2350
2564
  )
2351
2565
  ),
2352
2566
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/enterprise",
3
- "version": "0.5.88",
3
+ "version": "0.5.89",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,6 @@
1
1
  import { h, useState, useEffect, useCallback, Fragment, useApp, apiCall, engineCall, formatUptime, buildAgentDataMap, renderAgentBadge, showConfirm, getOrgId } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
+ import { TimezoneSelect } from '../components/timezones.js';
3
4
  import { DetailModal } from '../components/modal.js';
4
5
  import { CULTURES, LANGUAGES, DEFAULT_TRAITS, computeAge, PersonaForm } from '../components/persona-fields.js';
5
6
  import { TagInput } from '../components/tag-input.js';
@@ -2239,6 +2240,22 @@ function WorkforceSection(props) {
2239
2240
  var showAddTask = _showAddTask[0]; var setShowAddTask = _showAddTask[1];
2240
2241
  var _taskForm = useState({ title: '', description: '', priority: 'normal', type: 'general' });
2241
2242
  var taskForm = _taskForm[0]; var setTaskForm = _taskForm[1];
2243
+ var _editing = useState(false);
2244
+ var editing = _editing[0]; var setEditing = _editing[1];
2245
+ var _saving = useState(false);
2246
+ var saving = _saving[0]; var setSaving = _saving[1];
2247
+
2248
+ var dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
2249
+
2250
+ var defaultSchedForm = {
2251
+ agentId: agentId, timezone: 'UTC', scheduleType: 'standard',
2252
+ config: { standardHours: { start: '09:00', end: '17:00', daysOfWeek: [1, 2, 3, 4, 5] } },
2253
+ enforceClockIn: true, enforceClockOut: true, autoWakeEnabled: true,
2254
+ offHoursAction: 'pause', gracePeriodMinutes: 5, enabled: true
2255
+ };
2256
+
2257
+ var _schedForm = useState(defaultSchedForm);
2258
+ var schedForm = _schedForm[0]; var setSchedForm = _schedForm[1];
2242
2259
 
2243
2260
  var loadAll = function() {
2244
2261
  setLoading(true);
@@ -2248,7 +2265,8 @@ function WorkforceSection(props) {
2248
2265
  engineCall('/workforce/tasks/' + agentId).catch(function() { return []; }),
2249
2266
  engineCall('/workforce/clock-records/' + agentId).catch(function() { return []; })
2250
2267
  ]).then(function(results) {
2251
- setSchedule(results[0]);
2268
+ var sched = results[0]?.schedule || results[0];
2269
+ setSchedule(sched);
2252
2270
  setStatus(results[1]);
2253
2271
  setTasks(results[2]?.tasks || results[2] || []);
2254
2272
  setClockRecords(results[3]?.records || results[3] || []);
@@ -2258,6 +2276,69 @@ function WorkforceSection(props) {
2258
2276
 
2259
2277
  useEffect(function() { loadAll(); }, [agentId]);
2260
2278
 
2279
+ var startEdit = function() {
2280
+ if (schedule) {
2281
+ setSchedForm({
2282
+ agentId: agentId,
2283
+ timezone: schedule.timezone || 'UTC',
2284
+ scheduleType: schedule.scheduleType || 'standard',
2285
+ config: schedule.config || { standardHours: { start: '09:00', end: '17:00', daysOfWeek: [1, 2, 3, 4, 5] } },
2286
+ enforceClockIn: schedule.enforceClockIn ?? true,
2287
+ enforceClockOut: schedule.enforceClockOut ?? true,
2288
+ autoWakeEnabled: schedule.autoWakeEnabled ?? true,
2289
+ offHoursAction: schedule.offHoursAction || 'pause',
2290
+ gracePeriodMinutes: schedule.gracePeriodMinutes ?? 5,
2291
+ enabled: schedule.enabled ?? true
2292
+ });
2293
+ } else {
2294
+ setSchedForm(Object.assign({}, defaultSchedForm, { agentId: agentId }));
2295
+ }
2296
+ setEditing(true);
2297
+ };
2298
+
2299
+ var saveSchedule = function() {
2300
+ setSaving(true);
2301
+ var isUpdate = schedule && schedule.id;
2302
+ var method = isUpdate ? 'PUT' : 'POST';
2303
+ var url = isUpdate ? '/workforce/schedules/' + schedule.id : '/workforce/schedules';
2304
+ engineCall(url, { method: method, body: JSON.stringify(schedForm) })
2305
+ .then(function() { toast('Schedule saved', 'success'); setEditing(false); loadAll(); })
2306
+ .catch(function(err) { toast(err.message, 'error'); })
2307
+ .finally(function() { setSaving(false); });
2308
+ };
2309
+
2310
+ var deleteSchedule = function() {
2311
+ if (!schedule) return;
2312
+ engineCall('/workforce/schedules/' + agentId, { method: 'DELETE' })
2313
+ .then(function() { toast('Schedule removed', 'success'); setEditing(false); loadAll(); })
2314
+ .catch(function(err) { toast(err.message, 'error'); });
2315
+ };
2316
+
2317
+ var toggleDay = function(d) {
2318
+ var days = (schedForm.config?.standardHours?.daysOfWeek || []).slice();
2319
+ var idx = days.indexOf(d);
2320
+ if (idx >= 0) days.splice(idx, 1); else days.push(d);
2321
+ days.sort();
2322
+ setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { standardHours: Object.assign({}, schedForm.config?.standardHours, { daysOfWeek: days }) }) }));
2323
+ };
2324
+
2325
+ var addShift = function() {
2326
+ var shifts = (schedForm.config?.shifts || []).concat([{ name: '', start: '09:00', end: '17:00' }]);
2327
+ setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { shifts: shifts }) }));
2328
+ };
2329
+
2330
+ var updateShift = function(idx, key, val) {
2331
+ var shifts = (schedForm.config?.shifts || []).slice();
2332
+ shifts[idx] = Object.assign({}, shifts[idx], (function() { var o = {}; o[key] = val; return o; })());
2333
+ setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { shifts: shifts }) }));
2334
+ };
2335
+
2336
+ var removeShift = function(idx) {
2337
+ var shifts = (schedForm.config?.shifts || []).slice();
2338
+ shifts.splice(idx, 1);
2339
+ setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { shifts: shifts }) }));
2340
+ };
2341
+
2261
2342
  var clockIn = function() {
2262
2343
  engineCall('/workforce/clock-in/' + agentId, { method: 'POST' })
2263
2344
  .then(function() { toast('Agent clocked in', 'success'); loadAll(); })
@@ -2294,6 +2375,20 @@ function WorkforceSection(props) {
2294
2375
  .catch(function(err) { toast(err.message, 'error'); });
2295
2376
  };
2296
2377
 
2378
+ // Helper: format schedule hours display
2379
+ var formatHours = function(s) {
2380
+ if (!s) return '-';
2381
+ if (s.scheduleType === 'standard' && s.config?.standardHours) {
2382
+ return (s.config.standardHours.start || '09:00') + ' - ' + (s.config.standardHours.end || '17:00');
2383
+ }
2384
+ if (s.scheduleType === 'shift' && s.config?.shifts?.length) {
2385
+ return s.config.shifts.map(function(sh) { return (sh.name ? sh.name + ': ' : '') + sh.start + '-' + sh.end; }).join(', ');
2386
+ }
2387
+ return '-';
2388
+ };
2389
+
2390
+ var formatDays = function(days) { return days?.map(function(d) { return dayNames[d]; }).join(', ') || '-'; };
2391
+
2297
2392
  if (loading) {
2298
2393
  return h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } }, 'Loading workforce data...');
2299
2394
  }
@@ -2321,32 +2416,151 @@ function WorkforceSection(props) {
2321
2416
 
2322
2417
  // ─── Schedule Card ──────────────────────────────────
2323
2418
  h('div', { className: 'card', style: { marginBottom: 20 } },
2324
- h('div', { className: 'card-header' }, h('span', null, 'Schedule')),
2419
+ h('div', { className: 'card-header', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
2420
+ h('span', null, 'Schedule'),
2421
+ !editing && h('button', { className: 'btn btn-ghost btn-sm', onClick: startEdit }, I.edit(), schedule ? ' Edit' : ' Configure')
2422
+ ),
2325
2423
  h('div', { className: 'card-body' },
2326
- schedule
2327
- ? h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 } },
2328
- h('div', null,
2329
- h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Schedule Type'),
2330
- h('div', { style: { fontSize: 14, fontWeight: 600 } }, schedule.type || schedule.scheduleType || 'Standard')
2424
+ editing
2425
+ ? h(Fragment, null,
2426
+ // Schedule Type
2427
+ h('div', { className: 'form-group' },
2428
+ h('label', { className: 'form-label' }, 'Schedule Type'),
2429
+ h('div', { style: { display: 'flex', gap: 16 } },
2430
+ ['standard', 'shift', 'custom'].map(function(t) {
2431
+ return h('label', { key: t, style: { display: 'flex', alignItems: 'center', gap: 4, cursor: 'pointer' } },
2432
+ h('input', { type: 'radio', name: 'schedTypeDetail', checked: schedForm.scheduleType === t, onChange: function() { setSchedForm(Object.assign({}, schedForm, { scheduleType: t })); } }),
2433
+ t.charAt(0).toUpperCase() + t.slice(1)
2434
+ );
2435
+ })
2436
+ )
2331
2437
  ),
2332
- h('div', null,
2333
- h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Hours'),
2334
- h('div', { style: { fontSize: 14, fontWeight: 600 } }, schedule.hours || schedule.workHours || '-')
2438
+ // Standard fields
2439
+ schedForm.scheduleType === 'standard' && h(Fragment, null,
2440
+ h('div', { style: { display: 'flex', gap: 12 } },
2441
+ h('div', { className: 'form-group', style: { flex: 1 } },
2442
+ h('label', { className: 'form-label' }, 'Start Time'),
2443
+ h('input', { className: 'input', type: 'time', value: schedForm.config?.standardHours?.start || '09:00', onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { standardHours: Object.assign({}, schedForm.config?.standardHours, { start: e.target.value }) }) })); } })
2444
+ ),
2445
+ h('div', { className: 'form-group', style: { flex: 1 } },
2446
+ h('label', { className: 'form-label' }, 'End Time'),
2447
+ h('input', { className: 'input', type: 'time', value: schedForm.config?.standardHours?.end || '17:00', onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { config: Object.assign({}, schedForm.config, { standardHours: Object.assign({}, schedForm.config?.standardHours, { end: e.target.value }) }) })); } })
2448
+ )
2449
+ ),
2450
+ h('div', { className: 'form-group' },
2451
+ h('label', { className: 'form-label' }, 'Days of Week'),
2452
+ h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap' } },
2453
+ [0, 1, 2, 3, 4, 5, 6].map(function(d) {
2454
+ return h('button', {
2455
+ key: d, type: 'button',
2456
+ className: 'btn btn-sm ' + ((schedForm.config?.standardHours?.daysOfWeek || []).includes(d) ? 'btn-primary' : 'btn-ghost'),
2457
+ onClick: function() { toggleDay(d); }
2458
+ }, dayNames[d]);
2459
+ })
2460
+ )
2461
+ )
2335
2462
  ),
2336
- h('div', null,
2337
- h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Timezone'),
2338
- h('div', { style: { fontSize: 14, fontWeight: 600 } }, schedule.timezone || 'UTC')
2463
+ // Shift fields
2464
+ schedForm.scheduleType === 'shift' && h(Fragment, null,
2465
+ h('div', { className: 'form-group' },
2466
+ h('label', { className: 'form-label' }, 'Shifts'),
2467
+ (schedForm.config?.shifts || []).map(function(sh, idx) {
2468
+ return h('div', { key: idx, style: { display: 'flex', gap: 8, alignItems: 'center', marginBottom: 8, padding: 8, background: 'var(--bg-tertiary)', borderRadius: 6 } },
2469
+ h('input', { className: 'input', style: { flex: 1 }, placeholder: 'Shift name', value: sh.name, onChange: function(e) { updateShift(idx, 'name', e.target.value); } }),
2470
+ h('input', { className: 'input', type: 'time', style: { width: 110 }, value: sh.start, onChange: function(e) { updateShift(idx, 'start', e.target.value); } }),
2471
+ h('input', { className: 'input', type: 'time', style: { width: 110 }, value: sh.end, onChange: function(e) { updateShift(idx, 'end', e.target.value); } }),
2472
+ h('button', { className: 'btn btn-ghost btn-icon btn-sm', onClick: function() { removeShift(idx); } }, I.x())
2473
+ );
2474
+ }),
2475
+ h('button', { className: 'btn btn-ghost btn-sm', onClick: addShift }, I.plus(), ' Add Shift')
2476
+ )
2339
2477
  ),
2340
- h('div', null,
2341
- h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Enforcement'),
2342
- h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap' } },
2343
- schedule.enforceStart != null && h('span', { className: schedule.enforceStart ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enforceStart ? 'Enforce Start' : 'Flexible Start'),
2344
- schedule.enforceEnd != null && h('span', { className: schedule.enforceEnd ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enforceEnd ? 'Enforce End' : 'Flexible End'),
2345
- schedule.enforceBreaks != null && h('span', { className: schedule.enforceBreaks ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enforceBreaks ? 'Enforce Breaks' : 'Flexible Breaks')
2478
+ // Timezone
2479
+ h('div', { className: 'form-group' },
2480
+ h('label', { className: 'form-label' }, 'Timezone'),
2481
+ TimezoneSelect(h, schedForm.timezone, function(e) { setSchedForm(Object.assign({}, schedForm, { timezone: e.target.value })); })
2482
+ ),
2483
+ // Toggles
2484
+ h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginTop: 8 } },
2485
+ h('label', { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 13 } },
2486
+ h('input', { type: 'checkbox', checked: schedForm.enforceClockIn, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { enforceClockIn: e.target.checked })); } }),
2487
+ 'Enforce Clock-In'
2488
+ ),
2489
+ h('label', { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 13 } },
2490
+ h('input', { type: 'checkbox', checked: schedForm.enforceClockOut, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { enforceClockOut: e.target.checked })); } }),
2491
+ 'Enforce Clock-Out'
2492
+ ),
2493
+ h('label', { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 13 } },
2494
+ h('input', { type: 'checkbox', checked: schedForm.autoWakeEnabled, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { autoWakeEnabled: e.target.checked })); } }),
2495
+ 'Auto-Wake'
2496
+ ),
2497
+ h('label', { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 13 } },
2498
+ h('input', { type: 'checkbox', checked: schedForm.enabled, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { enabled: e.target.checked })); } }),
2499
+ 'Enabled'
2500
+ )
2501
+ ),
2502
+ // Off-hours + grace
2503
+ h('div', { style: { display: 'flex', gap: 12, marginTop: 12 } },
2504
+ h('div', { className: 'form-group', style: { flex: 1 } },
2505
+ h('label', { className: 'form-label' }, 'Off-Hours Action'),
2506
+ h('select', { className: 'input', value: schedForm.offHoursAction, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { offHoursAction: e.target.value })); } },
2507
+ h('option', { value: 'pause' }, 'Pause'), h('option', { value: 'stop' }, 'Stop'), h('option', { value: 'queue' }, 'Queue'))
2508
+ ),
2509
+ h('div', { className: 'form-group', style: { flex: 1 } },
2510
+ h('label', { className: 'form-label' }, 'Grace Period (min)'),
2511
+ h('input', { className: 'input', type: 'number', value: schedForm.gracePeriodMinutes, onChange: function(e) { setSchedForm(Object.assign({}, schedForm, { gracePeriodMinutes: parseInt(e.target.value) || 0 })); } })
2346
2512
  )
2513
+ ),
2514
+ // Actions
2515
+ h('div', { style: { display: 'flex', gap: 8, marginTop: 16, justifyContent: 'space-between' } },
2516
+ h('div', { style: { display: 'flex', gap: 8 } },
2517
+ h('button', { className: 'btn btn-primary', disabled: saving, onClick: saveSchedule }, saving ? 'Saving...' : 'Save Schedule'),
2518
+ h('button', { className: 'btn btn-ghost', onClick: function() { setEditing(false); } }, 'Cancel')
2519
+ ),
2520
+ schedule && h('button', { className: 'btn btn-ghost', style: { color: 'var(--danger)' }, onClick: deleteSchedule }, I.x(), ' Remove Schedule')
2347
2521
  )
2348
2522
  )
2349
- : h('div', { style: { textAlign: 'center', padding: 20, color: 'var(--text-muted)', fontSize: 13 } }, 'No schedule configured.')
2523
+ : schedule
2524
+ ? h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 } },
2525
+ h('div', null,
2526
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Schedule Type'),
2527
+ h('div', { style: { fontSize: 14, fontWeight: 600 } }, (schedule.scheduleType || 'standard').charAt(0).toUpperCase() + (schedule.scheduleType || 'standard').slice(1))
2528
+ ),
2529
+ h('div', null,
2530
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Hours'),
2531
+ h('div', { style: { fontSize: 14, fontWeight: 600 } }, formatHours(schedule))
2532
+ ),
2533
+ h('div', null,
2534
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Timezone'),
2535
+ h('div', { style: { fontSize: 14, fontWeight: 600 } }, schedule.timezone || 'UTC')
2536
+ ),
2537
+ h('div', null,
2538
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Days'),
2539
+ h('div', { style: { fontSize: 14, fontWeight: 600 } }, formatDays(schedule.config?.standardHours?.daysOfWeek))
2540
+ ),
2541
+ h('div', null,
2542
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Enforcement'),
2543
+ h('div', { style: { display: 'flex', gap: 6, flexWrap: 'wrap' } },
2544
+ h('span', { className: schedule.enforceClockIn ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enforceClockIn ? 'Clock-In Enforced' : 'Clock-In Flexible'),
2545
+ h('span', { className: schedule.enforceClockOut ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enforceClockOut ? 'Clock-Out Enforced' : 'Clock-Out Flexible')
2546
+ )
2547
+ ),
2548
+ h('div', null,
2549
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Off-Hours'),
2550
+ h('div', { style: { fontSize: 14, fontWeight: 600 } }, (schedule.offHoursAction || 'pause').charAt(0).toUpperCase() + (schedule.offHoursAction || 'pause').slice(1) + (schedule.gracePeriodMinutes ? ' (' + schedule.gracePeriodMinutes + 'min grace)' : ''))
2551
+ ),
2552
+ h('div', null,
2553
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Status'),
2554
+ h('div', { style: { display: 'flex', gap: 6 } },
2555
+ h('span', { className: schedule.enabled ? 'badge badge-success' : 'badge badge-neutral' }, schedule.enabled ? 'Enabled' : 'Disabled'),
2556
+ schedule.autoWakeEnabled && h('span', { className: 'badge badge-info' }, 'Auto-Wake')
2557
+ )
2558
+ )
2559
+ )
2560
+ : h('div', { style: { textAlign: 'center', padding: 20, color: 'var(--text-muted)', fontSize: 13 } },
2561
+ 'No schedule configured. ',
2562
+ h('button', { className: 'btn btn-ghost btn-sm', onClick: startEdit }, 'Configure now')
2563
+ )
2350
2564
  )
2351
2565
  ),
2352
2566