@agenticmail/enterprise 0.5.46 → 0.5.47

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.
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Comprehensive IANA timezone list grouped by region.
3
+ * Used for timezone dropdown selectors across the dashboard.
4
+ */
5
+
6
+ export const TIMEZONE_GROUPS = {
7
+ 'US & Canada': [
8
+ 'America/New_York',
9
+ 'America/Chicago',
10
+ 'America/Denver',
11
+ 'America/Los_Angeles',
12
+ 'America/Anchorage',
13
+ 'Pacific/Honolulu',
14
+ 'America/Phoenix',
15
+ 'America/Indiana/Indianapolis',
16
+ 'America/Detroit',
17
+ 'America/Kentucky/Louisville',
18
+ 'America/Boise',
19
+ 'America/Juneau',
20
+ 'America/Adak',
21
+ 'America/Nome',
22
+ 'America/Sitka',
23
+ 'America/Yakutat',
24
+ 'America/Metlakatla',
25
+ 'America/Menominee',
26
+ 'America/North_Dakota/Center',
27
+ 'America/North_Dakota/New_Salem',
28
+ 'America/North_Dakota/Beulah',
29
+ 'America/Indiana/Knox',
30
+ 'America/Indiana/Marengo',
31
+ 'America/Indiana/Petersburg',
32
+ 'America/Indiana/Tell_City',
33
+ 'America/Indiana/Vevay',
34
+ 'America/Indiana/Vincennes',
35
+ 'America/Indiana/Winamac',
36
+ 'America/Kentucky/Monticello',
37
+ 'America/Toronto',
38
+ 'America/Vancouver',
39
+ 'America/Winnipeg',
40
+ 'America/Edmonton',
41
+ 'America/Halifax',
42
+ 'America/Regina',
43
+ 'America/St_Johns',
44
+ 'America/Yellowknife',
45
+ 'America/Whitehorse',
46
+ 'America/Iqaluit',
47
+ 'America/Moncton',
48
+ 'America/Thunder_Bay',
49
+ ],
50
+ 'Central & South America': [
51
+ 'America/Mexico_City',
52
+ 'America/Cancun',
53
+ 'America/Tijuana',
54
+ 'America/Bogota',
55
+ 'America/Lima',
56
+ 'America/Santiago',
57
+ 'America/Buenos_Aires',
58
+ 'America/Sao_Paulo',
59
+ 'America/Caracas',
60
+ 'America/Guatemala',
61
+ 'America/Havana',
62
+ 'America/Jamaica',
63
+ 'America/Panama',
64
+ 'America/Costa_Rica',
65
+ 'America/El_Salvador',
66
+ 'America/Tegucigalpa',
67
+ 'America/Managua',
68
+ 'America/Guayaquil',
69
+ 'America/La_Paz',
70
+ 'America/Asuncion',
71
+ 'America/Montevideo',
72
+ 'America/Guyana',
73
+ 'America/Paramaribo',
74
+ 'America/Port-au-Prince',
75
+ 'America/Santo_Domingo',
76
+ 'America/Puerto_Rico',
77
+ 'America/Martinique',
78
+ 'America/Barbados',
79
+ 'America/Curacao',
80
+ 'America/Aruba',
81
+ 'America/Trinidad',
82
+ 'America/Cayenne',
83
+ 'America/Belize',
84
+ 'America/Recife',
85
+ 'America/Fortaleza',
86
+ 'America/Manaus',
87
+ 'America/Belem',
88
+ 'America/Cuiaba',
89
+ 'America/Campo_Grande',
90
+ 'America/Porto_Velho',
91
+ 'America/Rio_Branco',
92
+ 'America/Noronha',
93
+ 'America/Araguaina',
94
+ 'America/Bahia',
95
+ 'America/Maceio',
96
+ 'America/Cordoba',
97
+ 'America/Mendoza',
98
+ 'America/Punta_Arenas',
99
+ ],
100
+ 'Europe': [
101
+ 'UTC',
102
+ 'Europe/London',
103
+ 'Europe/Dublin',
104
+ 'Europe/Lisbon',
105
+ 'Europe/Paris',
106
+ 'Europe/Berlin',
107
+ 'Europe/Amsterdam',
108
+ 'Europe/Brussels',
109
+ 'Europe/Zurich',
110
+ 'Europe/Vienna',
111
+ 'Europe/Rome',
112
+ 'Europe/Madrid',
113
+ 'Europe/Barcelona',
114
+ 'Europe/Stockholm',
115
+ 'Europe/Oslo',
116
+ 'Europe/Copenhagen',
117
+ 'Europe/Helsinki',
118
+ 'Europe/Warsaw',
119
+ 'Europe/Prague',
120
+ 'Europe/Budapest',
121
+ 'Europe/Bucharest',
122
+ 'Europe/Sofia',
123
+ 'Europe/Athens',
124
+ 'Europe/Istanbul',
125
+ 'Europe/Moscow',
126
+ 'Europe/Kiev',
127
+ 'Europe/Minsk',
128
+ 'Europe/Riga',
129
+ 'Europe/Tallinn',
130
+ 'Europe/Vilnius',
131
+ 'Europe/Belgrade',
132
+ 'Europe/Zagreb',
133
+ 'Europe/Ljubljana',
134
+ 'Europe/Sarajevo',
135
+ 'Europe/Skopje',
136
+ 'Europe/Bratislava',
137
+ 'Europe/Luxembourg',
138
+ 'Europe/Malta',
139
+ 'Europe/Monaco',
140
+ 'Europe/Andorra',
141
+ 'Europe/Gibraltar',
142
+ 'Europe/Tirane',
143
+ 'Europe/Chisinau',
144
+ 'Europe/Samara',
145
+ 'Europe/Volgograd',
146
+ 'Europe/Kaliningrad',
147
+ 'Atlantic/Reykjavik',
148
+ 'Atlantic/Azores',
149
+ 'Atlantic/Canary',
150
+ 'Atlantic/Faroe',
151
+ 'Atlantic/Madeira',
152
+ ],
153
+ 'Africa': [
154
+ 'Africa/Cairo',
155
+ 'Africa/Lagos',
156
+ 'Africa/Nairobi',
157
+ 'Africa/Johannesburg',
158
+ 'Africa/Casablanca',
159
+ 'Africa/Accra',
160
+ 'Africa/Algiers',
161
+ 'Africa/Tunis',
162
+ 'Africa/Tripoli',
163
+ 'Africa/Khartoum',
164
+ 'Africa/Addis_Ababa',
165
+ 'Africa/Dar_es_Salaam',
166
+ 'Africa/Kampala',
167
+ 'Africa/Maputo',
168
+ 'Africa/Lusaka',
169
+ 'Africa/Harare',
170
+ 'Africa/Windhoek',
171
+ 'Africa/Luanda',
172
+ 'Africa/Kinshasa',
173
+ 'Africa/Lubumbashi',
174
+ 'Africa/Douala',
175
+ 'Africa/Libreville',
176
+ 'Africa/Brazzaville',
177
+ 'Africa/Malabo',
178
+ 'Africa/Niamey',
179
+ 'Africa/Ndjamena',
180
+ 'Africa/Bamako',
181
+ 'Africa/Nouakchott',
182
+ 'Africa/Dakar',
183
+ 'Africa/Conakry',
184
+ 'Africa/Freetown',
185
+ 'Africa/Monrovia',
186
+ 'Africa/Abidjan',
187
+ 'Africa/Lome',
188
+ 'Africa/Porto-Novo',
189
+ 'Africa/Ouagadougou',
190
+ 'Africa/Banjul',
191
+ 'Africa/Bissau',
192
+ 'Africa/Sao_Tome',
193
+ 'Africa/Asmara',
194
+ 'Africa/Djibouti',
195
+ 'Africa/Mogadishu',
196
+ 'Africa/Juba',
197
+ 'Africa/Kigali',
198
+ 'Africa/Bujumbura',
199
+ 'Africa/Blantyre',
200
+ 'Africa/Gaborone',
201
+ 'Africa/Maseru',
202
+ 'Africa/Mbabane',
203
+ 'Indian/Antananarivo',
204
+ 'Indian/Mauritius',
205
+ 'Indian/Reunion',
206
+ 'Indian/Mayotte',
207
+ 'Indian/Comoro',
208
+ 'Atlantic/Cape_Verde',
209
+ ],
210
+ 'Asia': [
211
+ 'Asia/Dubai',
212
+ 'Asia/Riyadh',
213
+ 'Asia/Qatar',
214
+ 'Asia/Kuwait',
215
+ 'Asia/Bahrain',
216
+ 'Asia/Muscat',
217
+ 'Asia/Tehran',
218
+ 'Asia/Baghdad',
219
+ 'Asia/Amman',
220
+ 'Asia/Beirut',
221
+ 'Asia/Damascus',
222
+ 'Asia/Jerusalem',
223
+ 'Asia/Karachi',
224
+ 'Asia/Kolkata',
225
+ 'Asia/Colombo',
226
+ 'Asia/Dhaka',
227
+ 'Asia/Kathmandu',
228
+ 'Asia/Almaty',
229
+ 'Asia/Tashkent',
230
+ 'Asia/Bishkek',
231
+ 'Asia/Tbilisi',
232
+ 'Asia/Baku',
233
+ 'Asia/Yerevan',
234
+ 'Asia/Kabul',
235
+ 'Asia/Ashgabat',
236
+ 'Asia/Dushanbe',
237
+ 'Asia/Rangoon',
238
+ 'Asia/Bangkok',
239
+ 'Asia/Jakarta',
240
+ 'Asia/Ho_Chi_Minh',
241
+ 'Asia/Phnom_Penh',
242
+ 'Asia/Vientiane',
243
+ 'Asia/Singapore',
244
+ 'Asia/Kuala_Lumpur',
245
+ 'Asia/Brunei',
246
+ 'Asia/Manila',
247
+ 'Asia/Shanghai',
248
+ 'Asia/Hong_Kong',
249
+ 'Asia/Macau',
250
+ 'Asia/Taipei',
251
+ 'Asia/Seoul',
252
+ 'Asia/Tokyo',
253
+ 'Asia/Pyongyang',
254
+ 'Asia/Ulaanbaatar',
255
+ 'Asia/Vladivostok',
256
+ 'Asia/Irkutsk',
257
+ 'Asia/Krasnoyarsk',
258
+ 'Asia/Novosibirsk',
259
+ 'Asia/Omsk',
260
+ 'Asia/Yekaterinburg',
261
+ 'Asia/Magadan',
262
+ 'Asia/Kamchatka',
263
+ 'Asia/Sakhalin',
264
+ 'Asia/Yakutsk',
265
+ 'Asia/Chita',
266
+ 'Asia/Srednekolymsk',
267
+ 'Asia/Anadyr',
268
+ ],
269
+ 'Australia & Pacific': [
270
+ 'Australia/Sydney',
271
+ 'Australia/Melbourne',
272
+ 'Australia/Brisbane',
273
+ 'Australia/Perth',
274
+ 'Australia/Adelaide',
275
+ 'Australia/Hobart',
276
+ 'Australia/Darwin',
277
+ 'Australia/Lord_Howe',
278
+ 'Pacific/Auckland',
279
+ 'Pacific/Fiji',
280
+ 'Pacific/Chatham',
281
+ 'Pacific/Tongatapu',
282
+ 'Pacific/Apia',
283
+ 'Pacific/Noumea',
284
+ 'Pacific/Port_Moresby',
285
+ 'Pacific/Guadalcanal',
286
+ 'Pacific/Efate',
287
+ 'Pacific/Tarawa',
288
+ 'Pacific/Majuro',
289
+ 'Pacific/Kwajalein',
290
+ 'Pacific/Pago_Pago',
291
+ 'Pacific/Guam',
292
+ 'Pacific/Palau',
293
+ 'Pacific/Kosrae',
294
+ 'Pacific/Pohnpei',
295
+ 'Pacific/Chuuk',
296
+ 'Pacific/Wake',
297
+ 'Pacific/Midway',
298
+ 'Pacific/Gambier',
299
+ 'Pacific/Marquesas',
300
+ 'Pacific/Tahiti',
301
+ 'Pacific/Pitcairn',
302
+ 'Pacific/Easter',
303
+ 'Pacific/Galapagos',
304
+ 'Pacific/Rarotonga',
305
+ 'Pacific/Niue',
306
+ 'Pacific/Norfolk',
307
+ 'Indian/Maldives',
308
+ 'Indian/Chagos',
309
+ 'Indian/Cocos',
310
+ 'Indian/Christmas',
311
+ ],
312
+ };
313
+
314
+ /**
315
+ * Flat list of all timezones for simple iteration.
316
+ */
317
+ export const ALL_TIMEZONES = Object.values(TIMEZONE_GROUPS).flat();
318
+
319
+ /**
320
+ * Get a human-friendly label for a timezone.
321
+ * e.g. "America/New_York" → "America/New York (EST)"
322
+ */
323
+ export function getTimezoneLabel(tz) {
324
+ try {
325
+ const now = new Date();
326
+ const formatter = new Intl.DateTimeFormat('en-US', { timeZone: tz, timeZoneName: 'short' });
327
+ const parts = formatter.formatToParts(now);
328
+ const abbr = parts.find(p => p.type === 'timeZoneName')?.value || '';
329
+ const display = tz.replace(/_/g, ' ');
330
+ return abbr ? `${display} (${abbr})` : display;
331
+ } catch {
332
+ return tz.replace(/_/g, ' ');
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Render a timezone <select> using Preact's h().
338
+ * @param {Function} h - Preact createElement
339
+ * @param {string} value - Current selected timezone
340
+ * @param {Function} onChange - Change handler (receives event)
341
+ * @param {object} [props] - Additional props for the <select> element
342
+ */
343
+ export function TimezoneSelect(h, value, onChange, props = {}) {
344
+ return h('select', { className: 'input', value, onChange, ...props },
345
+ h('option', { value: '' }, '-- Select Timezone --'),
346
+ Object.entries(TIMEZONE_GROUPS).map(([group, tzs]) =>
347
+ h('optgroup', { key: group, label: group },
348
+ tzs.map(tz => h('option', { key: tz, value: tz }, getTimezoneLabel(tz)))
349
+ )
350
+ )
351
+ );
352
+ }
@@ -1,5 +1,6 @@
1
1
  import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, buildAgentEmailMap, resolveAgentEmail, buildAgentDataMap, renderAgentBadge, getOrgId } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
+ import { TimezoneSelect } from '../components/timezones.js';
3
4
 
4
5
  export function WorkforcePage() {
5
6
  const { toast } = useApp();
@@ -275,14 +276,27 @@ export function WorkforcePage() {
275
276
  ? h('tr', null, h('td', { colSpan: 5, style: { textAlign: 'center', color: 'var(--text-muted)', padding: 40 } }, 'No agents found'))
276
277
  : status.agents.map(a => h('tr', { key: a.agentId },
277
278
  h('td', null, renderAgentBadge(a.agentId || a.id, agentData)),
278
- h('td', null, statusBadge(a.status)),
279
+ h('td', null, statusBadge(a.clockStatus || a.status)),
279
280
  h('td', null, a.schedule
280
- ? (a.schedule.type || 'Standard') + ': ' + (a.schedule.start || '-') + ' - ' + (a.schedule.end || '-')
281
+ ? h(Fragment, null,
282
+ schedTypeBadge(a.schedule.scheduleType || a.schedule.type || 'standard'),
283
+ ' ',
284
+ a.schedule.scheduleType === 'standard' && a.schedule.config?.standardHours
285
+ ? (a.schedule.config.standardHours.start || '09:00') + ' - ' + (a.schedule.config.standardHours.end || '17:00')
286
+ : a.schedule.scheduleType === 'shift' && a.schedule.config?.shifts?.length
287
+ ? a.schedule.config.shifts[0].start + ' - ' + a.schedule.config.shifts[0].end
288
+ : (a.schedule.start || '-') + ' - ' + (a.schedule.end || '-')
289
+ )
281
290
  : h('span', { style: { color: 'var(--text-muted)' } }, 'None')),
282
- h('td', null, a.nextEvent ? formatTime(a.nextEvent) : '-'),
291
+ h('td', null, a.nextEvent
292
+ ? h(Fragment, null,
293
+ h('span', { className: 'badge', style: { background: a.nextEvent.type === 'clock_in' ? 'var(--success)' : 'var(--warning)', color: '#fff', marginRight: 4 } }, a.nextEvent.type === 'clock_in' ? 'Clock In' : 'Clock Out'),
294
+ formatTime(a.nextEvent.at)
295
+ )
296
+ : '-'),
283
297
  h('td', { style: { display: 'flex', gap: 4 } },
284
- a.status !== 'clocked_in' && h('button', { className: 'btn btn-ghost btn-sm', onClick: () => handleClockIn(a.agentId) }, I.play(), ' Clock In'),
285
- a.status === 'clocked_in' && h('button', { className: 'btn btn-ghost btn-sm', onClick: () => handleClockOut(a.agentId) }, I.pause(), ' Clock Out')
298
+ (a.clockStatus || a.status) !== 'clocked_in' && h('button', { className: 'btn btn-ghost btn-sm', onClick: () => handleClockIn(a.agentId || a.id) }, I.play(), ' Clock In'),
299
+ (a.clockStatus || a.status) === 'clocked_in' && h('button', { className: 'btn btn-ghost btn-sm', onClick: () => handleClockOut(a.agentId || a.id) }, I.pause(), ' Clock Out')
286
300
  )
287
301
  ))
288
302
  )
@@ -525,7 +539,7 @@ export function WorkforcePage() {
525
539
  // Timezone
526
540
  h('div', { className: 'form-group' },
527
541
  h('label', { className: 'form-label' }, 'Timezone'),
528
- h('input', { className: 'input', value: schedForm.timezone, onChange: e => setSchedForm({ ...schedForm, timezone: e.target.value }), placeholder: 'UTC' })
542
+ TimezoneSelect(h, schedForm.timezone, e => setSchedForm({ ...schedForm, timezone: e.target.value }))
529
543
  ),
530
544
  // Toggles
531
545
  h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 } },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/enterprise",
3
- "version": "0.5.46",
3
+ "version": "0.5.47",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Comprehensive IANA timezone list grouped by region.
3
+ * Used for timezone dropdown selectors across the dashboard.
4
+ */
5
+
6
+ export const TIMEZONE_GROUPS = {
7
+ 'US & Canada': [
8
+ 'America/New_York',
9
+ 'America/Chicago',
10
+ 'America/Denver',
11
+ 'America/Los_Angeles',
12
+ 'America/Anchorage',
13
+ 'Pacific/Honolulu',
14
+ 'America/Phoenix',
15
+ 'America/Indiana/Indianapolis',
16
+ 'America/Detroit',
17
+ 'America/Kentucky/Louisville',
18
+ 'America/Boise',
19
+ 'America/Juneau',
20
+ 'America/Adak',
21
+ 'America/Nome',
22
+ 'America/Sitka',
23
+ 'America/Yakutat',
24
+ 'America/Metlakatla',
25
+ 'America/Menominee',
26
+ 'America/North_Dakota/Center',
27
+ 'America/North_Dakota/New_Salem',
28
+ 'America/North_Dakota/Beulah',
29
+ 'America/Indiana/Knox',
30
+ 'America/Indiana/Marengo',
31
+ 'America/Indiana/Petersburg',
32
+ 'America/Indiana/Tell_City',
33
+ 'America/Indiana/Vevay',
34
+ 'America/Indiana/Vincennes',
35
+ 'America/Indiana/Winamac',
36
+ 'America/Kentucky/Monticello',
37
+ 'America/Toronto',
38
+ 'America/Vancouver',
39
+ 'America/Winnipeg',
40
+ 'America/Edmonton',
41
+ 'America/Halifax',
42
+ 'America/Regina',
43
+ 'America/St_Johns',
44
+ 'America/Yellowknife',
45
+ 'America/Whitehorse',
46
+ 'America/Iqaluit',
47
+ 'America/Moncton',
48
+ 'America/Thunder_Bay',
49
+ ],
50
+ 'Central & South America': [
51
+ 'America/Mexico_City',
52
+ 'America/Cancun',
53
+ 'America/Tijuana',
54
+ 'America/Bogota',
55
+ 'America/Lima',
56
+ 'America/Santiago',
57
+ 'America/Buenos_Aires',
58
+ 'America/Sao_Paulo',
59
+ 'America/Caracas',
60
+ 'America/Guatemala',
61
+ 'America/Havana',
62
+ 'America/Jamaica',
63
+ 'America/Panama',
64
+ 'America/Costa_Rica',
65
+ 'America/El_Salvador',
66
+ 'America/Tegucigalpa',
67
+ 'America/Managua',
68
+ 'America/Guayaquil',
69
+ 'America/La_Paz',
70
+ 'America/Asuncion',
71
+ 'America/Montevideo',
72
+ 'America/Guyana',
73
+ 'America/Paramaribo',
74
+ 'America/Port-au-Prince',
75
+ 'America/Santo_Domingo',
76
+ 'America/Puerto_Rico',
77
+ 'America/Martinique',
78
+ 'America/Barbados',
79
+ 'America/Curacao',
80
+ 'America/Aruba',
81
+ 'America/Trinidad',
82
+ 'America/Cayenne',
83
+ 'America/Belize',
84
+ 'America/Recife',
85
+ 'America/Fortaleza',
86
+ 'America/Manaus',
87
+ 'America/Belem',
88
+ 'America/Cuiaba',
89
+ 'America/Campo_Grande',
90
+ 'America/Porto_Velho',
91
+ 'America/Rio_Branco',
92
+ 'America/Noronha',
93
+ 'America/Araguaina',
94
+ 'America/Bahia',
95
+ 'America/Maceio',
96
+ 'America/Cordoba',
97
+ 'America/Mendoza',
98
+ 'America/Punta_Arenas',
99
+ ],
100
+ 'Europe': [
101
+ 'UTC',
102
+ 'Europe/London',
103
+ 'Europe/Dublin',
104
+ 'Europe/Lisbon',
105
+ 'Europe/Paris',
106
+ 'Europe/Berlin',
107
+ 'Europe/Amsterdam',
108
+ 'Europe/Brussels',
109
+ 'Europe/Zurich',
110
+ 'Europe/Vienna',
111
+ 'Europe/Rome',
112
+ 'Europe/Madrid',
113
+ 'Europe/Barcelona',
114
+ 'Europe/Stockholm',
115
+ 'Europe/Oslo',
116
+ 'Europe/Copenhagen',
117
+ 'Europe/Helsinki',
118
+ 'Europe/Warsaw',
119
+ 'Europe/Prague',
120
+ 'Europe/Budapest',
121
+ 'Europe/Bucharest',
122
+ 'Europe/Sofia',
123
+ 'Europe/Athens',
124
+ 'Europe/Istanbul',
125
+ 'Europe/Moscow',
126
+ 'Europe/Kiev',
127
+ 'Europe/Minsk',
128
+ 'Europe/Riga',
129
+ 'Europe/Tallinn',
130
+ 'Europe/Vilnius',
131
+ 'Europe/Belgrade',
132
+ 'Europe/Zagreb',
133
+ 'Europe/Ljubljana',
134
+ 'Europe/Sarajevo',
135
+ 'Europe/Skopje',
136
+ 'Europe/Bratislava',
137
+ 'Europe/Luxembourg',
138
+ 'Europe/Malta',
139
+ 'Europe/Monaco',
140
+ 'Europe/Andorra',
141
+ 'Europe/Gibraltar',
142
+ 'Europe/Tirane',
143
+ 'Europe/Chisinau',
144
+ 'Europe/Samara',
145
+ 'Europe/Volgograd',
146
+ 'Europe/Kaliningrad',
147
+ 'Atlantic/Reykjavik',
148
+ 'Atlantic/Azores',
149
+ 'Atlantic/Canary',
150
+ 'Atlantic/Faroe',
151
+ 'Atlantic/Madeira',
152
+ ],
153
+ 'Africa': [
154
+ 'Africa/Cairo',
155
+ 'Africa/Lagos',
156
+ 'Africa/Nairobi',
157
+ 'Africa/Johannesburg',
158
+ 'Africa/Casablanca',
159
+ 'Africa/Accra',
160
+ 'Africa/Algiers',
161
+ 'Africa/Tunis',
162
+ 'Africa/Tripoli',
163
+ 'Africa/Khartoum',
164
+ 'Africa/Addis_Ababa',
165
+ 'Africa/Dar_es_Salaam',
166
+ 'Africa/Kampala',
167
+ 'Africa/Maputo',
168
+ 'Africa/Lusaka',
169
+ 'Africa/Harare',
170
+ 'Africa/Windhoek',
171
+ 'Africa/Luanda',
172
+ 'Africa/Kinshasa',
173
+ 'Africa/Lubumbashi',
174
+ 'Africa/Douala',
175
+ 'Africa/Libreville',
176
+ 'Africa/Brazzaville',
177
+ 'Africa/Malabo',
178
+ 'Africa/Niamey',
179
+ 'Africa/Ndjamena',
180
+ 'Africa/Bamako',
181
+ 'Africa/Nouakchott',
182
+ 'Africa/Dakar',
183
+ 'Africa/Conakry',
184
+ 'Africa/Freetown',
185
+ 'Africa/Monrovia',
186
+ 'Africa/Abidjan',
187
+ 'Africa/Lome',
188
+ 'Africa/Porto-Novo',
189
+ 'Africa/Ouagadougou',
190
+ 'Africa/Banjul',
191
+ 'Africa/Bissau',
192
+ 'Africa/Sao_Tome',
193
+ 'Africa/Asmara',
194
+ 'Africa/Djibouti',
195
+ 'Africa/Mogadishu',
196
+ 'Africa/Juba',
197
+ 'Africa/Kigali',
198
+ 'Africa/Bujumbura',
199
+ 'Africa/Blantyre',
200
+ 'Africa/Gaborone',
201
+ 'Africa/Maseru',
202
+ 'Africa/Mbabane',
203
+ 'Indian/Antananarivo',
204
+ 'Indian/Mauritius',
205
+ 'Indian/Reunion',
206
+ 'Indian/Mayotte',
207
+ 'Indian/Comoro',
208
+ 'Atlantic/Cape_Verde',
209
+ ],
210
+ 'Asia': [
211
+ 'Asia/Dubai',
212
+ 'Asia/Riyadh',
213
+ 'Asia/Qatar',
214
+ 'Asia/Kuwait',
215
+ 'Asia/Bahrain',
216
+ 'Asia/Muscat',
217
+ 'Asia/Tehran',
218
+ 'Asia/Baghdad',
219
+ 'Asia/Amman',
220
+ 'Asia/Beirut',
221
+ 'Asia/Damascus',
222
+ 'Asia/Jerusalem',
223
+ 'Asia/Karachi',
224
+ 'Asia/Kolkata',
225
+ 'Asia/Colombo',
226
+ 'Asia/Dhaka',
227
+ 'Asia/Kathmandu',
228
+ 'Asia/Almaty',
229
+ 'Asia/Tashkent',
230
+ 'Asia/Bishkek',
231
+ 'Asia/Tbilisi',
232
+ 'Asia/Baku',
233
+ 'Asia/Yerevan',
234
+ 'Asia/Kabul',
235
+ 'Asia/Ashgabat',
236
+ 'Asia/Dushanbe',
237
+ 'Asia/Rangoon',
238
+ 'Asia/Bangkok',
239
+ 'Asia/Jakarta',
240
+ 'Asia/Ho_Chi_Minh',
241
+ 'Asia/Phnom_Penh',
242
+ 'Asia/Vientiane',
243
+ 'Asia/Singapore',
244
+ 'Asia/Kuala_Lumpur',
245
+ 'Asia/Brunei',
246
+ 'Asia/Manila',
247
+ 'Asia/Shanghai',
248
+ 'Asia/Hong_Kong',
249
+ 'Asia/Macau',
250
+ 'Asia/Taipei',
251
+ 'Asia/Seoul',
252
+ 'Asia/Tokyo',
253
+ 'Asia/Pyongyang',
254
+ 'Asia/Ulaanbaatar',
255
+ 'Asia/Vladivostok',
256
+ 'Asia/Irkutsk',
257
+ 'Asia/Krasnoyarsk',
258
+ 'Asia/Novosibirsk',
259
+ 'Asia/Omsk',
260
+ 'Asia/Yekaterinburg',
261
+ 'Asia/Magadan',
262
+ 'Asia/Kamchatka',
263
+ 'Asia/Sakhalin',
264
+ 'Asia/Yakutsk',
265
+ 'Asia/Chita',
266
+ 'Asia/Srednekolymsk',
267
+ 'Asia/Anadyr',
268
+ ],
269
+ 'Australia & Pacific': [
270
+ 'Australia/Sydney',
271
+ 'Australia/Melbourne',
272
+ 'Australia/Brisbane',
273
+ 'Australia/Perth',
274
+ 'Australia/Adelaide',
275
+ 'Australia/Hobart',
276
+ 'Australia/Darwin',
277
+ 'Australia/Lord_Howe',
278
+ 'Pacific/Auckland',
279
+ 'Pacific/Fiji',
280
+ 'Pacific/Chatham',
281
+ 'Pacific/Tongatapu',
282
+ 'Pacific/Apia',
283
+ 'Pacific/Noumea',
284
+ 'Pacific/Port_Moresby',
285
+ 'Pacific/Guadalcanal',
286
+ 'Pacific/Efate',
287
+ 'Pacific/Tarawa',
288
+ 'Pacific/Majuro',
289
+ 'Pacific/Kwajalein',
290
+ 'Pacific/Pago_Pago',
291
+ 'Pacific/Guam',
292
+ 'Pacific/Palau',
293
+ 'Pacific/Kosrae',
294
+ 'Pacific/Pohnpei',
295
+ 'Pacific/Chuuk',
296
+ 'Pacific/Wake',
297
+ 'Pacific/Midway',
298
+ 'Pacific/Gambier',
299
+ 'Pacific/Marquesas',
300
+ 'Pacific/Tahiti',
301
+ 'Pacific/Pitcairn',
302
+ 'Pacific/Easter',
303
+ 'Pacific/Galapagos',
304
+ 'Pacific/Rarotonga',
305
+ 'Pacific/Niue',
306
+ 'Pacific/Norfolk',
307
+ 'Indian/Maldives',
308
+ 'Indian/Chagos',
309
+ 'Indian/Cocos',
310
+ 'Indian/Christmas',
311
+ ],
312
+ };
313
+
314
+ /**
315
+ * Flat list of all timezones for simple iteration.
316
+ */
317
+ export const ALL_TIMEZONES = Object.values(TIMEZONE_GROUPS).flat();
318
+
319
+ /**
320
+ * Get a human-friendly label for a timezone.
321
+ * e.g. "America/New_York" → "America/New York (EST)"
322
+ */
323
+ export function getTimezoneLabel(tz) {
324
+ try {
325
+ const now = new Date();
326
+ const formatter = new Intl.DateTimeFormat('en-US', { timeZone: tz, timeZoneName: 'short' });
327
+ const parts = formatter.formatToParts(now);
328
+ const abbr = parts.find(p => p.type === 'timeZoneName')?.value || '';
329
+ const display = tz.replace(/_/g, ' ');
330
+ return abbr ? `${display} (${abbr})` : display;
331
+ } catch {
332
+ return tz.replace(/_/g, ' ');
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Render a timezone <select> using Preact's h().
338
+ * @param {Function} h - Preact createElement
339
+ * @param {string} value - Current selected timezone
340
+ * @param {Function} onChange - Change handler (receives event)
341
+ * @param {object} [props] - Additional props for the <select> element
342
+ */
343
+ export function TimezoneSelect(h, value, onChange, props = {}) {
344
+ return h('select', { className: 'input', value, onChange, ...props },
345
+ h('option', { value: '' }, '-- Select Timezone --'),
346
+ Object.entries(TIMEZONE_GROUPS).map(([group, tzs]) =>
347
+ h('optgroup', { key: group, label: group },
348
+ tzs.map(tz => h('option', { key: tz, value: tz }, getTimezoneLabel(tz)))
349
+ )
350
+ )
351
+ );
352
+ }
@@ -1,5 +1,6 @@
1
1
  import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, buildAgentEmailMap, resolveAgentEmail, buildAgentDataMap, renderAgentBadge, getOrgId } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
+ import { TimezoneSelect } from '../components/timezones.js';
3
4
 
4
5
  export function WorkforcePage() {
5
6
  const { toast } = useApp();
@@ -275,14 +276,27 @@ export function WorkforcePage() {
275
276
  ? h('tr', null, h('td', { colSpan: 5, style: { textAlign: 'center', color: 'var(--text-muted)', padding: 40 } }, 'No agents found'))
276
277
  : status.agents.map(a => h('tr', { key: a.agentId },
277
278
  h('td', null, renderAgentBadge(a.agentId || a.id, agentData)),
278
- h('td', null, statusBadge(a.status)),
279
+ h('td', null, statusBadge(a.clockStatus || a.status)),
279
280
  h('td', null, a.schedule
280
- ? (a.schedule.type || 'Standard') + ': ' + (a.schedule.start || '-') + ' - ' + (a.schedule.end || '-')
281
+ ? h(Fragment, null,
282
+ schedTypeBadge(a.schedule.scheduleType || a.schedule.type || 'standard'),
283
+ ' ',
284
+ a.schedule.scheduleType === 'standard' && a.schedule.config?.standardHours
285
+ ? (a.schedule.config.standardHours.start || '09:00') + ' - ' + (a.schedule.config.standardHours.end || '17:00')
286
+ : a.schedule.scheduleType === 'shift' && a.schedule.config?.shifts?.length
287
+ ? a.schedule.config.shifts[0].start + ' - ' + a.schedule.config.shifts[0].end
288
+ : (a.schedule.start || '-') + ' - ' + (a.schedule.end || '-')
289
+ )
281
290
  : h('span', { style: { color: 'var(--text-muted)' } }, 'None')),
282
- h('td', null, a.nextEvent ? formatTime(a.nextEvent) : '-'),
291
+ h('td', null, a.nextEvent
292
+ ? h(Fragment, null,
293
+ h('span', { className: 'badge', style: { background: a.nextEvent.type === 'clock_in' ? 'var(--success)' : 'var(--warning)', color: '#fff', marginRight: 4 } }, a.nextEvent.type === 'clock_in' ? 'Clock In' : 'Clock Out'),
294
+ formatTime(a.nextEvent.at)
295
+ )
296
+ : '-'),
283
297
  h('td', { style: { display: 'flex', gap: 4 } },
284
- a.status !== 'clocked_in' && h('button', { className: 'btn btn-ghost btn-sm', onClick: () => handleClockIn(a.agentId) }, I.play(), ' Clock In'),
285
- a.status === 'clocked_in' && h('button', { className: 'btn btn-ghost btn-sm', onClick: () => handleClockOut(a.agentId) }, I.pause(), ' Clock Out')
298
+ (a.clockStatus || a.status) !== 'clocked_in' && h('button', { className: 'btn btn-ghost btn-sm', onClick: () => handleClockIn(a.agentId || a.id) }, I.play(), ' Clock In'),
299
+ (a.clockStatus || a.status) === 'clocked_in' && h('button', { className: 'btn btn-ghost btn-sm', onClick: () => handleClockOut(a.agentId || a.id) }, I.pause(), ' Clock Out')
286
300
  )
287
301
  ))
288
302
  )
@@ -525,7 +539,7 @@ export function WorkforcePage() {
525
539
  // Timezone
526
540
  h('div', { className: 'form-group' },
527
541
  h('label', { className: 'form-label' }, 'Timezone'),
528
- h('input', { className: 'input', value: schedForm.timezone, onChange: e => setSchedForm({ ...schedForm, timezone: e.target.value }), placeholder: 'UTC' })
542
+ TimezoneSelect(h, schedForm.timezone, e => setSchedForm({ ...schedForm, timezone: e.target.value }))
529
543
  ),
530
544
  // Toggles
531
545
  h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 } },