@7365admin1/layer-common 3.2.2-staging.120 → 3.2.2-staging.122

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,63 +1,26 @@
1
+ <!--
2
+ HID INTERCOM MANAGEMENT - the readers' door intercoms and their contact list.
3
+
4
+ The largest of the six panels, and it was a private copy of the standard
5
+ table: an above-card toolbar, a `v-tabs` strip, a raw `v-table` with TWO
6
+ column sets, a refresh strip with a pager, a SECOND pager in the footer, an
7
+ empty state and its own chip palette - in about a hundred and forty literal
8
+ hexes. Those literals are why it had no dark theme: `#1f2937` on the dark
9
+ card measures 1.13:1, `white on #a7a7a7` 2.41:1 and the "Intercom" tab label
10
+ 2.54:1.
11
+
12
+ It is the design's standard table screen with tabs, so it is drawn with the
13
+ shared one: `TableMain` supplies the heading, the card, the in-card toolbar,
14
+ the `app-tab` strip, the header row, the rows and ONE pager. The two column
15
+ sets stay two column sets - `tableHeaders` and `visibleRows` switch on the
16
+ active tab exactly as the two `<thead>` blocks did.
17
+
18
+ Every dialog, handler, SIP call and web-phone control is unchanged. Only the
19
+ chrome and the colours move; no call, connect, hangup or device path is
20
+ touched.
21
+ -->
1
22
  <template>
2
23
  <section class="hid-intercom-management">
3
- <div class="top-bar">
4
- <v-btn
5
- v-if="activeTab === 'contacts'"
6
- rounded="xl"
7
- class="text-none add-button"
8
- variant="flat"
9
- prepend-icon="mdi-plus"
10
- @click="openContactDialog()"
11
- >
12
- Add Contacts
13
- </v-btn>
14
-
15
- <v-btn
16
- v-else
17
- rounded="xl"
18
- class="text-none web-call-button"
19
- :class="{ 'is-connected': webPhone.isRegistered.value, 'has-incoming-call': webPhone.callState.value === 'incoming' }"
20
- variant="flat"
21
- prepend-icon="mdi-phone-in-talk-outline"
22
- @click="openWebPhoneDialog()"
23
- >
24
- {{ webPhone.callState.value === 'incoming' ? 'Incoming Call' : 'Web Call' }}
25
- </v-btn>
26
-
27
- <v-select
28
- v-if="readers.length"
29
- v-model="selectedReaderId"
30
- :items="readerOptions"
31
- item-title="title"
32
- item-value="value"
33
- variant="outlined"
34
- density="compact"
35
- hide-details
36
- class="reader-input"
37
- @update:model-value="page = 1"
38
- />
39
-
40
- <div class="toolbar-spacer" />
41
-
42
- <v-text-field
43
- v-model="search"
44
- placeholder="Search"
45
- variant="outlined"
46
- density="compact"
47
- hide-details
48
- class="search-input"
49
- />
50
-
51
- <v-select
52
- v-model="statusFilter"
53
- :items="statusOptions"
54
- variant="outlined"
55
- density="compact"
56
- hide-details
57
- class="status-input"
58
- />
59
- </div>
60
-
61
24
  <v-alert
62
25
  v-if="message.text"
63
26
  :type="message.type"
@@ -68,109 +31,130 @@
68
31
  {{ message.text }}
69
32
  </v-alert>
70
33
 
71
- <v-card flat border class="table-card">
72
- <div class="table-refresh">
73
- <v-btn icon="mdi-refresh" variant="text" density="comfortable" :loading="loading" @click="reload" />
74
- <div class="toolbar-spacer" />
75
- <span>{{ pageRange }}</span>
76
- <v-btn icon="mdi-chevron-left" variant="text" density="compact" :disabled="page <= 1" @click="page--" />
77
- <v-btn icon="mdi-chevron-right" variant="text" density="compact" :disabled="page >= pages" @click="page++" />
78
- </div>
79
-
80
- <v-tabs v-model="activeTab" class="intercom-tabs" density="compact">
81
- <v-tab value="intercom" class="text-none">Intercom</v-tab>
82
- <v-tab value="contacts" class="text-none">Intercom Contacts</v-tab>
83
- </v-tabs>
84
-
85
- <div class="table-body">
86
- <v-table>
87
- <thead>
88
- <tr v-if="activeTab === 'intercom'">
89
- <th>Name</th>
90
- <th>Location</th>
91
- <th>SIP Server Address</th>
92
- <th>Operating Mode</th>
93
- <th>Dial Code</th>
94
- <th>SIP Status</th>
95
- <th class="text-right"></th>
96
- </tr>
97
- <tr v-else>
98
- <th>Name</th>
99
- <th>Location</th>
100
- <th>SIP Server Address</th>
101
- <th>Dial Code</th>
102
- <th class="text-right"></th>
103
- </tr>
104
- </thead>
105
- <tbody>
106
- <template v-if="activeTab === 'intercom'">
107
- <tr v-for="row in paginatedIntercomRows" :key="row.reader._id">
108
- <td>{{ row.name }}</td>
109
- <td>{{ row.location }}</td>
110
- <td>
111
- <span :class="{ 'link-text': row.serverAddress !== 'N/A' }">{{ row.serverAddress }}</span>
112
- </td>
113
- <td>{{ row.operatingMode }}</td>
114
- <td>{{ row.dialCode }}</td>
115
- <td>
116
- <v-chip size="small" variant="flat" :class="getStatusClass(row.status)">
117
- {{ row.status }}
118
- </v-chip>
119
- </td>
120
- <td class="text-right">
121
- <v-menu location="bottom end">
122
- <template #activator="{ props: menuProps }">
123
- <v-btn v-bind="menuProps" icon="mdi-dots-vertical" variant="text" density="comfortable" />
124
- </template>
125
- <v-list density="compact" class="row-menu">
126
- <v-list-item title="View" @click="openViewDialog(row)" />
127
- <v-list-item title="SIP Settings" @click="openSipDialog(row)" />
128
- <v-list-item title="Call Settings" @click="openCallDialog(row)" />
129
- <v-list-item title="Dial Code" @click="openDialCodeDialog(row)" />
130
- <v-list-item title="Make a call" @click="openMakeCallDialog(row)" />
131
- <v-list-item title="Web call" @click="openWebPhoneDialog(row)" />
132
- </v-list>
133
- </v-menu>
134
- </td>
135
- </tr>
136
- </template>
137
-
138
- <template v-else>
139
- <tr v-for="contact in paginatedContacts" :key="contact._rowKey">
140
- <td>{{ contact.name }}</td>
141
- <td>{{ contact.location }}</td>
142
- <td>
143
- <span :class="{ 'link-text': contact.serverAddress !== 'N/A' }">{{ contact.serverAddress }}</span>
144
- </td>
145
- <td>{{ contact.dialCode }}</td>
146
- <td class="text-right">
147
- <v-menu location="bottom end">
148
- <template #activator="{ props: menuProps }">
149
- <v-btn v-bind="menuProps" icon="mdi-dots-vertical" variant="text" density="comfortable" />
150
- </template>
151
- <v-list density="compact" class="row-menu">
152
- <v-list-item title="View" @click="openContactView(contact)" />
153
- <v-list-item title="Edit" @click="openContactDialog(contact)" />
154
- <v-list-item title="Delete" @click="openDeleteContact(contact)" />
155
- </v-list>
156
- </v-menu>
157
- </td>
158
- </tr>
159
- </template>
160
- </tbody>
161
- </v-table>
162
-
163
- <div v-if="!visibleRows.length" class="empty-state">
164
- No intercom records found.
34
+ <TableMain
35
+ title="Intercom"
36
+ :headers="tableHeaders"
37
+ :items="visiblePageRows"
38
+ :loading="loading"
39
+ :items-per-page="-1"
40
+ :page="page"
41
+ :pages="pages"
42
+ :page-range="pageRange"
43
+ item-value="_rowKey"
44
+ show-header
45
+ @refresh="reload"
46
+ @update:page="page = $event"
47
+ >
48
+ <!-- The action swaps with the tab, as it did in the old top bar. -->
49
+ <template #actions>
50
+ <AppButton
51
+ v-if="activeTab === 'contacts'"
52
+ icon="mdi-plus"
53
+ @click="openContactDialog()"
54
+ >
55
+ Add Contacts
56
+ </AppButton>
57
+ <AppButton
58
+ v-else
59
+ icon="mdi-phone-in-talk-outline"
60
+ class="web-call-button"
61
+ :class="webPhoneButtonClass"
62
+ @click="openWebPhoneDialog()"
63
+ >
64
+ {{ webPhone.callState.value === 'incoming' ? 'Incoming Call' : 'Web Call' }}
65
+ </AppButton>
66
+ </template>
67
+
68
+ <!-- Was `v-tabs`/`v-tab`, whose selected state is drawn from Vuetify's
69
+ `on-surface`. `.app-tab` is the shared strip. -->
70
+ <template #tabs>
71
+ <button
72
+ v-for="tab in tabOptions"
73
+ :key="tab.value"
74
+ type="button"
75
+ role="tab"
76
+ class="app-tab"
77
+ :class="{ 'app-tab--active': activeTab === tab.value }"
78
+ :aria-selected="activeTab === tab.value"
79
+ @click="activeTab = tab.value"
80
+ >
81
+ {{ tab.label }}
82
+ </button>
83
+ </template>
84
+
85
+ <template #extension>
86
+ <div class="app-filter-row hid-intercom-filters">
87
+ <!-- Staging (1e6224e) added a reader picker here and filters both
88
+ tabs by it; it keeps that job, drawn as the shared select. -->
89
+ <AppSelect
90
+ v-if="readers.length"
91
+ v-model="selectedReaderId"
92
+ class="reader-select"
93
+ :items="readerOptions"
94
+ @update:model-value="page = 1"
95
+ />
96
+ <AppField v-model="search" search placeholder="Search" />
97
+ <AppSelect v-model="statusFilter" :items="statusOptions" />
165
98
  </div>
166
- </div>
167
-
168
- <div class="table-footer">
169
- <span>{{ pageRange }}</span>
170
- <v-btn icon="mdi-chevron-left" variant="text" density="comfortable" :disabled="page <= 1" @click="page--" />
171
- <v-btn icon="mdi-chevron-right" variant="text" density="comfortable" :disabled="page >= pages" @click="page++" />
172
- </div>
173
- </v-card>
99
+ </template>
100
+
101
+ <template #[`item.name`]="{ item }">
102
+ <span class="app-cell--strong">{{ item.name }}</span>
103
+ </template>
104
+
105
+ <template #[`item.location`]="{ item }">
106
+ <span class="app-cell--muted">{{ item.location }}</span>
107
+ </template>
108
+
109
+ <template #[`item.serverAddress`]="{ item }">
110
+ <span :class="item.serverAddress !== 'N/A' ? 'link-text' : 'app-cell--muted'">
111
+ {{ item.serverAddress }}
112
+ </span>
113
+ </template>
114
+
115
+ <template #[`item.operatingMode`]="{ item }">
116
+ <span class="app-cell--muted">{{ item.operatingMode }}</span>
117
+ </template>
118
+
119
+ <template #[`item.dialCode`]="{ item }">
120
+ <span class="app-cell--num">{{ item.dialCode }}</span>
121
+ </template>
122
+
123
+ <!-- The shared chip. This screen's `getStatusClass` was another private
124
+ copy of the status-to-colour map. -->
125
+ <template #[`item.status`]="{ item }">
126
+ <StatusChip :status="item.status" />
127
+ </template>
128
+
129
+ <template #[`item.action-table`]="{ item }">
130
+ <v-menu location="bottom end">
131
+ <template #activator="{ props: menuProps }">
132
+ <AppButton v-bind="menuProps" variant="row" icon="mdi-dots-vertical" />
133
+ </template>
134
+ <v-list v-if="activeTab === 'intercom'" density="compact" class="row-menu">
135
+ <v-list-item title="View" @click="openViewDialog(item)" />
136
+ <v-list-item title="SIP Settings" @click="openSipDialog(item)" />
137
+ <v-list-item title="Call Settings" @click="openCallDialog(item)" />
138
+ <v-list-item title="Dial Code" @click="openDialCodeDialog(item)" />
139
+ <v-list-item title="Make a call" @click="openMakeCallDialog(item)" />
140
+ <v-list-item title="Web call" @click="openWebPhoneDialog(item)" />
141
+ </v-list>
142
+ <v-list v-else density="compact" class="row-menu">
143
+ <v-list-item title="View" @click="openContactView(item)" />
144
+ <v-list-item title="Edit" @click="openContactDialog(item)" />
145
+ <v-list-item title="Delete" @click="openDeleteContact(item)" />
146
+ </v-list>
147
+ </v-menu>
148
+ </template>
149
+
150
+ <!-- The screen's own sentence, kept. -->
151
+ <template #no-data>
152
+ <div class="table-card__empty">
153
+ <v-icon icon="mdi-deskphone" size="32" />
154
+ <span>No intercom records found.</span>
155
+ </div>
156
+ </template>
157
+ </TableMain>
174
158
 
175
159
  <v-dialog v-model="viewDialog" max-width="360">
176
160
  <v-card class="detail-dialog" rounded="lg">
@@ -200,43 +184,53 @@
200
184
  <v-card-text class="dialog-body">
201
185
  <div class="sip-switch-row">
202
186
  <span>Enable / Disable SIP</span>
203
- <v-switch v-model="sipForm.enabled" color="success" density="compact" hide-details />
187
+ <v-switch v-model="sipForm.enabled" class="app-switch" density="compact" hide-details />
204
188
  </div>
205
189
  <div class="sip-label">Server Address <span>*</span></div>
206
- <v-text-field v-model="sipForm.serverIp" :disabled="!sipForm.enabled" placeholder="SIP server address" variant="outlined" density="compact" hide-details="auto" class="sip-input" />
190
+ <AppField v-model="sipForm.serverIp" :disabled="!sipForm.enabled" placeholder="SIP server address" class="sip-input" />
207
191
  <div class="sip-label">Port <span>*</span></div>
208
- <v-text-field v-model="sipForm.serverPort" :disabled="!sipForm.enabled" placeholder="0000" variant="outlined" density="compact" hide-details="auto" class="sip-input" />
192
+ <AppField v-model="sipForm.serverPort" :disabled="!sipForm.enabled" placeholder="0000" class="sip-input" />
209
193
  <div class="form-two-col">
210
194
  <div>
211
195
  <div class="sip-label">Initial RTP Port <span>*</span></div>
212
- <v-text-field v-model="sipForm.initialRtpPort" :disabled="!sipForm.enabled" placeholder="0000" variant="outlined" density="compact" hide-details="auto" class="sip-input" />
196
+ <AppField v-model="sipForm.initialRtpPort" :disabled="!sipForm.enabled" placeholder="0000" class="sip-input" />
213
197
  </div>
214
198
  <div>
215
199
  <div class="sip-label">Final RTP Port <span>*</span></div>
216
- <v-text-field v-model="sipForm.finalRtpPort" :disabled="!sipForm.enabled" placeholder="0000" variant="outlined" density="compact" hide-details="auto" class="sip-input" />
200
+ <AppField v-model="sipForm.finalRtpPort" :disabled="!sipForm.enabled" placeholder="0000" class="sip-input" />
217
201
  </div>
218
202
  </div>
219
203
  <div class="sip-switch-row sip-section-switch">
220
204
  <span>Numeric Extension</span>
221
- <v-switch v-model="sipForm.numericExtension" :disabled="!sipForm.enabled" color="success" density="compact" hide-details />
205
+ <v-switch v-model="sipForm.numericExtension" :disabled="!sipForm.enabled" class="app-switch" density="compact" hide-details />
222
206
  </div>
223
207
  <div class="sip-label">Extension <span>*</span></div>
224
- <v-text-field v-model="sipForm.extension" :disabled="areSipCredentialFieldsDisabled" placeholder="0000" variant="outlined" density="compact" hide-details="auto" class="sip-input" />
208
+ <AppField v-model="sipForm.extension" :disabled="areSipCredentialFieldsDisabled" placeholder="0000" class="sip-input" />
225
209
  <div class="sip-label">Login <span>*</span></div>
226
- <v-text-field v-model="sipForm.login" :disabled="areSipCredentialFieldsDisabled" placeholder="Enter username" variant="outlined" density="compact" hide-details="auto" class="sip-input" />
210
+ <AppField v-model="sipForm.login" :disabled="areSipCredentialFieldsDisabled" placeholder="Enter username" class="sip-input" />
227
211
  <div class="sip-label">Password <span>*</span></div>
228
- <v-text-field
212
+ <!-- The reveal eye moves into `AppField`'s `trailing` slot - the same
213
+ slot panel 4's access PIN uses, which is why it is a shared slot
214
+ rather than a private hack in two screens. -->
215
+ <AppField
229
216
  v-model="sipForm.password"
217
+ :disabled="areSipCredentialFieldsDisabled"
230
218
  placeholder="Enter password"
231
219
  :type="showSipPassword ? 'text' : 'password'"
232
- :disabled="areSipCredentialFieldsDisabled"
233
- variant="outlined"
234
- density="compact"
235
- hide-details="auto"
220
+ autocomplete="new-password"
236
221
  class="sip-input"
237
- :append-inner-icon="showSipPassword ? 'mdi-eye-off' : 'mdi-eye'"
238
- @click:append-inner="showSipPassword = !showSipPassword"
239
- />
222
+ >
223
+ <template #trailing>
224
+ <button
225
+ type="button"
226
+ class="app-field__trailing"
227
+ :aria-label="showSipPassword ? 'Hide SIP password' : 'Show SIP password'"
228
+ @click="showSipPassword = !showSipPassword"
229
+ >
230
+ <v-icon :icon="showSipPassword ? 'mdi-eye-off' : 'mdi-eye'" size="18" />
231
+ </button>
232
+ </template>
233
+ </AppField>
240
234
  <div class="sip-label">Operating Mode <span>*</span></div>
241
235
  <v-btn-toggle v-model="sipForm.operatingMode" :disabled="!sipForm.enabled" mandatory divided class="mode-toggle">
242
236
  <v-btn value="sip-client" class="text-none">SIP Client</v-btn>
@@ -252,7 +246,7 @@
252
246
  <span>Enable video</span>
253
247
  <small>Allow this reader to send its camera feed during SIP intercom calls.</small>
254
248
  </div>
255
- <v-switch v-model="sipForm.videoEnabled" :disabled="!sipForm.enabled" color="success" density="compact" hide-details />
249
+ <v-switch v-model="sipForm.videoEnabled" :disabled="!sipForm.enabled" class="app-switch" density="compact" hide-details />
256
250
  </div>
257
251
  </v-card-text>
258
252
  <v-card-actions class="dialog-actions pa-0">
@@ -274,37 +268,37 @@
274
268
  </v-radio-group>
275
269
  <div class="call-toggle-row">
276
270
  <span>Automatic Answering</span>
277
- <v-switch v-model="callForm.autoAnswer" color="success" density="compact" hide-details />
271
+ <v-switch v-model="callForm.autoAnswer" class="app-switch" density="compact" hide-details />
278
272
  </div>
279
273
  <div class="call-label">Automatic answering delay (seconds)</div>
280
- <v-text-field v-model="callForm.autoAnswerDelay" :disabled="!callForm.autoAnswer" placeholder="0" variant="outlined" density="compact" hide-details="auto" class="call-input" />
274
+ <AppField v-model="callForm.autoAnswerDelay" :disabled="!callForm.autoAnswer" placeholder="0" class="call-input" />
281
275
  <div class="form-two-col call-two-col">
282
276
  <div>
283
277
  <div class="call-label">Registration time (seconds)</div>
284
- <v-text-field v-model="callForm.registrationTime" :disabled="!callForm.autoAnswer" placeholder="0" variant="outlined" density="compact" hide-details="auto" class="call-input" />
278
+ <AppField v-model="callForm.registrationTime" :disabled="!callForm.autoAnswer" placeholder="0" class="call-input" />
285
279
  </div>
286
280
  <div>
287
281
  <div class="call-label">Keep-alive (seconds)</div>
288
- <v-text-field v-model="callForm.keepAlive" :disabled="!callForm.autoAnswer" placeholder="0" variant="outlined" density="compact" hide-details="auto" class="call-input" />
282
+ <AppField v-model="callForm.keepAlive" :disabled="!callForm.autoAnswer" placeholder="0" class="call-input" />
289
283
  </div>
290
284
  </div>
291
285
  <div class="call-label">Maximum call time (seconds)</div>
292
- <v-text-field v-model="callForm.maxCallTime" :disabled="!callForm.autoAnswer" placeholder="0" variant="outlined" density="compact" hide-details="auto" class="call-input" />
286
+ <AppField v-model="callForm.maxCallTime" :disabled="!callForm.autoAnswer" placeholder="0" class="call-input" />
293
287
  <div class="call-toggle-row">
294
288
  <span>Dial Button</span>
295
- <v-switch v-model="callForm.dialButton" color="success" density="compact" hide-details />
289
+ <v-switch v-model="callForm.dialButton" class="app-switch" density="compact" hide-details />
296
290
  </div>
297
291
  <div class="call-toggle-row">
298
292
  <span>External Dial Button</span>
299
- <v-switch v-model="callForm.externalDialButton" color="success" density="compact" hide-details />
293
+ <v-switch v-model="callForm.externalDialButton" class="app-switch" density="compact" hide-details />
300
294
  </div>
301
295
  <div class="call-toggle-row">
302
296
  <span>Facial identification during the call</span>
303
- <v-switch v-model="callForm.faceDuringCall" color="success" density="compact" hide-details />
297
+ <v-switch v-model="callForm.faceDuringCall" class="app-switch" density="compact" hide-details />
304
298
  </div>
305
299
  <div class="call-toggle-row">
306
300
  <span>Custom Icon</span>
307
- <v-switch v-model="callForm.customIcon" color="success" density="compact" hide-details />
301
+ <v-switch v-model="callForm.customIcon" class="app-switch" density="compact" hide-details />
308
302
  </div>
309
303
  <div class="upload-row call-upload-row">
310
304
  <div class="upload-drop" :class="{ 'is-disabled': !callForm.customIcon }"><v-icon size="16">mdi-cloud-upload-outline</v-icon> Drag and drop files here</div>
@@ -316,10 +310,10 @@
316
310
  </div>
317
311
  <div class="call-toggle-row">
318
312
  <span>Automatic Call with unknown contact</span>
319
- <v-switch v-model="callForm.unknownContact" color="success" density="compact" hide-details />
313
+ <v-switch v-model="callForm.unknownContact" class="app-switch" density="compact" hide-details />
320
314
  </div>
321
315
  <div class="call-label">Call after (seconds)</div>
322
- <v-text-field v-model="callForm.callAfter" :disabled="!callForm.unknownContact" placeholder="0" variant="outlined" density="compact" hide-details="auto" class="call-input" />
316
+ <AppField v-model="callForm.callAfter" :disabled="!callForm.unknownContact" placeholder="0" class="call-input" />
323
317
  </v-card-text>
324
318
  <v-card-actions class="dialog-actions pa-0">
325
319
  <v-btn class="text-none action-cancel" variant="flat" height="44" @click="callDialog = false">Cancel</v-btn>
@@ -334,10 +328,10 @@
334
328
  <v-card-text class="dialog-body">
335
329
  <div class="call-toggle-row dial-toggle-row">
336
330
  <span>Dial Code</span>
337
- <v-switch v-model="dialForm.enabled" color="success" density="compact" hide-details />
331
+ <v-switch v-model="dialForm.enabled" class="app-switch" density="compact" hide-details />
338
332
  </div>
339
333
  <div class="call-label">Dial code to unlock</div>
340
- <v-text-field v-model="dialForm.code" :disabled="!dialForm.enabled" placeholder="0" variant="outlined" density="compact" hide-details="auto" class="call-input dial-input" />
334
+ <AppField v-model="dialForm.code" :disabled="!dialForm.enabled" placeholder="0" class="call-input dial-input" />
341
335
  </v-card-text>
342
336
  <v-card-actions class="dialog-actions pa-0">
343
337
  <v-btn class="text-none action-cancel" variant="flat" height="44" @click="dialCodeDialog = false">Cancel</v-btn>
@@ -352,15 +346,7 @@
352
346
  <v-card-text class="dialog-body make-call-body">
353
347
  <div class="make-call-field">
354
348
  <div class="call-label">Extension, SIP address, or peer IP</div>
355
- <v-text-field
356
- v-model="makeCallForm.target"
357
- placeholder="7100 or user@example.com"
358
- variant="outlined"
359
- density="compact"
360
- hide-details="auto"
361
- class="call-input"
362
- @keyup.enter="runMakeCall"
363
- />
349
+ <AppField v-model="makeCallForm.target" placeholder="7100 or user@example.com" @keyup.enter="runMakeCall" class="call-input" />
364
350
  </div>
365
351
  <div class="make-call-actions">
366
352
  <v-btn
@@ -446,7 +432,7 @@
446
432
  </div>
447
433
  <v-switch
448
434
  v-model="webPhoneForm.video"
449
- color="success"
435
+ class="app-switch"
450
436
  density="compact"
451
437
  hide-details
452
438
  :disabled="webPhone.isRegistered.value"
@@ -525,16 +511,7 @@
525
511
  <template v-else>
526
512
  <div class="call-label">Extension or SIP Address</div>
527
513
  <div class="web-phone-dial-row">
528
- <v-text-field
529
- v-model="webPhoneForm.destination"
530
- placeholder="1001 or device@example.com"
531
- variant="outlined"
532
- density="compact"
533
- hide-details="auto"
534
- class="call-input mb-0"
535
- :disabled="!webPhone.isRegistered.value || webPhone.hasCall.value"
536
- @keyup.enter="startWebCall"
537
- />
514
+ <AppField v-model="webPhoneForm.destination" :disabled="!webPhone.isRegistered.value || webPhone.hasCall.value" placeholder="1001 or device@example.com" @keyup.enter="startWebCall" class="call-input mb-0" />
538
515
  <v-btn
539
516
  icon="mdi-phone"
540
517
  class="start-web-call"
@@ -578,11 +555,11 @@
578
555
  <v-card-title class="dialog-title">{{ contactForm.id ? 'Edit Contact' : 'Add Contact' }}</v-card-title>
579
556
  <v-card-text class="dialog-body">
580
557
  <FieldLabel text="Name" required />
581
- <v-text-field v-model="contactForm.name" placeholder="Enter name" variant="outlined" density="compact" hide-details="auto" />
558
+ <AppField v-model="contactForm.name" placeholder="Enter name" />
582
559
  <FieldLabel text="Location" />
583
- <v-text-field v-model="contactForm.location" placeholder="Location" variant="outlined" density="compact" hide-details="auto" />
560
+ <AppField v-model="contactForm.location" placeholder="Location" />
584
561
  <FieldLabel text="Server Address" required />
585
- <v-text-field v-model="contactForm.serverAddress" placeholder="192.168.1.1" variant="outlined" density="compact" hide-details="auto" />
562
+ <AppField v-model="contactForm.serverAddress" placeholder="192.168.1.1" />
586
563
  </v-card-text>
587
564
  <v-card-actions class="dialog-actions pa-0">
588
565
  <v-btn class="text-none action-cancel" variant="flat" height="44" @click="contactDialog = false">Cancel</v-btn>
@@ -883,6 +860,59 @@ const filteredContacts = computed(() => {
883
860
 
884
861
  const paginatedIntercomRows = computed(() => paginate(filteredIntercomRows.value));
885
862
  const paginatedContacts = computed(() => paginate(filteredContacts.value));
863
+
864
+ /**
865
+ * The two tabs, as data rather than two `v-tab` elements, so the shared
866
+ * `app-tab` strip can render them. Same values, same order.
867
+ */
868
+ const tabOptions: Array<{ label: string; value: IntercomTab }> = [
869
+ { label: "Intercom", value: "intercom" },
870
+ { label: "Intercom Contacts", value: "contacts" },
871
+ ];
872
+
873
+ /**
874
+ * The screen has TWO column sets, and it keeps them - this is the same pair the
875
+ * two `<thead>` blocks drew. `sortable: false` preserves the raw `v-table`'s
876
+ * behaviour, and no `weight` is set, so the layout is `TableMain`'s automatic
877
+ * one: the handoff draws no HID table and states no proportions.
878
+ */
879
+ const tableHeaders = computed(() =>
880
+ activeTab.value === "intercom"
881
+ ? [
882
+ { title: "Name", value: "name", sortable: false },
883
+ { title: "Location", value: "location", sortable: false },
884
+ { title: "SIP Server Address", value: "serverAddress", sortable: false },
885
+ { title: "Operating Mode", value: "operatingMode", sortable: false },
886
+ { title: "Dial Code", value: "dialCode", sortable: false },
887
+ { title: "SIP Status", value: "status", sortable: false },
888
+ { title: "", value: "action-table", sortable: false },
889
+ ]
890
+ : [
891
+ { title: "Name", value: "name", sortable: false },
892
+ { title: "Location", value: "location", sortable: false },
893
+ { title: "SIP Server Address", value: "serverAddress", sortable: false },
894
+ { title: "Dial Code", value: "dialCode", sortable: false },
895
+ { title: "", value: "action-table", sortable: false },
896
+ ]
897
+ );
898
+
899
+ /**
900
+ * `TableMain` takes ONE item list, so the two `v-for` blocks become one. The
901
+ * intercom rows are keyed on their reader id (they have no `_rowKey` of their
902
+ * own, where contacts do), so one is supplied for the table's `item-value`.
903
+ */
904
+ const visiblePageRows = computed(() =>
905
+ activeTab.value === "intercom"
906
+ ? paginatedIntercomRows.value.map((row) => ({ ...row, _rowKey: row.reader?._id }))
907
+ : paginatedContacts.value
908
+ );
909
+
910
+ /** The web-call button turns green when registered and amber on an incoming
911
+ call - the same two states the old `.web-call-button` modifiers drew. */
912
+ const webPhoneButtonClass = computed(() => ({
913
+ "is-connected": webPhone.isRegistered.value,
914
+ "has-incoming-call": webPhone.callState.value === "incoming",
915
+ }));
886
916
  const isPeerToPeerMode = computed(() => sipForm.operatingMode === "peer-to-peer");
887
917
  const areSipCredentialFieldsDisabled = computed(() => !sipForm.enabled || isPeerToPeerMode.value);
888
918
  const intercomDetailSections = computed(() => {
@@ -1561,13 +1591,6 @@ async function deleteContact() {
1561
1591
  }
1562
1592
  }
1563
1593
 
1564
- function getStatusClass(status: string) {
1565
- if (status === "Connected") return "status-success";
1566
- if (status === "Disabled" || status === "Not connected") return "status-muted";
1567
- if (status === "Failed") return "status-error";
1568
- return "status-warning";
1569
- }
1570
-
1571
1594
  function getDialModeLabel(value: string) {
1572
1595
  if (value === "1") return "From the contact list";
1573
1596
  if (value === "2") return "Numeric Keypad + Contacts";
@@ -1661,7 +1684,7 @@ export default {
1661
1684
  <span>{{ label }}</span>
1662
1685
  <v-switch
1663
1686
  :model-value="modelValue"
1664
- color="success"
1687
+ class="app-switch"
1665
1688
  density="compact"
1666
1689
  hide-details
1667
1690
  @update:model-value="$emit('update:modelValue', $event)"
@@ -1680,138 +1703,66 @@ export default {
1680
1703
  padding: 12px;
1681
1704
  }
1682
1705
 
1683
- .top-bar {
1684
- display: flex;
1685
- align-items: center;
1686
- gap: 12px;
1687
- margin-bottom: 12px;
1688
- }
1689
1706
 
1690
1707
  .toolbar-spacer {
1691
1708
  flex: 1 1 auto;
1692
1709
  }
1693
1710
 
1694
- .top-placeholder {
1695
- width: 160px;
1696
- }
1697
1711
 
1698
- .web-call-button {
1699
- min-width: 160px;
1700
- background: #e5e7eb;
1701
- color: #0b2c40;
1702
- font-size: 12px;
1712
+ /*
1713
+ The filter row sits INSIDE the card's extension, so it needs the card's own
1714
+ side padding rather than the row's default bottom margin - same block as
1715
+ panels 2, 4 and 5.
1716
+ */
1717
+ .hid-intercom-filters {
1718
+ margin: 0;
1719
+ padding: 10px 14px;
1703
1720
  }
1704
1721
 
1705
- .web-call-button.is-connected {
1706
- background: #d9f2e1;
1707
- color: #16743a;
1722
+ .hid-intercom-filters :deep(.app-field) {
1723
+ max-width: 240px;
1708
1724
  }
1709
1725
 
1710
- .web-call-button.has-incoming-call {
1711
- background: #1769aa;
1712
- color: #fff;
1726
+ /* The reader option now reads "Reader - Portal", so it needs more room than
1727
+ the other filters. `AppSelect`'s root class IS `app-field`, so this has to
1728
+ be more specific than the rule above rather than a `.app-select` one. */
1729
+ .hid-intercom-filters :deep(.app-field.reader-select) {
1730
+ max-width: 320px;
1713
1731
  }
1714
1732
 
1715
- .add-button {
1733
+ /*
1734
+ The web-call button is the card's PRIMARY action, so its resting state is
1735
+ `AppButton`'s own accent - it was a flat `#e5e7eb` grey, which read as
1736
+ disabled next to every other screen's primary. Only the two STATE colours
1737
+ are the screen's own, because the handoff draws no such states.
1738
+ */
1739
+ .web-call-button {
1716
1740
  min-width: 160px;
1717
- background: #dedede;
1718
- color: #0b2c40;
1719
- font-size: 12px;
1720
1741
  }
1721
1742
 
1722
- .search-input {
1723
- max-width: 220px;
1743
+ .web-call-button.is-connected {
1744
+ background: var(--ok);
1745
+ color: var(--on-accent-strong);
1724
1746
  }
1725
1747
 
1726
- .reader-input {
1727
- width: 300px;
1728
- max-width: 36vw;
1748
+ .web-call-button.has-incoming-call {
1749
+ background: var(--warn);
1750
+ color: var(--on-accent-strong);
1729
1751
  }
1730
1752
 
1731
- .status-input {
1732
- max-width: 160px;
1733
- }
1734
1753
 
1735
- .table-card {
1736
- display: flex;
1737
- flex-direction: column;
1738
- min-height: 520px;
1739
- overflow: hidden;
1740
- background: #fff;
1741
- border-color: #e2e5e9;
1742
- border-radius: 6px;
1743
- }
1744
1754
 
1745
- .table-refresh {
1746
- display: flex;
1747
- align-items: center;
1748
- min-height: 42px;
1749
- padding: 0 12px;
1750
- background: #f6f6f6;
1751
- color: #0b2c40;
1752
- font-size: 12px;
1753
- }
1754
1755
 
1755
- .intercom-tabs {
1756
- border-bottom: 1px solid #e5e7eb;
1757
- }
1758
1756
 
1759
- .intercom-tabs :deep(.v-tab) {
1760
- flex: 1 1 0;
1761
- min-width: 0;
1762
- font-size: 12px;
1763
- text-transform: none;
1764
- }
1765
1757
 
1766
- .intercom-tabs :deep(.v-tab--selected) {
1767
- color: #0b4ea2;
1768
- }
1769
1758
 
1770
- .table-body {
1771
- position: relative;
1772
- flex: 1 1 auto;
1773
- min-height: 0;
1774
- overflow: auto;
1775
- }
1776
1759
 
1777
- .table-body :deep(table) {
1778
- table-layout: fixed;
1779
- min-width: 840px;
1780
- }
1781
1760
 
1782
- .table-body :deep(th) {
1783
- height: 48px;
1784
- color: #0b2c40;
1785
- font-size: 12px;
1786
- font-weight: 500;
1787
- }
1788
1761
 
1789
- .table-body :deep(td) {
1790
- height: 54px;
1791
- color: #1f2937;
1792
- font-size: 12px;
1793
- }
1794
1762
 
1795
- .table-footer {
1796
- display: flex;
1797
- flex: 0 0 auto;
1798
- align-items: center;
1799
- justify-content: flex-end;
1800
- gap: 4px;
1801
- min-height: 42px;
1802
- padding: 0 12px;
1803
- background: #f6f6f6;
1804
- color: #0b2c40;
1805
- font-size: 12px;
1806
- }
1807
1763
 
1808
- .empty-state {
1809
- display: grid;
1810
- min-height: 180px;
1811
- place-items: center;
1812
- color: #667085;
1813
- font-size: 13px;
1814
- }
1764
+
1765
+
1815
1766
 
1816
1767
  .row-menu {
1817
1768
  min-width: 160px;
@@ -1819,37 +1770,21 @@ export default {
1819
1770
  }
1820
1771
 
1821
1772
  .link-text {
1822
- color: #1565c0;
1773
+ color: var(--accent-text);
1823
1774
  }
1824
1775
 
1825
1776
  .success-text {
1826
- color: #2faf4a;
1777
+ color: var(--ok);
1827
1778
  }
1828
1779
 
1829
- .status-success {
1830
- background: #46b85a;
1831
- color: #fff;
1832
- }
1833
1780
 
1834
- .status-muted {
1835
- background: #a7a7a7;
1836
- color: #fff;
1837
- }
1838
1781
 
1839
- .status-warning {
1840
- background: #fff2cc;
1841
- color: #8a5b00;
1842
- }
1843
1782
 
1844
- .status-error {
1845
- background: #f9d0d0;
1846
- color: #991b1b;
1847
- }
1848
1783
 
1849
1784
  .form-dialog,
1850
1785
  .detail-dialog {
1851
1786
  overflow: hidden;
1852
- background: #fff;
1787
+ background: var(--card);
1853
1788
  }
1854
1789
 
1855
1790
  .sip-settings-dialog {
@@ -1860,8 +1795,8 @@ export default {
1860
1795
  .dialog-title {
1861
1796
  min-height: 48px;
1862
1797
  padding: 14px 16px;
1863
- background: #f4f4f4;
1864
- color: #0d1720;
1798
+ background: var(--thead);
1799
+ color: var(--text);
1865
1800
  font-size: 14px;
1866
1801
  font-weight: 700;
1867
1802
  }
@@ -1882,13 +1817,13 @@ export default {
1882
1817
 
1883
1818
  .field-label {
1884
1819
  margin: 12px 0 7px;
1885
- color: #344054;
1820
+ color: var(--text2);
1886
1821
  font-size: 10px;
1887
1822
  font-weight: 500;
1888
1823
  }
1889
1824
 
1890
1825
  .field-label span {
1891
- color: #d92d20;
1826
+ color: var(--err);
1892
1827
  }
1893
1828
 
1894
1829
  .sip-settings-dialog .field-label {
@@ -1897,14 +1832,14 @@ export default {
1897
1832
 
1898
1833
  .sip-label {
1899
1834
  margin: 12px 0 6px;
1900
- color: #263542;
1835
+ color: var(--text);
1901
1836
  font-size: 10px;
1902
1837
  font-weight: 500;
1903
1838
  line-height: 1.2;
1904
1839
  }
1905
1840
 
1906
1841
  .sip-label span {
1907
- color: #e5484d;
1842
+ color: var(--err);
1908
1843
  }
1909
1844
 
1910
1845
  .sip-switch-row {
@@ -1914,10 +1849,10 @@ export default {
1914
1849
  min-height: 40px;
1915
1850
  margin-bottom: 12px;
1916
1851
  padding: 0 12px;
1917
- border: 1px solid #dfe3e8;
1852
+ border: 1px solid var(--border);
1918
1853
  border-radius: 3px;
1919
- background: #fff;
1920
- color: #25313b;
1854
+ background: var(--card);
1855
+ color: var(--text);
1921
1856
  font-size: 12px;
1922
1857
  }
1923
1858
 
@@ -1935,44 +1870,17 @@ export default {
1935
1870
  }
1936
1871
 
1937
1872
  .sip-section-switch small {
1938
- color: #667085;
1873
+ color: var(--muted);
1939
1874
  font-size: 10px;
1940
1875
  line-height: 1.4;
1941
1876
  }
1942
1877
 
1943
- .sip-switch-row :deep(.v-switch) {
1944
- flex: 0 0 auto;
1945
- }
1946
1878
 
1947
- .sip-switch-row :deep(.v-selection-control) {
1948
- min-height: 32px;
1949
- }
1950
1879
 
1951
- .sip-switch-row :deep(.v-selection-control__wrapper) {
1952
- height: 28px;
1953
- }
1954
1880
 
1955
- .sip-switch-row :deep(.v-switch__track) {
1956
- height: 24px;
1957
- min-width: 42px;
1958
- opacity: 1;
1959
- }
1960
1881
 
1961
- .sip-switch-row :deep(.v-switch__thumb) {
1962
- background-color: #fff !important;
1963
- color: #fff !important;
1964
- height: 20px;
1965
- width: 20px;
1966
- }
1967
1882
 
1968
- .sip-switch-row :deep(.v-selection-control--dirty .v-switch__thumb) {
1969
- background-color: #fff !important;
1970
- color: #fff !important;
1971
- }
1972
1883
 
1973
- .sip-switch-row :deep(.v-selection-control--dirty .v-switch__thumb::before) {
1974
- background-color: #fff !important;
1975
- }
1976
1884
 
1977
1885
  .toggle-field {
1978
1886
  display: flex;
@@ -1981,27 +1889,27 @@ export default {
1981
1889
  min-height: 38px;
1982
1890
  margin-bottom: 8px;
1983
1891
  padding: 0 10px;
1984
- border: 1px solid #e5e7eb;
1892
+ border: 1px solid var(--border);
1985
1893
  border-radius: 3px;
1986
- color: #344054;
1894
+ color: var(--text2);
1987
1895
  font-size: 12px;
1988
1896
  }
1989
1897
 
1990
1898
  .sip-input :deep(.v-field) {
1991
1899
  min-height: 40px;
1992
1900
  border-radius: 3px;
1993
- background: #fff;
1901
+ background: var(--card);
1994
1902
  font-size: 12px;
1995
1903
  box-shadow: none;
1996
1904
  }
1997
1905
 
1998
1906
  .sip-input :deep(.v-field__outline) {
1999
1907
  --v-field-border-opacity: 1;
2000
- color: #dfe3e8;
1908
+ color: var(--border);
2001
1909
  }
2002
1910
 
2003
1911
  .sip-input :deep(.v-field--focused .v-field__outline) {
2004
- color: #9db7d1;
1912
+ color: var(--border);
2005
1913
  }
2006
1914
 
2007
1915
  .sip-input :deep(.v-field__outline__start),
@@ -2014,28 +1922,28 @@ export default {
2014
1922
  padding-top: 0;
2015
1923
  padding-bottom: 0;
2016
1924
  padding-inline: 12px;
2017
- color: #4a5562;
1925
+ color: var(--text2);
2018
1926
  font-size: 12px;
2019
1927
  }
2020
1928
 
2021
1929
  .sip-input :deep(.v-field__append-inner) {
2022
1930
  min-height: 40px;
2023
- color: #697586;
1931
+ color: var(--muted);
2024
1932
  }
2025
1933
 
2026
1934
  .sip-input :deep(.v-field--disabled) {
2027
- background: #f7f8fa;
1935
+ background: var(--hover);
2028
1936
  opacity: 1;
2029
1937
  }
2030
1938
 
2031
1939
  .sip-input :deep(.v-field--disabled .v-field__input),
2032
1940
  .sip-input :deep(.v-field--disabled input::placeholder) {
2033
- color: #98a2b3;
1941
+ color: var(--muted);
2034
1942
  opacity: 1;
2035
1943
  }
2036
1944
 
2037
1945
  .sip-input :deep(input::placeholder) {
2038
- color: #697586;
1946
+ color: var(--muted);
2039
1947
  opacity: 1;
2040
1948
  }
2041
1949
 
@@ -2050,7 +1958,7 @@ export default {
2050
1958
  width: 100%;
2051
1959
  grid-template-columns: 1fr 1fr;
2052
1960
  margin-bottom: 6px;
2053
- border: 1px solid #dfe3e8;
1961
+ border: 1px solid var(--border);
2054
1962
  border-radius: 3px;
2055
1963
  overflow: hidden;
2056
1964
  }
@@ -2059,20 +1967,20 @@ export default {
2059
1967
  min-width: 0;
2060
1968
  min-height: 40px;
2061
1969
  border-radius: 0;
2062
- color: #667085;
1970
+ color: var(--muted);
2063
1971
  font-size: 12px;
2064
1972
  font-weight: 400;
2065
1973
  letter-spacing: 0;
2066
1974
  }
2067
1975
 
2068
1976
  .mode-toggle :deep(.v-btn--active) {
2069
- background: #082b40;
2070
- color: #fff;
1977
+ background: var(--text);
1978
+ color: var(--card);
2071
1979
  }
2072
1980
 
2073
1981
  .mode-toggle :deep(.v-btn--disabled) {
2074
- background: #f7f8fa;
2075
- color: #98a2b3;
1982
+ background: var(--hover);
1983
+ color: var(--muted);
2076
1984
  opacity: 1;
2077
1985
  }
2078
1986
 
@@ -2080,13 +1988,9 @@ export default {
2080
1988
  margin-top: 0;
2081
1989
  }
2082
1990
 
2083
- .sip-settings-dialog :deep(.v-selection-control-group--inline) {
2084
- gap: 34px;
2085
- min-height: 34px;
2086
- }
2087
1991
 
2088
1992
  .sip-settings-dialog :deep(.v-radio .v-label) {
2089
- color: #344054;
1993
+ color: var(--text2);
2090
1994
  font-size: 12px;
2091
1995
  }
2092
1996
 
@@ -2108,14 +2012,14 @@ export default {
2108
2012
 
2109
2013
  .call-label {
2110
2014
  margin: 8px 0 6px;
2111
- color: #1f2937;
2015
+ color: var(--text);
2112
2016
  font-size: 10px;
2113
2017
  font-weight: 500;
2114
2018
  letter-spacing: 0;
2115
2019
  }
2116
2020
 
2117
2021
  .call-label span {
2118
- color: #d92d20;
2022
+ color: var(--err);
2119
2023
  }
2120
2024
 
2121
2025
  .call-radio-list {
@@ -2126,29 +2030,12 @@ export default {
2126
2030
  width: 100%;
2127
2031
  }
2128
2032
 
2129
- .call-radio-list :deep(.v-selection-control-group) {
2130
- gap: 8px;
2131
- }
2132
2033
 
2133
- .call-radio-list :deep(.v-selection-control) {
2134
- min-height: 36px;
2135
- margin-bottom: 0;
2136
- padding: 0 10px;
2137
- border: 1px solid #dfe3e8;
2138
- border-radius: 3px;
2139
- background: #fff;
2140
- }
2141
2034
 
2142
- .call-radio-list :deep(.v-selection-control__wrapper) {
2143
- width: 24px;
2144
- }
2145
2035
 
2146
- .call-radio-list :deep(.v-selection-control__input) {
2147
- transform: scale(0.9);
2148
- }
2149
2036
 
2150
2037
  .call-radio-list :deep(.v-label) {
2151
- color: #475467;
2038
+ color: var(--text2);
2152
2039
  font-size: 11px;
2153
2040
  letter-spacing: 0;
2154
2041
  opacity: 1;
@@ -2161,34 +2048,17 @@ export default {
2161
2048
  min-height: 36px;
2162
2049
  margin-bottom: 10px;
2163
2050
  padding: 0 10px;
2164
- border: 1px solid #e5e7eb;
2051
+ border: 1px solid var(--border);
2165
2052
  border-radius: 3px;
2166
- background: #fff;
2167
- color: #1f2937;
2053
+ background: var(--card);
2054
+ color: var(--text);
2168
2055
  font-size: 11px;
2169
2056
  letter-spacing: 0;
2170
2057
  }
2171
2058
 
2172
- .call-toggle-row :deep(.v-selection-control) {
2173
- min-height: 28px;
2174
- }
2175
2059
 
2176
- .call-toggle-row :deep(.v-selection-control__wrapper) {
2177
- height: 28px;
2178
- }
2179
2060
 
2180
- .call-toggle-row :deep(.v-switch__track) {
2181
- height: 22px;
2182
- min-width: 40px;
2183
- opacity: 1;
2184
- }
2185
2061
 
2186
- .call-toggle-row :deep(.v-switch__thumb) {
2187
- width: 18px;
2188
- height: 18px;
2189
- background-color: #fff !important;
2190
- color: #fff !important;
2191
- }
2192
2062
 
2193
2063
  .call-input {
2194
2064
  margin-bottom: 8px;
@@ -2197,13 +2067,13 @@ export default {
2197
2067
  .call-input :deep(.v-field) {
2198
2068
  min-height: 38px;
2199
2069
  border-radius: 3px;
2200
- background: #fff;
2070
+ background: var(--card);
2201
2071
  box-shadow: none;
2202
2072
  }
2203
2073
 
2204
2074
  .call-input :deep(.v-field__outline) {
2205
2075
  --v-field-border-opacity: 1;
2206
- color: #cfd5dc;
2076
+ color: var(--border);
2207
2077
  }
2208
2078
 
2209
2079
  .call-input :deep(.v-field__input) {
@@ -2211,19 +2081,19 @@ export default {
2211
2081
  padding-top: 0;
2212
2082
  padding-bottom: 0;
2213
2083
  padding-inline: 12px;
2214
- color: #4a4f55;
2084
+ color: var(--text2);
2215
2085
  font-size: 12px;
2216
2086
  letter-spacing: 0;
2217
2087
  }
2218
2088
 
2219
2089
  .call-input :deep(.v-field--disabled) {
2220
- background: #f7f8fa;
2090
+ background: var(--hover);
2221
2091
  opacity: 1;
2222
2092
  }
2223
2093
 
2224
2094
  .call-input :deep(.v-field--disabled .v-field__input),
2225
2095
  .call-input :deep(.v-field--disabled input::placeholder) {
2226
- color: #98a2b3;
2096
+ color: var(--muted);
2227
2097
  opacity: 1;
2228
2098
  }
2229
2099
 
@@ -2265,17 +2135,17 @@ export default {
2265
2135
  .make-call-danger {
2266
2136
  min-height: 46px;
2267
2137
  border-radius: 0;
2268
- color: #fff;
2138
+ color: var(--card);
2269
2139
  font-size: 14px;
2270
2140
  letter-spacing: 0;
2271
2141
  }
2272
2142
 
2273
2143
  .make-call-primary {
2274
- background: #06324f;
2144
+ background: var(--text);
2275
2145
  }
2276
2146
 
2277
2147
  .make-call-danger {
2278
- background: #d91345;
2148
+ background: var(--err);
2279
2149
  }
2280
2150
 
2281
2151
  .make-call-status {
@@ -2286,27 +2156,27 @@ export default {
2286
2156
  }
2287
2157
 
2288
2158
  .make-call-status.is-success {
2289
- background: #e7f7ec;
2290
- color: #17833b;
2159
+ background: var(--ok-bg);
2160
+ color: var(--ok);
2291
2161
  }
2292
2162
 
2293
2163
  .make-call-status.is-warning {
2294
- background: #fff7db;
2295
- color: #8a5b00;
2164
+ background: var(--warn-bg);
2165
+ color: var(--warn);
2296
2166
  }
2297
2167
 
2298
2168
  .make-call-status.is-error {
2299
- background: #fde8e8;
2300
- color: #b42318;
2169
+ background: var(--err-bg);
2170
+ color: var(--err);
2301
2171
  }
2302
2172
 
2303
2173
  .make-call-status.is-info {
2304
- background: #edf4ff;
2305
- color: #075fb7;
2174
+ background: var(--accent-soft);
2175
+ color: var(--accent-text);
2306
2176
  }
2307
2177
 
2308
2178
  .make-call-note {
2309
- color: #5d6673;
2179
+ color: var(--text2);
2310
2180
  font-size: 13px;
2311
2181
  line-height: 1.45;
2312
2182
  }
@@ -2317,7 +2187,7 @@ export default {
2317
2187
  max-height: min(90vh, 760px);
2318
2188
  max-width: 94vw;
2319
2189
  overflow: hidden;
2320
- background: #fff;
2190
+ background: var(--card);
2321
2191
  flex-direction: column;
2322
2192
  }
2323
2193
 
@@ -2342,23 +2212,23 @@ export default {
2342
2212
  }
2343
2213
 
2344
2214
  .web-phone-status.ready {
2345
- background: #d9f2e1;
2346
- color: #16743a;
2215
+ background: var(--ok-bg);
2216
+ color: var(--ok);
2347
2217
  }
2348
2218
 
2349
2219
  .web-phone-status.incoming {
2350
- background: #dcecff;
2351
- color: #125c9a;
2220
+ background: var(--accent-soft);
2221
+ color: var(--accent-text);
2352
2222
  }
2353
2223
 
2354
2224
  .web-phone-status.failed {
2355
- background: #fce2e2;
2356
- color: #a92222;
2225
+ background: var(--err-bg);
2226
+ color: var(--err);
2357
2227
  }
2358
2228
 
2359
2229
  .web-phone-status.idle {
2360
- background: #e5e7eb;
2361
- color: #5d6673;
2230
+ background: var(--border);
2231
+ color: var(--text2);
2362
2232
  }
2363
2233
 
2364
2234
  .web-phone-body {
@@ -2372,12 +2242,12 @@ export default {
2372
2242
 
2373
2243
  .web-phone-settings {
2374
2244
  padding-right: 24px;
2375
- border-right: 1px solid #e5e7eb;
2245
+ border-right: 1px solid var(--border);
2376
2246
  }
2377
2247
 
2378
2248
  .web-phone-section-title {
2379
2249
  margin-bottom: 14px;
2380
- color: #0d1720;
2250
+ color: var(--text);
2381
2251
  font-size: 13px;
2382
2252
  font-weight: 700;
2383
2253
  }
@@ -2388,9 +2258,9 @@ export default {
2388
2258
  align-items: center;
2389
2259
  justify-content: center;
2390
2260
  padding: 18px;
2391
- border: 1px solid #dce2e8;
2261
+ border: 1px solid var(--border);
2392
2262
  border-radius: 6px;
2393
- color: #5d6673;
2263
+ color: var(--text2);
2394
2264
  flex-direction: column;
2395
2265
  font-size: 12px;
2396
2266
  gap: 12px;
@@ -2398,15 +2268,15 @@ export default {
2398
2268
  }
2399
2269
 
2400
2270
  .sip-account-state.is-error {
2401
- border-color: #f0c7c7;
2402
- background: #fff8f8;
2403
- color: #a92222;
2271
+ border-color: var(--err-bg);
2272
+ background: var(--err-bg);
2273
+ color: var(--err);
2404
2274
  }
2405
2275
 
2406
2276
  .sip-account-retry {
2407
2277
  min-width: 104px;
2408
- border-color: #aeb8c2;
2409
- color: #344054;
2278
+ border-color: var(--muted);
2279
+ color: var(--text2);
2410
2280
  }
2411
2281
 
2412
2282
  .sip-account-summary {
@@ -2414,9 +2284,9 @@ export default {
2414
2284
  min-height: 96px;
2415
2285
  align-items: center;
2416
2286
  padding: 16px;
2417
- border: 1px solid #dce2e8;
2287
+ border: 1px solid var(--border);
2418
2288
  border-radius: 6px;
2419
- background: #f8fafc;
2289
+ background: var(--hover);
2420
2290
  grid-template-columns: minmax(0, 1fr) auto;
2421
2291
  gap: 18px;
2422
2292
  }
@@ -2429,12 +2299,12 @@ export default {
2429
2299
  }
2430
2300
 
2431
2301
  .sip-account-label {
2432
- color: #667085;
2302
+ color: var(--muted);
2433
2303
  font-size: 11px;
2434
2304
  }
2435
2305
 
2436
2306
  .sip-account-extension {
2437
- color: #0d1720;
2307
+ color: var(--text);
2438
2308
  font-size: 24px;
2439
2309
  line-height: 1.1;
2440
2310
  }
@@ -2442,7 +2312,7 @@ export default {
2442
2312
  .sip-account-address {
2443
2313
  max-width: 170px;
2444
2314
  overflow: hidden;
2445
- color: #536273;
2315
+ color: var(--text2);
2446
2316
  font-size: 11px;
2447
2317
  text-align: right;
2448
2318
  text-overflow: ellipsis;
@@ -2456,15 +2326,15 @@ export default {
2456
2326
  .web-phone-connect {
2457
2327
  min-height: 42px;
2458
2328
  margin-top: 14px;
2459
- background: #082b40;
2460
- color: #fff;
2329
+ background: var(--text);
2330
+ color: var(--card);
2461
2331
  }
2462
2332
 
2463
2333
  .web-phone-disconnect {
2464
2334
  min-height: 42px;
2465
2335
  margin-top: 14px;
2466
- border-color: #aeb8c2;
2467
- color: #344054;
2336
+ border-color: var(--muted);
2337
+ color: var(--text2);
2468
2338
  }
2469
2339
 
2470
2340
  .web-phone-console {
@@ -2479,9 +2349,9 @@ export default {
2479
2349
  min-height: 260px;
2480
2350
  overflow: hidden;
2481
2351
  place-items: center;
2482
- border: 1px solid #d7dde4;
2352
+ border: 1px solid var(--border);
2483
2353
  border-radius: 6px;
2484
- background: #102a3b;
2354
+ background: var(--text);
2485
2355
  }
2486
2356
 
2487
2357
  .remote-video {
@@ -2504,7 +2374,7 @@ export default {
2504
2374
  align-items: center;
2505
2375
  justify-content: center;
2506
2376
  padding: 28px;
2507
- color: #fff;
2377
+ color: var(--card);
2508
2378
  flex-direction: column;
2509
2379
  gap: 10px;
2510
2380
  text-align: center;
@@ -2518,7 +2388,7 @@ export default {
2518
2388
  .audio-call-state span {
2519
2389
  max-width: 100%;
2520
2390
  overflow: hidden;
2521
- color: #c9d7e1;
2391
+ color: var(--border);
2522
2392
  font-size: 11px;
2523
2393
  text-overflow: ellipsis;
2524
2394
  white-space: nowrap;
@@ -2536,13 +2406,13 @@ export default {
2536
2406
  width: 42px;
2537
2407
  height: 38px;
2538
2408
  border-radius: 4px;
2539
- background: #2a9b50;
2540
- color: #fff;
2409
+ background: var(--ok);
2410
+ color: var(--card);
2541
2411
  }
2542
2412
 
2543
2413
  .hangup-web-call,
2544
2414
  .reject-call {
2545
- background: #c9293d;
2415
+ background: var(--err);
2546
2416
  }
2547
2417
 
2548
2418
  .incoming-call-actions {
@@ -2555,11 +2425,11 @@ export default {
2555
2425
  .answer-call,
2556
2426
  .reject-call {
2557
2427
  min-height: 42px;
2558
- color: #fff;
2428
+ color: var(--card);
2559
2429
  }
2560
2430
 
2561
2431
  .answer-call {
2562
- background: #2a9b50;
2432
+ background: var(--ok);
2563
2433
  }
2564
2434
 
2565
2435
  .active-call-controls {
@@ -2601,28 +2471,28 @@ export default {
2601
2471
  justify-content: center;
2602
2472
  gap: 6px;
2603
2473
  min-height: 38px;
2604
- border: 1px dashed #c9d2dc;
2605
- color: #475467;
2474
+ border: 1px dashed var(--border);
2475
+ color: var(--text2);
2606
2476
  font-size: 11px;
2607
2477
  letter-spacing: 0;
2608
2478
  }
2609
2479
 
2610
2480
  .upload-drop.is-disabled {
2611
- background: #fafafa;
2612
- color: #98a2b3;
2481
+ background: var(--thead);
2482
+ color: var(--muted);
2613
2483
  }
2614
2484
 
2615
2485
  .upload-button {
2616
2486
  width: 40px;
2617
2487
  height: 38px;
2618
2488
  border-radius: 3px;
2619
- background: #1976d2;
2620
- color: #fff;
2489
+ background: var(--accent-text);
2490
+ color: var(--card);
2621
2491
  }
2622
2492
 
2623
2493
  .upload-button.v-btn--disabled {
2624
- background: #eceff3;
2625
- color: #c0c6cf;
2494
+ background: var(--hover);
2495
+ color: var(--border);
2626
2496
  opacity: 1;
2627
2497
  }
2628
2498
 
@@ -2632,7 +2502,7 @@ export default {
2632
2502
  justify-content: space-between;
2633
2503
  min-height: 32px;
2634
2504
  margin: 0 0 10px;
2635
- color: #475467;
2505
+ color: var(--text2);
2636
2506
  font-size: 11px;
2637
2507
  }
2638
2508
 
@@ -2643,12 +2513,12 @@ export default {
2643
2513
  }
2644
2514
 
2645
2515
  .call-file-name .v-icon {
2646
- color: #2fb344;
2516
+ color: var(--ok);
2647
2517
  }
2648
2518
 
2649
2519
  .detail-section-title {
2650
2520
  margin: 14px 0 8px;
2651
- color: #0d1720;
2521
+ color: var(--text);
2652
2522
  font-size: 13px;
2653
2523
  font-weight: 700;
2654
2524
  }
@@ -2663,12 +2533,12 @@ export default {
2663
2533
  justify-content: space-between;
2664
2534
  gap: 16px;
2665
2535
  min-height: 26px;
2666
- color: #667085;
2536
+ color: var(--muted);
2667
2537
  font-size: 11px;
2668
2538
  }
2669
2539
 
2670
2540
  .detail-row strong {
2671
- color: #101828;
2541
+ color: var(--text);
2672
2542
  font-weight: 500;
2673
2543
  text-align: right;
2674
2544
  }
@@ -2676,22 +2546,25 @@ export default {
2676
2546
  .dialog-actions {
2677
2547
  display: grid;
2678
2548
  grid-template-columns: 1fr 1fr;
2679
- border-top: 1px solid #e5e7eb;
2549
+ border-top: 1px solid var(--border);
2680
2550
  }
2681
2551
 
2682
2552
  .sip-settings-dialog .dialog-actions {
2683
2553
  min-height: 46px;
2684
2554
  }
2685
2555
 
2556
+ /* The split footer bar, as panels 2, 4 and 5 keep it: the SHAPE is the
2557
+ screen's own (the handoff draws no such bar); only the colours change. The
2558
+ navy was a fixed hex that sat on the dark card unchanged. */
2686
2559
  .action-cancel {
2687
- background: #fff;
2688
- color: #0d1720;
2560
+ background: var(--card);
2561
+ color: var(--text2);
2689
2562
  border-radius: 0;
2690
2563
  }
2691
2564
 
2692
2565
  .action-submit {
2693
- background: #082b40;
2694
- color: #fff;
2566
+ background: var(--accent-strong);
2567
+ color: var(--on-accent-strong);
2695
2568
  border-radius: 0;
2696
2569
  }
2697
2570
 
@@ -2700,15 +2573,8 @@ export default {
2700
2573
  }
2701
2574
 
2702
2575
  @media (max-width: 900px) {
2703
- .top-bar {
2704
- align-items: stretch;
2705
- flex-direction: column;
2706
- }
2707
-
2708
- .top-placeholder,
2709
- .reader-input,
2710
- .search-input,
2711
- .status-input {
2576
+ .hid-intercom-filters :deep(.app-field),
2577
+ .hid-intercom-filters :deep(.app-select) {
2712
2578
  width: 100%;
2713
2579
  max-width: none;
2714
2580
  }
@@ -2726,7 +2592,7 @@ export default {
2726
2592
  padding-right: 0;
2727
2593
  padding-bottom: 20px;
2728
2594
  border-right: 0;
2729
- border-bottom: 1px solid #e5e7eb;
2595
+ border-bottom: 1px solid var(--border);
2730
2596
  }
2731
2597
 
2732
2598
  }