-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathintercom.html
More file actions
248 lines (236 loc) · 12.4 KB
/
Copy pathintercom.html
File metadata and controls
248 lines (236 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Домофон — DoorFlow</title>
<link rel="stylesheet" href="assets/fonts.css">
<link rel="stylesheet" href="assets/styles.css">
</head>
<body>
<script src="assets/db.js"></script>
<script src="assets/app.js"></script>
<script>
const DB = window.VDX_DB;
let icTab = 'Станции';
let icFilter = 'Все';
const STATION_TYPES = ['Дверная станция','Панель лобби','Станция ворот','Внутренний монитор'];
const RING_GROUPS = ['Ресепшн','Охрана','Эксплуатация','Ночная смена'];
const DOOR_NAMES = DB.doors.map(d => d.name);
let callTimer = null;
let callSeconds = 0;
let callMuted = false;
let currentCallStation = null;
function stationCard(s) {
const statCls = tagClass(s.status);
const stateCls = s.state === 'Звонит' ? 'warn' : (s.state === 'В разговоре' ? 'ok' : (s.state === 'Офлайн' ? 'bad' : 'warn'));
return `<div class="entity">
<div class="entity-head">
<div><h3>${s.name}</h3><p>${s.type} · SIP ${s.sip}</p></div>
<div class="entity-badges"><span class="tag ${statCls}">${s.status}</span></div>
</div>
<div class="entity-badges">
<span class="tag ${s.video ? 'ok' : 'warn'}">${s.video ? 'Видео' : 'Только аудио'}</span>
<span class="tag ${stateCls}">${s.state}</span>
</div>
<div class="entity-facts">
<div>Привязана к двери: <b>${s.linkedDoor}</b></div>
<div>Группа звонка: <b>${s.ringGroup}</b> · Пропущено сегодня: <b>${s.missedToday}</b></div>
</div>
<div class="entity-actions">
<button onclick="openCallModal('${s.name}')">${svg('phone')}Позвонить</button>
<button onclick="stationAction('${s.name}','open')">${svg('doorOpen2')}Открыть дверь</button>
<button onclick="stationAction('${s.name}','dnd')">${svg('phoneOff')}Не беспокоить</button>
<button onclick="openStationEdit('${s.name}')">${svg('penLine')}Изменить</button>
<button class="danger" onclick="deleteStation('${s.name}')">${svg('trash')}</button>
</div>
</div>`;
}
function callLogRow(c) {
return `<tr><td>${c.station}</td><td>${c.caller}</td><td><span class="tag ${c.result==='Отвечен'?'ok':'bad'}">${c.result}</span></td><td>${c.operator}</td><td>${c.time}</td></tr>`;
}
function renderIntercom() {
const grid = document.getElementById('ic-grid');
const addBtn = document.getElementById('ic-add-btn');
if (icTab === 'Станции') {
let rows = DB.intercomStations;
if (icFilter !== 'Все') rows = rows.filter(s => s.status === icFilter || s.state === icFilter);
grid.innerHTML = `<div class="cardgrid">${rows.length ? rows.map(stationCard).join('') : emptyState('Станций по этому фильтру нет.')}</div>`;
addBtn.style.display = '';
} else {
grid.innerHTML = `<div class="tblwrap"><table class="dt"><thead><tr><th>Станция</th><th>Звонящий</th><th>Результат</th><th>Оператор</th><th>Время</th></tr></thead>
<tbody>${DB.intercomCallLog.map(callLogRow).join('')}</tbody></table></div>`;
addBtn.style.display = 'none';
}
}
function setIcTab(v) { icTab = v; document.querySelectorAll('#ic-tabs .ftab').forEach(b => b.classList.toggle('active', b.textContent === v)); renderIntercom(); }
function setIcFilter(v) { icFilter = v; document.querySelectorAll('#ic-filter .ftab').forEach(b => b.classList.toggle('active', b.textContent === v)); renderIntercom(); }
function stationAction(name, action) {
const s = DB.intercomStations.find(x => x.name === name);
if (!s) return;
if (action === 'open') toast(`Дверь у «${name}» открыта`);
if (action === 'dnd') { s.state = 'Не беспокоить'; toast(`«${name}»: режим «Не беспокоить»`); }
renderIntercom();
}
/* ============ Call modal (live video simulation) ============ */
function fmtTime(sec) {
const m = String(Math.floor(sec / 60)).padStart(2, '0');
const s = String(sec % 60).padStart(2, '0');
return `${m}:${s}`;
}
function callVideoHTML(label) {
return `<div style="background:#0c1420;border-radius:10px;overflow:hidden;position:relative;aspect-ratio:16/9;margin:14px 0">
<span style="position:absolute;top:10px;left:12px;color:#ef5350;font:700 11px var(--font-mono);display:flex;align-items:center;gap:5px">
<span style="width:7px;height:7px;border-radius:50%;background:#ef5350;display:inline-block"></span>LIVE
</span>
<span style="position:absolute;top:10px;right:12px;color:#c7cedb;font:600 11px var(--font-body)">${label}</span>
<span id="call-timer" style="position:absolute;bottom:10px;left:12px;color:#c7cedb;font:600 11px var(--font-mono)">${fmtTime(callSeconds)}</span>
<span style="position:absolute;bottom:10px;right:12px;color:#c7cedb;font:600 11px var(--font-mono)">1080p · 25 fps</span>
</div>`;
}
function openCallModal(name) {
const s = DB.intercomStations.find(x => x.name === name);
if (!s) return;
currentCallStation = name;
callSeconds = 0;
callMuted = false;
s.state = 'Звонит';
renderIntercom();
openModal(`Вызов станции...`, `
<div class="hint" style="margin-bottom:0">${s.ringGroup} · ${s.name} · SIP ${s.sip} · ${s.linkedDoor}</div>
${callVideoHTML(s.name + ' — камера домофона')}
<div id="call-controls" style="display:flex;gap:8px">
<button class="btn outline" onclick="toggleMute()">${svg('phoneOff')}Без звука</button>
<button class="btn danger" onclick="hangUp()">${svg('phoneOff')}Завершить</button>
<button class="btn outline" onclick="stationAction('${name}','open')">${svg('doorOpen2')}Открыть дверь</button>
<button class="btn outline" onclick="toast('Открыта страница двери')">${svg('doors')}Страница двери</button>
</div>
`, {wide:false});
clearInterval(callTimer);
setTimeout(() => {
if (currentCallStation !== name) return;
s.state = 'В разговоре';
renderIntercom();
updateCallModalHead(`В разговоре · 00:00`);
callTimer = setInterval(() => {
callSeconds++;
const t = document.getElementById('call-timer');
if (t) t.textContent = fmtTime(callSeconds);
updateCallModalHead(`В разговоре · ${fmtTime(callSeconds)}`);
}, 1000);
}, 1500);
}
function updateCallModalHead(title) {
const h = document.querySelector('#modal-root .modal-head h3');
if (h) h.textContent = title;
}
function toggleMute() {
callMuted = !callMuted;
const btn = document.querySelector('#call-controls .btn.outline');
if (btn) btn.innerHTML = `${svg(callMuted ? 'phoneOff' : 'phone')}${callMuted ? 'Включить звук' : 'Без звука'}`;
toast(callMuted ? 'Микрофон выключен' : 'Микрофон включён');
}
function hangUp() {
clearInterval(callTimer);
const s = DB.intercomStations.find(x => x.name === currentCallStation);
if (s) s.state = 'Ожидание';
currentCallStation = null;
closeModal();
renderIntercom();
toast('Звонок завершён');
}
function openAddStation() {
openModal('Добавить станцию домофона', `
<p class="hint" style="margin:-4px 0 14px">Настройте SIP-идентификатор, группу звонка, видео и привязанную дверь.</p>
<div class="formgrid">
${fField('Название', fInput('ns-name','','Станция паркинга'))}
<div></div>
${fField('Тип', fSelect('ns-type', STATION_TYPES, 'Дверная станция'))}
${fField('Группа звонка', fSelect('ns-ring', RING_GROUPS, 'Ресепшн'))}
${fField('Расположение', fInput('ns-location','','Северный холл'))}
${fField('SIP ID', fInput('ns-sip','','2007'))}
${fField('IP-адрес', fInput('ns-ip','','10.20.40.67'))}
${fField('Привязанная дверь', fCombo('ns-door', ['Не привязано'].concat(DOOR_NAMES), 'Не привязано'))}
</div>
<div class="formgrid" style="margin-top:6px">
${fField('Видеокамера', `<label class="switch"><input type="checkbox" id="ns-video" checked><span class="slider"></span></label>`)}
${fField('Онлайн', `<label class="switch"><input type="checkbox" id="ns-online" checked><span class="slider"></span></label>`)}
</div>
`, {wide:true});
document.getElementById('modal-root').querySelector('.modal').insertAdjacentHTML('beforeend', modalFooter(`
<button class="btn outline" onclick="closeModal()">Отмена</button>
<button class="btn" onclick="saveNewStation()">Добавить станцию</button>
`));
}
function saveNewStation() {
const name = document.getElementById('ns-name').value.trim();
const sip = document.getElementById('ns-sip').value.trim();
if (!name || !sip) { toast('Название и SIP ID обязательны'); return; }
const online = document.getElementById('ns-online').checked;
const door = document.getElementById('ns-door').value;
DB.intercomStations.unshift({
name,
type: document.getElementById('ns-type').value,
location: document.getElementById('ns-location').value || '—',
ip: document.getElementById('ns-ip').value || '—',
sip,
status: online ? 'Онлайн' : 'Офлайн',
video: document.getElementById('ns-video').checked,
state: online ? 'Ожидание' : 'Офлайн',
linkedDoor: door === 'Не привязано' ? '—' : door,
ringGroup: document.getElementById('ns-ring').value,
missedToday: 0
});
closeModal(); renderIntercom(); toast(`«${name}» добавлена`);
}
function openStationEdit(name) {
const s = DB.intercomStations.find(x => x.name === name);
if (!s) return;
openModal(`Изменить: ${s.name}`, `
<div class="formgrid">
${fField('Название', fInput('es-name', s.name))}
${fField('Статус', fSelect('es-status', ['Онлайн','Офлайн'], s.status))}
${fField('Группа звонка', fSelect('es-ring', RING_GROUPS, s.ringGroup))}
${fField('Привязанная дверь', fCombo('es-door', DOOR_NAMES, s.linkedDoor))}
</div>
`);
document.getElementById('modal-root').querySelector('.modal').insertAdjacentHTML('beforeend', modalFooter(`
<button class="btn outline" onclick="closeModal()">Отмена</button>
<button class="btn" onclick="saveStationEdit('${name}')">Сохранить</button>
`));
}
function saveStationEdit(oldName) {
const s = DB.intercomStations.find(x => x.name === oldName);
if (!s) return;
s.name = document.getElementById('es-name').value.trim() || s.name;
s.status = document.getElementById('es-status').value;
s.ringGroup = document.getElementById('es-ring').value;
s.linkedDoor = document.getElementById('es-door').value;
closeModal(); renderIntercom(); toast('Изменения сохранены');
}
function deleteStation(name) {
const idx = DB.intercomStations.findIndex(x => x.name === name);
if (idx > -1) DB.intercomStations.splice(idx, 1);
renderIntercom(); toast(`«${name}» удалена`);
}
const s = DB.intercomSummary;
const tabTabs = ['Станции','Журнал звонков'];
const filterOpts = ['Все','Онлайн','Офлайн','Звонит','В разговоре','Не беспокоить'];
const content = `
${pageHead('DoorFlow Tower · демо-аккаунт', 'Домофон', 'Видеопанели, группы вызова и удалённое открытие двери из одной консоли.',
`<button class="btn" id="ic-add-btn" onclick="openAddStation()">${svg('plus')}Добавить станцию</button>`)}
${statRow([
{ icon:'intercom', label:'Станции', value: s.total },
{ icon:'circleCheck', label:'Онлайн', value: s.online },
{ icon:'phone', label:'Активные звонки', value: s.activeCalls },
{ icon:'phoneOff', label:'Пропущено сегодня', value: s.missedToday, bad: true },
])}
<div id="ic-tabs">${filterTabs('ic', tabTabs, 'Станции', 'setIcTab')}</div>
<div id="ic-filter" style="margin-top:10px">${filterTabs('icf', filterOpts, 'Все', 'setIcFilter')}</div>
<div id="ic-grid" style="margin-top:16px"></div>
`;
renderShell('intercom', content);
renderIntercom();
</script>
</body>
</html>