2026-09-13 19:11:47 +03:30
|
|
|
|
<script setup lang="ts">
|
2026-09-14 16:49:43 +03:30
|
|
|
|
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
2026-09-13 19:11:47 +03:30
|
|
|
|
import axios from 'axios';
|
|
|
|
|
|
import { api } from '@/lib/api';
|
2026-09-14 16:49:43 +03:30
|
|
|
|
import UiIcon from '@/components/UiIcon.vue';
|
|
|
|
|
|
import { formatClockFa, formatJalaliNumeric, toFaDigits } from '@/lib/jalali';
|
2026-09-13 19:11:47 +03:30
|
|
|
|
import type {
|
|
|
|
|
|
CreateMessageAutomationRuleRequest,
|
|
|
|
|
|
MessageAutomationChannel,
|
|
|
|
|
|
MessageAutomationDelayUnit,
|
|
|
|
|
|
MessageAutomationRuleDto,
|
|
|
|
|
|
MessageAutomationTarget,
|
|
|
|
|
|
MessageAutomationTrigger,
|
|
|
|
|
|
UpdateMessageAutomationRuleRequest,
|
|
|
|
|
|
} from '@mento/shared';
|
|
|
|
|
|
import {
|
|
|
|
|
|
MESSAGE_AUTOMATION_CHANNEL_LABELS,
|
2026-09-14 16:49:43 +03:30
|
|
|
|
MESSAGE_AUTOMATION_FIELDS,
|
|
|
|
|
|
MESSAGE_AUTOMATION_SMS_SEGMENT,
|
2026-09-13 19:11:47 +03:30
|
|
|
|
MESSAGE_AUTOMATION_TARGET_LABELS,
|
|
|
|
|
|
MESSAGE_AUTOMATION_TRIGGER_LABELS,
|
|
|
|
|
|
MESSAGE_AUTOMATION_TRIGGERS,
|
|
|
|
|
|
MESSAGE_AUTOMATION_UNIT_LABELS,
|
|
|
|
|
|
} from '@mento/shared';
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
const MOBILE_MQ = '(max-width: 979px)';
|
|
|
|
|
|
const DELAY_UNITS: MessageAutomationDelayUnit[] = ['hours', 'days', 'weeks', 'months'];
|
|
|
|
|
|
const DELAY_MAX: Record<MessageAutomationDelayUnit, number> = {
|
|
|
|
|
|
minutes: 1,
|
|
|
|
|
|
hours: 23,
|
|
|
|
|
|
days: 6,
|
|
|
|
|
|
weeks: 3,
|
|
|
|
|
|
months: 11,
|
|
|
|
|
|
};
|
|
|
|
|
|
const EMOJIS = ['🙏', '😊', '❤️', '👍', '🎉', '🌸', '✨', '👋', '💐', '🌟', '😍', '🤝'];
|
|
|
|
|
|
|
2026-09-13 19:11:47 +03:30
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
const saving = ref(false);
|
|
|
|
|
|
const error = ref('');
|
|
|
|
|
|
const ok = ref('');
|
|
|
|
|
|
const items = ref<MessageAutomationRuleDto[]>([]);
|
|
|
|
|
|
const editingId = ref<string | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
const name = ref('');
|
2026-09-14 16:49:43 +03:30
|
|
|
|
const description = ref('');
|
2026-09-13 19:11:47 +03:30
|
|
|
|
const trigger = ref<MessageAutomationTrigger>('invoice_issued');
|
2026-09-14 16:49:43 +03:30
|
|
|
|
const delayLater = ref(false);
|
|
|
|
|
|
const startDelayValue = ref(1);
|
|
|
|
|
|
const startDelayUnit = ref<MessageAutomationDelayUnit>('hours');
|
2026-09-13 19:11:47 +03:30
|
|
|
|
const repeatCount = ref(1);
|
2026-09-13 21:22:41 +03:30
|
|
|
|
const intervalValue = ref(2);
|
|
|
|
|
|
const intervalUnit = ref<MessageAutomationDelayUnit>('days');
|
2026-09-13 19:11:47 +03:30
|
|
|
|
const notifyTarget = ref<MessageAutomationTarget>('person');
|
|
|
|
|
|
const channel = ref<MessageAutomationChannel>('sms');
|
|
|
|
|
|
const body = ref('');
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
const bodyEl = ref<HTMLTextAreaElement | null>(null);
|
|
|
|
|
|
const fieldsOpen = ref(false);
|
|
|
|
|
|
const emojiOpen = ref(false);
|
|
|
|
|
|
const fieldsSheet = ref(false);
|
|
|
|
|
|
const isMobile = ref(typeof window !== 'undefined' && window.matchMedia(MOBILE_MQ).matches);
|
|
|
|
|
|
const mobileView = ref<'list' | 'wizard'>('list');
|
|
|
|
|
|
const mobileStep = ref(1);
|
|
|
|
|
|
const query = ref('');
|
|
|
|
|
|
const statusFilter = ref<'all' | 'active' | 'inactive'>('all');
|
|
|
|
|
|
const menuId = ref<string | null>(null);
|
|
|
|
|
|
const MOBILE_STEPS = [
|
|
|
|
|
|
{ n: 1, label: 'اطلاعات' },
|
|
|
|
|
|
{ n: 2, label: 'شرط اجرا' },
|
|
|
|
|
|
{ n: 3, label: 'پیام' },
|
|
|
|
|
|
{ n: 4, label: 'تکمیل' },
|
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
|
|
const visibleItems = computed(() => {
|
|
|
|
|
|
const q = query.value.trim();
|
|
|
|
|
|
return items.value.filter((row) => {
|
|
|
|
|
|
if (statusFilter.value === 'active' && !row.isActive) return false;
|
|
|
|
|
|
if (statusFilter.value === 'inactive' && row.isActive) return false;
|
|
|
|
|
|
if (!q) return true;
|
|
|
|
|
|
return `${row.name} ${row.description ?? ''} ${row.body}`.includes(q);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const delayMax = computed(() => DELAY_MAX[startDelayUnit.value] || 23);
|
|
|
|
|
|
const intervalMax = computed(() => DELAY_MAX[intervalUnit.value] || 6);
|
|
|
|
|
|
const bodyCount = computed(() => body.value.length);
|
|
|
|
|
|
const bodyOver = computed(() => bodyCount.value > MESSAGE_AUTOMATION_SMS_SEGMENT);
|
|
|
|
|
|
const smsParts = computed(() =>
|
|
|
|
|
|
bodyCount.value === 0 ? 0 : Math.ceil(bodyCount.value / MESSAGE_AUTOMATION_SMS_SEGMENT),
|
2026-09-13 19:11:47 +03:30
|
|
|
|
);
|
2026-09-14 16:49:43 +03:30
|
|
|
|
const previewBody = computed(() => {
|
|
|
|
|
|
const sample = {
|
|
|
|
|
|
'{نام}': 'علی',
|
|
|
|
|
|
'{شماره}': '۰۹۱۲۱۲۳۴۵۶۷',
|
|
|
|
|
|
'{تاریخ}': formatJalaliNumeric(new Date()),
|
|
|
|
|
|
'{عنوان}': name.value.trim() || 'عنوان',
|
|
|
|
|
|
'{شرکت}': 'شرکت نمونه',
|
|
|
|
|
|
'{title}': name.value.trim() || 'عنوان',
|
|
|
|
|
|
};
|
|
|
|
|
|
let text = body.value.trim();
|
|
|
|
|
|
for (const [token, value] of Object.entries(sample)) {
|
|
|
|
|
|
text = text.split(token).join(value);
|
|
|
|
|
|
}
|
|
|
|
|
|
return text;
|
|
|
|
|
|
});
|
|
|
|
|
|
const previewClock = computed(() => formatClockFa(new Date()));
|
|
|
|
|
|
|
|
|
|
|
|
watch(startDelayUnit, () => {
|
|
|
|
|
|
if (startDelayValue.value > delayMax.value) startDelayValue.value = delayMax.value;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
watch(intervalUnit, () => {
|
|
|
|
|
|
if (intervalValue.value > intervalMax.value) intervalValue.value = intervalMax.value;
|
|
|
|
|
|
});
|
2026-09-13 19:11:47 +03:30
|
|
|
|
|
|
|
|
|
|
function resetForm() {
|
|
|
|
|
|
editingId.value = null;
|
|
|
|
|
|
name.value = '';
|
2026-09-14 16:49:43 +03:30
|
|
|
|
description.value = '';
|
2026-09-13 19:11:47 +03:30
|
|
|
|
trigger.value = 'invoice_issued';
|
2026-09-14 16:49:43 +03:30
|
|
|
|
delayLater.value = false;
|
|
|
|
|
|
startDelayValue.value = 1;
|
|
|
|
|
|
startDelayUnit.value = 'hours';
|
2026-09-13 19:11:47 +03:30
|
|
|
|
repeatCount.value = 1;
|
2026-09-13 21:22:41 +03:30
|
|
|
|
intervalValue.value = 2;
|
|
|
|
|
|
intervalUnit.value = 'days';
|
2026-09-13 19:11:47 +03:30
|
|
|
|
notifyTarget.value = 'person';
|
|
|
|
|
|
channel.value = 'sms';
|
|
|
|
|
|
body.value = '';
|
2026-09-14 16:49:43 +03:30
|
|
|
|
fieldsOpen.value = false;
|
|
|
|
|
|
emojiOpen.value = false;
|
|
|
|
|
|
fieldsSheet.value = false;
|
|
|
|
|
|
menuId.value = null;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
ok.value = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
function ruleDate(row: MessageAutomationRuleDto) {
|
|
|
|
|
|
return formatJalaliNumeric(new Date(row.createdAt));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openMobileCreate() {
|
|
|
|
|
|
resetForm();
|
|
|
|
|
|
mobileStep.value = 1;
|
|
|
|
|
|
mobileView.value = 'wizard';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openMobileEdit(row: MessageAutomationRuleDto) {
|
|
|
|
|
|
startEdit(row);
|
|
|
|
|
|
menuId.value = null;
|
|
|
|
|
|
mobileStep.value = 1;
|
|
|
|
|
|
mobileView.value = 'wizard';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeMobileWizard() {
|
|
|
|
|
|
resetForm();
|
|
|
|
|
|
mobileStep.value = 1;
|
|
|
|
|
|
mobileView.value = 'list';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function nextMobileStep() {
|
|
|
|
|
|
if (mobileStep.value === 1 && !name.value.trim()) {
|
|
|
|
|
|
error.value = 'عنوان قانون را وارد کنید.';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (mobileStep.value === 3 && !body.value.trim()) {
|
|
|
|
|
|
error.value = 'متن پیام را وارد کنید.';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
if (mobileStep.value < 4) mobileStep.value += 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function prevMobileStep() {
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
if (mobileStep.value <= 1) {
|
|
|
|
|
|
closeMobileWizard();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
mobileStep.value -= 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-13 19:11:47 +03:30
|
|
|
|
function startEdit(row: MessageAutomationRuleDto) {
|
|
|
|
|
|
editingId.value = row.id;
|
|
|
|
|
|
name.value = row.name;
|
2026-09-14 16:49:43 +03:30
|
|
|
|
description.value = row.description ?? '';
|
2026-09-13 19:11:47 +03:30
|
|
|
|
trigger.value = row.trigger;
|
2026-09-14 16:49:43 +03:30
|
|
|
|
delayLater.value = row.startDelayValue > 0;
|
|
|
|
|
|
startDelayValue.value = Math.max(1, row.startDelayValue || 1);
|
|
|
|
|
|
startDelayUnit.value =
|
|
|
|
|
|
row.startDelayUnit === 'minutes' ? 'hours' : row.startDelayUnit;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
repeatCount.value = row.repeatCount;
|
2026-09-13 21:22:41 +03:30
|
|
|
|
intervalValue.value = row.intervalValue;
|
|
|
|
|
|
intervalUnit.value = row.intervalUnit;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
notifyTarget.value = row.notifyTarget;
|
|
|
|
|
|
channel.value = row.channel;
|
|
|
|
|
|
body.value = row.body;
|
|
|
|
|
|
ok.value = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function scheduleLabel(row: MessageAutomationRuleDto) {
|
|
|
|
|
|
const start =
|
|
|
|
|
|
row.startDelayValue === 0
|
2026-09-14 16:49:43 +03:30
|
|
|
|
? 'بلافاصله'
|
2026-09-13 21:22:41 +03:30
|
|
|
|
: `${toFaDigits(row.startDelayValue)} ${MESSAGE_AUTOMATION_UNIT_LABELS[row.startDelayUnit]} بعد`;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
if (row.repeatCount <= 1) return start;
|
2026-09-13 21:22:41 +03:30
|
|
|
|
return `${start} · ${toFaDigits(row.repeatCount)} بار، هر ${toFaDigits(row.intervalValue)} ${MESSAGE_AUTOMATION_UNIT_LABELS[row.intervalUnit]}`;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.get<MessageAutomationRuleDto[]>('/message-automations');
|
|
|
|
|
|
items.value = data;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
error.value = axios.isAxiosError(e) && !e.response
|
|
|
|
|
|
? 'اتصال به API برقرار نشد.'
|
|
|
|
|
|
: 'بارگذاری قوانین پیام ناموفق بود.';
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function payload(): CreateMessageAutomationRuleRequest {
|
2026-09-14 16:49:43 +03:30
|
|
|
|
const named = name.value.trim();
|
2026-09-13 19:11:47 +03:30
|
|
|
|
return {
|
2026-09-14 16:49:43 +03:30
|
|
|
|
name: named,
|
2026-09-13 20:11:48 +03:30
|
|
|
|
isActive: true,
|
2026-09-13 19:11:47 +03:30
|
|
|
|
trigger: trigger.value,
|
2026-09-14 16:49:43 +03:30
|
|
|
|
startDelayValue: delayLater.value ? Math.max(1, startDelayValue.value || 1) : 0,
|
|
|
|
|
|
startDelayUnit: delayLater.value ? startDelayUnit.value : 'minutes',
|
|
|
|
|
|
repeatCount: Math.min(10, Math.max(1, Number(repeatCount.value) || 1)),
|
2026-09-13 21:22:41 +03:30
|
|
|
|
intervalValue: Math.max(1, intervalValue.value || 1),
|
|
|
|
|
|
intervalUnit: intervalUnit.value,
|
2026-09-13 19:11:47 +03:30
|
|
|
|
notifyTarget: notifyTarget.value,
|
|
|
|
|
|
channel: channel.value,
|
2026-09-14 16:49:43 +03:30
|
|
|
|
title: named,
|
|
|
|
|
|
description: description.value.trim() || null,
|
2026-09-13 19:11:47 +03:30
|
|
|
|
body: body.value.trim(),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function save() {
|
2026-09-14 16:49:43 +03:30
|
|
|
|
if (!name.value.trim() || !body.value.trim()) {
|
|
|
|
|
|
error.value = 'عنوان قانون و متن پیام را وارد کنید.';
|
2026-09-13 19:11:47 +03:30
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
saving.value = true;
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
ok.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (editingId.value) {
|
2026-09-13 20:11:48 +03:30
|
|
|
|
const { isActive: _ignore, ...rest } = payload();
|
|
|
|
|
|
const bodyDto: UpdateMessageAutomationRuleRequest = rest;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
await api.patch(`/message-automations/${editingId.value}`, bodyDto);
|
|
|
|
|
|
ok.value = 'قانون بهروز شد.';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await api.post('/message-automations', payload());
|
|
|
|
|
|
ok.value = 'قانون ساخته شد.';
|
|
|
|
|
|
}
|
|
|
|
|
|
resetForm();
|
2026-09-14 16:49:43 +03:30
|
|
|
|
if (isMobile.value) {
|
|
|
|
|
|
mobileStep.value = 1;
|
|
|
|
|
|
mobileView.value = 'list';
|
|
|
|
|
|
}
|
2026-09-13 19:11:47 +03:30
|
|
|
|
await load();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
error.value = 'ذخیره قانون ناموفق بود.';
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
saving.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function toggle(row: MessageAutomationRuleDto) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await api.patch(`/message-automations/${row.id}`, { isActive: !row.isActive });
|
|
|
|
|
|
await load();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
error.value = 'تغییر وضعیت ناموفق بود.';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-15 14:10:52 +03:30
|
|
|
|
const pendingDelete = ref<MessageAutomationRuleDto | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
function askRemove(row: MessageAutomationRuleDto) {
|
|
|
|
|
|
menuId.value = null;
|
|
|
|
|
|
pendingDelete.value = row;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function cancelRemove() {
|
|
|
|
|
|
pendingDelete.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function confirmRemove() {
|
|
|
|
|
|
const row = pendingDelete.value;
|
|
|
|
|
|
if (!row) return;
|
|
|
|
|
|
pendingDelete.value = null;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
try {
|
|
|
|
|
|
await api.delete(`/message-automations/${row.id}`);
|
|
|
|
|
|
if (editingId.value === row.id) resetForm();
|
|
|
|
|
|
await load();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
error.value = 'حذف قانون ناموفق بود.';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
function insertAtCursor(text: string) {
|
|
|
|
|
|
const el = bodyEl.value;
|
|
|
|
|
|
const start = el?.selectionStart ?? body.value.length;
|
|
|
|
|
|
const end = el?.selectionEnd ?? start;
|
|
|
|
|
|
body.value = `${body.value.slice(0, start)}${text}${body.value.slice(end)}`;
|
|
|
|
|
|
void nextTick(() => {
|
|
|
|
|
|
if (!el) return;
|
|
|
|
|
|
const pos = start + text.length;
|
|
|
|
|
|
el.focus();
|
|
|
|
|
|
el.setSelectionRange(pos, pos);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function insertField(token: string) {
|
|
|
|
|
|
insertAtCursor(token);
|
|
|
|
|
|
fieldsOpen.value = false;
|
|
|
|
|
|
fieldsSheet.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function insertEmoji(mark: string) {
|
|
|
|
|
|
insertAtCursor(mark);
|
|
|
|
|
|
emojiOpen.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mq: MediaQueryList | null = null;
|
|
|
|
|
|
function syncViewport() {
|
|
|
|
|
|
isMobile.value = !!mq?.matches;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
mq = window.matchMedia(MOBILE_MQ);
|
|
|
|
|
|
syncViewport();
|
|
|
|
|
|
mq.addEventListener('change', syncViewport);
|
|
|
|
|
|
void load();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
mq?.removeEventListener('change', syncViewport);
|
|
|
|
|
|
});
|
2026-09-13 19:11:47 +03:30
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<section class="page">
|
2026-09-14 16:49:43 +03:30
|
|
|
|
<div v-if="isMobile && mobileView === 'list'" class="m-home">
|
|
|
|
|
|
<header class="m-title">
|
|
|
|
|
|
<h1>اتوماسیون پیامکها</h1>
|
|
|
|
|
|
<p>با قوانین هوشمند، در زمان مناسب پیام مناسب را ارسال کنید.</p>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<div class="m-hero">
|
|
|
|
|
|
<img
|
|
|
|
|
|
class="hero-art"
|
|
|
|
|
|
src="/brand/message-automation-hero.png"
|
|
|
|
|
|
alt=""
|
|
|
|
|
|
width="1600"
|
|
|
|
|
|
height="983"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<h2>ارتباط هوشمند با مشتریان</h2>
|
|
|
|
|
|
<p class="sub">برای هر رویداد در سیستم، به صورت خودکار پیامک ارسال کنید.</p>
|
|
|
|
|
|
<button class="btn primary wide" type="button" @click="openMobileCreate">
|
|
|
|
|
|
<UiIcon name="plus" :size="18" />
|
|
|
|
|
|
قانون جدید
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="m-tools">
|
|
|
|
|
|
<label class="m-search">
|
|
|
|
|
|
<UiIcon name="search" :size="16" />
|
|
|
|
|
|
<input v-model="query" type="search" placeholder="جستجوی قانون..." />
|
|
|
|
|
|
<UiIcon name="funnel" :size="16" />
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div class="m-tabs" role="tablist">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="tab"
|
|
|
|
|
|
:class="{ on: statusFilter === 'all' }"
|
|
|
|
|
|
@click="statusFilter = 'all'"
|
|
|
|
|
|
>
|
|
|
|
|
|
همه
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="tab"
|
|
|
|
|
|
:class="{ on: statusFilter === 'active' }"
|
|
|
|
|
|
@click="statusFilter = 'active'"
|
|
|
|
|
|
>
|
|
|
|
|
|
فعال
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="tab"
|
|
|
|
|
|
:class="{ on: statusFilter === 'inactive' }"
|
|
|
|
|
|
@click="statusFilter = 'inactive'"
|
|
|
|
|
|
>
|
|
|
|
|
|
غیرفعال
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p v-if="error" class="error">{{ error }}</p>
|
|
|
|
|
|
<p v-if="ok" class="ok">{{ ok }}</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="m-cards">
|
|
|
|
|
|
<article
|
|
|
|
|
|
v-for="row in visibleItems"
|
|
|
|
|
|
:key="row.id"
|
|
|
|
|
|
class="m-card"
|
|
|
|
|
|
:class="{ off: !row.isActive }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<header>
|
|
|
|
|
|
<div class="m-menu">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-label="عملیات"
|
|
|
|
|
|
@click="menuId = menuId === row.id ? null : row.id"
|
|
|
|
|
|
>
|
|
|
|
|
|
<UiIcon name="dots-v" :size="18" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div v-if="menuId === row.id" class="pop">
|
|
|
|
|
|
<button type="button" @click="openMobileEdit(row)">ویرایش</button>
|
2026-09-15 14:10:52 +03:30
|
|
|
|
<button type="button" class="danger" @click="askRemove(row)">حذف</button>
|
2026-09-14 16:49:43 +03:30
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="m-copy">
|
|
|
|
|
|
<strong>{{ row.name }}</strong>
|
|
|
|
|
|
<p>{{ row.description || scheduleLabel(row) }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="toggle"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:class="{ on: row.isActive }"
|
|
|
|
|
|
:aria-pressed="row.isActive"
|
|
|
|
|
|
:aria-label="row.isActive ? 'فعال' : 'غیرفعال'"
|
|
|
|
|
|
@click="toggle(row)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<footer>
|
|
|
|
|
|
<span class="m-status" :class="{ on: row.isActive }">
|
|
|
|
|
|
<i />
|
|
|
|
|
|
{{ row.isActive ? 'فعال' : 'غیرفعال' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<time>{{ ruleDate(row) }}</time>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
<p v-if="!loading && !visibleItems.length" class="empty">هنوز قانون پیامی نیست.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<form
|
|
|
|
|
|
v-else-if="isMobile"
|
|
|
|
|
|
class="m-wiz"
|
|
|
|
|
|
@submit.prevent="mobileStep === 4 ? save() : nextMobileStep()"
|
|
|
|
|
|
>
|
|
|
|
|
|
<header class="m-wiz-head">
|
|
|
|
|
|
<button class="m-back" type="button" aria-label="بازگشت" @click="prevMobileStep">
|
|
|
|
|
|
<UiIcon name="chevron" :size="20" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h1>{{ editingId ? 'ویرایش قانون' : 'قانون جدید' }}</h1>
|
|
|
|
|
|
<p>ایجاد یک قانون پیامک خودکار</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="block-ico" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="document" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<ol class="m-steps">
|
|
|
|
|
|
<li
|
|
|
|
|
|
v-for="step in MOBILE_STEPS"
|
|
|
|
|
|
:key="step.n"
|
|
|
|
|
|
:class="{ on: mobileStep === step.n, done: mobileStep > step.n }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<b>{{ toFaDigits(step.n) }}</b>
|
|
|
|
|
|
<span>{{ step.label }}</span>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
</ol>
|
|
|
|
|
|
|
|
|
|
|
|
<p v-if="error" class="error">{{ error }}</p>
|
|
|
|
|
|
<p v-if="ok" class="ok">{{ ok }}</p>
|
|
|
|
|
|
|
|
|
|
|
|
<section v-if="mobileStep === 1" class="stack">
|
|
|
|
|
|
<section class="block">
|
|
|
|
|
|
<header class="block-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2>اطلاعات کلی</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="block-ico" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="document" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
<span>عنوان قانون <em>*</em></span>
|
|
|
|
|
|
<input v-model="name" maxlength="120" placeholder="مثلاً تشکر بعد از فاکتور" />
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
توضیحات
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="description"
|
|
|
|
|
|
rows="2"
|
|
|
|
|
|
maxlength="500"
|
|
|
|
|
|
placeholder="توضیح کوتاهی درباره هدف این قانون بنویسید..."
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="block">
|
|
|
|
|
|
<header class="block-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2>زمان و تعداد</h2>
|
|
|
|
|
|
<p>چه زمانی و چه تعداد باید پیامک ارسال شود؟</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="block-ico clock" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="clock" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<div class="m-schedule">
|
|
|
|
|
|
<label class="m-opt" :class="{ on: !delayLater }">
|
|
|
|
|
|
<input v-model="delayLater" type="radio" :value="false" />
|
|
|
|
|
|
<i />
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<strong>بلافاصله</strong>
|
|
|
|
|
|
<small>بلافاصله بعد از وقوع رویداد</small>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div class="m-opt-box" :class="{ on: delayLater }">
|
|
|
|
|
|
<label class="m-opt tight">
|
|
|
|
|
|
<input v-model="delayLater" type="radio" :value="true" />
|
|
|
|
|
|
<i />
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<strong>با تأخیر زمانی</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div v-if="delayLater" class="m-delay">
|
|
|
|
|
|
<em>بعد از</em>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model.number="startDelayValue"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min="1"
|
|
|
|
|
|
:max="delayMax"
|
|
|
|
|
|
aria-label="تأخیر"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<select v-model="startDelayUnit" aria-label="واحد تأخیر">
|
|
|
|
|
|
<option v-for="unit in DELAY_UNITS" :key="unit" :value="unit">
|
|
|
|
|
|
{{ MESSAGE_AUTOMATION_UNIT_LABELS[unit] }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="m-repeat">
|
|
|
|
|
|
<div class="m-delay">
|
|
|
|
|
|
<span class="cell-label">تعداد</span>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model.number="repeatCount"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min="1"
|
|
|
|
|
|
max="10"
|
|
|
|
|
|
aria-label="تعداد پیام"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="m-delay" :class="{ off: repeatCount <= 1 }">
|
|
|
|
|
|
<span class="cell-label">فاصله بین پیامها</span>
|
|
|
|
|
|
<em>هر</em>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model.number="intervalValue"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min="1"
|
|
|
|
|
|
:max="intervalMax"
|
|
|
|
|
|
:disabled="repeatCount <= 1"
|
|
|
|
|
|
aria-label="فاصله تکرار"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<select v-model="intervalUnit" :disabled="repeatCount <= 1" aria-label="واحد فاصله">
|
|
|
|
|
|
<option v-for="unit in DELAY_UNITS" :key="unit" :value="unit">
|
|
|
|
|
|
{{ MESSAGE_AUTOMATION_UNIT_LABELS[unit] }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="block">
|
|
|
|
|
|
<header class="block-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2>برای چه کسی؟</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="block-ico users" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="users" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
برای چه کسی
|
|
|
|
|
|
<select v-model="notifyTarget">
|
|
|
|
|
|
<option value="person">مشتری</option>
|
|
|
|
|
|
<option value="staff">مسئول پرونده</option>
|
|
|
|
|
|
<option value="both">مشتری و مسئول</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
از چه راهی؟
|
|
|
|
|
|
<select v-model="channel">
|
|
|
|
|
|
<option value="sms">پیامک</option>
|
|
|
|
|
|
<option value="app">ناتیف داخل برنامه</option>
|
|
|
|
|
|
<option value="both">پیامک و ناتیف</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section v-else-if="mobileStep === 2" class="stack">
|
|
|
|
|
|
<section class="block">
|
|
|
|
|
|
<header class="block-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2>رویداد/شرط اجرا</h2>
|
|
|
|
|
|
<p>این قانون در چه شرایطی فعال شود؟</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="block-ico funnel" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="funnel" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
نوع رویداد
|
|
|
|
|
|
<select v-model="trigger">
|
|
|
|
|
|
<option
|
|
|
|
|
|
v-for="item in MESSAGE_AUTOMATION_TRIGGERS"
|
|
|
|
|
|
:key="item"
|
|
|
|
|
|
:value="item"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ MESSAGE_AUTOMATION_TRIGGER_LABELS[item] }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section v-else-if="mobileStep === 3" class="stack">
|
|
|
|
|
|
<section class="block">
|
|
|
|
|
|
<header class="block-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2>متن پیام</h2>
|
|
|
|
|
|
<p>متن پیامک خود را بنویسید. میتوانید از فیلدهای پویا استفاده کنید.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="block-ico chat" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="chat" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<div class="msg-box">
|
|
|
|
|
|
<div class="msg-tools">
|
|
|
|
|
|
<div class="pop-wrap">
|
|
|
|
|
|
<button class="pill" type="button" @click="fieldsOpen = !fieldsOpen; emojiOpen = false">
|
|
|
|
|
|
<b>{ }</b>
|
|
|
|
|
|
فیلدهای پویا
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div v-if="fieldsOpen" class="pop">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="item in MESSAGE_AUTOMATION_FIELDS"
|
|
|
|
|
|
:key="item.token"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
@click="insertField(item.token)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<code>{{ item.token }}</code>
|
|
|
|
|
|
<span>{{ item.label }}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="pop-wrap">
|
|
|
|
|
|
<button class="icon-pill" type="button" aria-label="ایموجی" @click="emojiOpen = !emojiOpen; fieldsOpen = false">
|
|
|
|
|
|
😊
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div v-if="emojiOpen" class="pop emojis">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="mark in EMOJIS"
|
|
|
|
|
|
:key="mark"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
@click="insertEmoji(mark)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ mark }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
ref="bodyEl"
|
|
|
|
|
|
v-model="body"
|
|
|
|
|
|
maxlength="500"
|
|
|
|
|
|
rows="6"
|
|
|
|
|
|
placeholder="سلام {نام} عزیز"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span class="counter" :class="{ over: bodyOver }">
|
|
|
|
|
|
{{ toFaDigits(bodyCount) }}/{{ toFaDigits(MESSAGE_AUTOMATION_SMS_SEGMENT) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
<div class="tip">
|
|
|
|
|
|
<strong>
|
|
|
|
|
|
<UiIcon name="lightbulb" :size="16" />
|
|
|
|
|
|
نکته
|
|
|
|
|
|
</strong>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
میتوانید از فیلدهای پویا مثل
|
|
|
|
|
|
<bdi class="token">{نام}</bdi>،
|
|
|
|
|
|
<bdi class="token">{شماره}</bdi>
|
|
|
|
|
|
و
|
|
|
|
|
|
<bdi class="token">{تاریخ}</bdi>
|
|
|
|
|
|
در متن پیام استفاده کنید.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section v-else class="stack">
|
|
|
|
|
|
<section class="block">
|
|
|
|
|
|
<header class="block-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2>پیشنمایش پیام</h2>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="block-ico eye" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="eye" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<div class="m-preview">
|
|
|
|
|
|
<div class="m-bubble">
|
|
|
|
|
|
<p>{{ previewBody || 'متن پیام هنوز وارد نشده است.' }}</p>
|
|
|
|
|
|
<time>{{ previewClock }}</time>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="m-sms-note">
|
|
|
|
|
|
<strong>
|
|
|
|
|
|
<i>i</i>
|
|
|
|
|
|
تعداد کاراکتر: {{ toFaDigits(bodyCount) }} ({{ toFaDigits(smsParts) }} پیامک)
|
|
|
|
|
|
</strong>
|
|
|
|
|
|
<p>ممکن است بسته به نوع سامانه پیامکی تعداد کاراکتر متفاوت باشد.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<footer class="m-wiz-foot">
|
|
|
|
|
|
<button v-if="mobileStep < 4" class="btn primary" type="submit">
|
|
|
|
|
|
مرحله بعد
|
|
|
|
|
|
<span>←</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button v-else class="btn primary" type="submit" :disabled="saving">
|
|
|
|
|
|
{{ saving ? 'در حال ذخیره…' : editingId ? 'ذخیره' : 'ثبت قانون' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="btn ghost" type="button" @click="closeMobileWizard">انصراف</button>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
|
|
<header v-show="!isMobile" class="page-head">
|
2026-09-13 19:11:47 +03:30
|
|
|
|
<div class="head-copy">
|
2026-09-14 16:49:43 +03:30
|
|
|
|
<h1>اتوماسیون پیامکها</h1>
|
2026-09-13 19:11:47 +03:30
|
|
|
|
<p class="lead">
|
2026-09-14 16:49:43 +03:30
|
|
|
|
پیامک و ناتیف بعد از رویداد، با تأخیر یا همان لحظه.
|
2026-09-13 19:11:47 +03:30
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
2026-09-13 20:11:48 +03:30
|
|
|
|
<div class="head-actions">
|
|
|
|
|
|
<button class="btn ghost" type="button" :disabled="loading" @click="load">
|
|
|
|
|
|
بروزرسانی
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-09-13 19:11:47 +03:30
|
|
|
|
</header>
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
<p v-show="!isMobile && error" class="error">{{ error }}</p>
|
|
|
|
|
|
<p v-show="!isMobile && ok" class="ok">{{ ok }}</p>
|
2026-09-13 19:11:47 +03:30
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
<form v-show="!isMobile" class="composer" @submit.prevent="save">
|
|
|
|
|
|
<div class="stack">
|
|
|
|
|
|
<section class="block">
|
|
|
|
|
|
<header class="block-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2><b>{{ toFaDigits(1) }}</b> اطلاعات کلی</h2>
|
|
|
|
|
|
<p>ابتدا یک نام و توضیح برای این قانون مشخص کنید.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="block-ico" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="document" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
<span>عنوان قانون <em>*</em></span>
|
|
|
|
|
|
<input v-model="name" maxlength="120" placeholder="مثلاً تشکر بعد از فاکتور" />
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
توضیحات
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="description"
|
|
|
|
|
|
rows="2"
|
|
|
|
|
|
maxlength="500"
|
|
|
|
|
|
placeholder="توضیح کوتاهی درباره هدف این قانون بنویسید..."
|
|
|
|
|
|
/>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="block">
|
|
|
|
|
|
<header class="block-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2><b>{{ toFaDigits(2) }}</b> زمان و تعداد</h2>
|
|
|
|
|
|
<p>چه زمانی و چه تعداد باید پیامک ارسال شود؟</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="block-ico clock" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="clock" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<div class="schedule">
|
|
|
|
|
|
<div class="schedule-row">
|
|
|
|
|
|
<label class="choice" :class="{ on: !delayLater }">
|
|
|
|
|
|
<input v-model="delayLater" type="radio" :value="false" />
|
|
|
|
|
|
<i />
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<strong>بلافاصله</strong>
|
|
|
|
|
|
<small>بلافاصله بعد از وقوع رویداد</small>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<div class="split flush" :class="{ off: !delayLater }">
|
|
|
|
|
|
<label class="choice" :class="{ on: delayLater }">
|
|
|
|
|
|
<input v-model="delayLater" type="radio" :value="true" />
|
|
|
|
|
|
<i />
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<strong>با تأخیر زمانی</strong>
|
|
|
|
|
|
<small>با فاصله از وقوع رویداد</small>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<span class="delay-controls">
|
|
|
|
|
|
<em>بعد از</em>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model.number="startDelayValue"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min="1"
|
|
|
|
|
|
:max="delayMax"
|
|
|
|
|
|
:disabled="!delayLater"
|
|
|
|
|
|
aria-label="تأخیر"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<select v-model="startDelayUnit" :disabled="!delayLater" aria-label="واحد تأخیر">
|
|
|
|
|
|
<option v-for="unit in DELAY_UNITS" :key="unit" :value="unit">
|
|
|
|
|
|
{{ MESSAGE_AUTOMATION_UNIT_LABELS[unit] }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="schedule-row">
|
|
|
|
|
|
<label class="split">
|
|
|
|
|
|
<span class="cell-label">تعداد</span>
|
|
|
|
|
|
<span class="delay-controls">
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model.number="repeatCount"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min="1"
|
|
|
|
|
|
max="10"
|
|
|
|
|
|
aria-label="تعداد پیام"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="split flush" :class="{ off: repeatCount <= 1 }">
|
|
|
|
|
|
<span class="cell-label">فاصله بین پیامها</span>
|
|
|
|
|
|
<span class="delay-controls">
|
|
|
|
|
|
<em>هر</em>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model.number="intervalValue"
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
min="1"
|
|
|
|
|
|
:max="intervalMax"
|
|
|
|
|
|
:disabled="repeatCount <= 1"
|
|
|
|
|
|
aria-label="فاصله تکرار"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<select
|
|
|
|
|
|
v-model="intervalUnit"
|
|
|
|
|
|
:disabled="repeatCount <= 1"
|
|
|
|
|
|
aria-label="واحد فاصله"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option v-for="unit in DELAY_UNITS" :key="unit" :value="unit">
|
|
|
|
|
|
{{ MESSAGE_AUTOMATION_UNIT_LABELS[unit] }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="block">
|
|
|
|
|
|
<header class="block-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2><b>{{ toFaDigits(3) }}</b> رویداد/شرط اجرا</h2>
|
|
|
|
|
|
<p>این قانون در چه شرایطی فعال شود؟</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="block-ico funnel" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="funnel" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
نوع رویداد
|
|
|
|
|
|
<select v-model="trigger">
|
|
|
|
|
|
<option
|
|
|
|
|
|
v-for="item in MESSAGE_AUTOMATION_TRIGGERS"
|
|
|
|
|
|
:key="item"
|
|
|
|
|
|
:value="item"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ MESSAGE_AUTOMATION_TRIGGER_LABELS[item] }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
برای چه کسی
|
|
|
|
|
|
<select v-model="notifyTarget">
|
|
|
|
|
|
<option value="person">مشتری</option>
|
|
|
|
|
|
<option value="staff">مسئول پرونده</option>
|
|
|
|
|
|
<option value="both">مشتری و مسئول</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="field">
|
|
|
|
|
|
از چه راهی
|
|
|
|
|
|
<select v-model="channel">
|
|
|
|
|
|
<option value="sms">پیامک</option>
|
|
|
|
|
|
<option value="app">ناتیف داخل برنامه</option>
|
|
|
|
|
|
<option value="both">پیامک و ناتیف</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="block">
|
|
|
|
|
|
<header class="block-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h2><b>{{ toFaDigits(4) }}</b> متن پیام</h2>
|
|
|
|
|
|
<p>متن پیامک خود را بنویسید. میتوانید از فیلدهای پویا استفاده کنید.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="block-ico chat" aria-hidden="true">
|
|
|
|
|
|
<UiIcon name="chat" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<div class="msg-box">
|
|
|
|
|
|
<div class="msg-tools">
|
|
|
|
|
|
<div class="pop-wrap">
|
|
|
|
|
|
<button class="pill" type="button" @click="fieldsOpen = !fieldsOpen; emojiOpen = false">
|
|
|
|
|
|
<b>{ }</b>
|
|
|
|
|
|
فیلدهای پویا
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div v-if="fieldsOpen" class="pop">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="item in MESSAGE_AUTOMATION_FIELDS"
|
|
|
|
|
|
:key="item.token"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
@click="insertField(item.token)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<code>{{ item.token }}</code>
|
|
|
|
|
|
<span>{{ item.label }}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="pop-wrap">
|
|
|
|
|
|
<button class="icon-pill" type="button" aria-label="ایموجی" @click="emojiOpen = !emojiOpen; fieldsOpen = false">
|
|
|
|
|
|
😊
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div v-if="emojiOpen" class="pop emojis">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="mark in EMOJIS"
|
|
|
|
|
|
:key="mark"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
@click="insertEmoji(mark)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ mark }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
ref="bodyEl"
|
|
|
|
|
|
v-model="body"
|
|
|
|
|
|
maxlength="500"
|
|
|
|
|
|
rows="6"
|
|
|
|
|
|
placeholder="سلام {نام} عزیز"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span class="counter" :class="{ over: bodyOver }">
|
|
|
|
|
|
{{ toFaDigits(bodyCount) }}/{{ toFaDigits(MESSAGE_AUTOMATION_SMS_SEGMENT) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="form-actions">
|
|
|
|
|
|
<button class="btn primary" type="submit" :disabled="saving">
|
|
|
|
|
|
{{ saving ? 'در حال ذخیره…' : editingId ? 'ذخیره' : 'ساخت' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button v-if="editingId" class="btn ghost" type="button" @click="resetForm">
|
|
|
|
|
|
انصراف
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
2026-09-13 19:11:47 +03:30
|
|
|
|
</div>
|
2026-09-14 16:49:43 +03:30
|
|
|
|
|
|
|
|
|
|
<div class="side">
|
|
|
|
|
|
<aside class="intro">
|
|
|
|
|
|
<div class="hero">
|
|
|
|
|
|
<img
|
|
|
|
|
|
class="hero-art"
|
|
|
|
|
|
src="/brand/message-automation-hero.png"
|
|
|
|
|
|
alt=""
|
|
|
|
|
|
width="1600"
|
|
|
|
|
|
height="983"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<h3>ارتباط هوشمند با مشتریان</h3>
|
|
|
|
|
|
<p>با اتوماسیون پیامک، در زمان مناسب، پیام مناسب را به افراد مناسب ارسال کنید.</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li>
|
|
|
|
|
|
<UiIcon name="pulse" :size="20" />
|
|
|
|
|
|
افزایش فروش
|
|
|
|
|
|
</li>
|
|
|
|
|
|
<li>
|
|
|
|
|
|
<UiIcon name="clock" :size="20" />
|
|
|
|
|
|
صرفهجویی در زمان
|
|
|
|
|
|
</li>
|
|
|
|
|
|
<li>
|
|
|
|
|
|
<UiIcon name="users" :size="20" />
|
|
|
|
|
|
بهبود ارتباط با مشتریان
|
|
|
|
|
|
</li>
|
|
|
|
|
|
<li>
|
|
|
|
|
|
<UiIcon name="sliders" :size="20" />
|
|
|
|
|
|
کاملاً قابل شخصیسازی
|
|
|
|
|
|
</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
<div class="tip">
|
|
|
|
|
|
<strong>
|
|
|
|
|
|
<UiIcon name="lightbulb" :size="16" />
|
|
|
|
|
|
نکته
|
|
|
|
|
|
</strong>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
میتوانید از فیلدهای پویا مثل
|
|
|
|
|
|
<bdi class="token">{نام}</bdi>،
|
|
|
|
|
|
<bdi class="token">{شماره}</bdi>
|
|
|
|
|
|
و
|
|
|
|
|
|
<bdi class="token">{تاریخ}</bdi>
|
|
|
|
|
|
در متن پیام استفاده کنید.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<button type="button" @click="fieldsSheet = true">
|
|
|
|
|
|
مشاهده فهرست فیلدها
|
|
|
|
|
|
<span>←</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-09-13 19:11:47 +03:30
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
<div v-if="fieldsSheet" class="sheet-layer">
|
|
|
|
|
|
<button class="dim" type="button" aria-label="بستن" @click="fieldsSheet = false" />
|
|
|
|
|
|
<section class="sheet" role="dialog" aria-modal="true" aria-label="فهرست فیلدها">
|
|
|
|
|
|
<header>
|
|
|
|
|
|
<h3>فهرست فیلدهای پویا</h3>
|
|
|
|
|
|
<button type="button" aria-label="بستن" @click="fieldsSheet = false">
|
|
|
|
|
|
<UiIcon name="x" :size="16" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="item in MESSAGE_AUTOMATION_FIELDS"
|
|
|
|
|
|
:key="item.token"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
@click="insertField(item.token)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<code>{{ item.token }}</code>
|
|
|
|
|
|
<span>{{ item.label }}</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-show="!isMobile" class="list-wrap">
|
2026-09-13 20:11:48 +03:30
|
|
|
|
<table v-if="items.length" class="desk-table">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>عنوان</th>
|
|
|
|
|
|
<th>رویداد</th>
|
|
|
|
|
|
<th>زمانبندی</th>
|
|
|
|
|
|
<th>مخاطب</th>
|
|
|
|
|
|
<th>کانال</th>
|
2026-09-15 11:05:29 +03:30
|
|
|
|
<th>عملیات</th>
|
2026-09-13 20:11:48 +03:30
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="row in items" :key="row.id" :class="{ off: !row.isActive }">
|
|
|
|
|
|
<td>
|
|
|
|
|
|
<strong>{{ row.name }}</strong>
|
2026-09-14 16:49:43 +03:30
|
|
|
|
<span class="preview">{{ row.description || row.body }}</span>
|
2026-09-13 20:11:48 +03:30
|
|
|
|
</td>
|
|
|
|
|
|
<td>{{ MESSAGE_AUTOMATION_TRIGGER_LABELS[row.trigger] }}</td>
|
|
|
|
|
|
<td>{{ scheduleLabel(row) }}</td>
|
|
|
|
|
|
<td>{{ MESSAGE_AUTOMATION_TARGET_LABELS[row.notifyTarget] }}</td>
|
|
|
|
|
|
<td>{{ MESSAGE_AUTOMATION_CHANNEL_LABELS[row.channel] }}</td>
|
2026-09-15 11:05:29 +03:30
|
|
|
|
<td class="ops">
|
2026-09-15 14:10:52 +03:30
|
|
|
|
<div class="ops-row">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="icon-btn status"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:class="{ on: row.isActive }"
|
|
|
|
|
|
:aria-pressed="row.isActive"
|
|
|
|
|
|
:aria-label="row.isActive ? 'فعال' : 'غیرفعال'"
|
|
|
|
|
|
:title="row.isActive ? 'فعال' : 'غیرفعال'"
|
|
|
|
|
|
@click="toggle(row)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<UiIcon :name="row.isActive ? 'check' : 'x-circle'" :size="24" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="icon-btn edit" type="button" title="ویرایش" aria-label="ویرایش" @click="startEdit(row)">
|
|
|
|
|
|
<UiIcon name="edit" :size="24" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="icon-btn danger" type="button" title="حذف" aria-label="حذف" @click="askRemove(row)">
|
|
|
|
|
|
<UiIcon name="trash" :size="24" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-09-13 20:11:48 +03:30
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="mobile-cards">
|
|
|
|
|
|
<article
|
|
|
|
|
|
v-for="row in items"
|
|
|
|
|
|
:key="row.id"
|
|
|
|
|
|
class="card"
|
|
|
|
|
|
:class="{ off: !row.isActive }"
|
|
|
|
|
|
>
|
|
|
|
|
|
<header>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<strong>{{ row.name }}</strong>
|
|
|
|
|
|
<p class="muted">{{ MESSAGE_AUTOMATION_TRIGGER_LABELS[row.trigger] }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="toggle"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:class="{ on: row.isActive }"
|
|
|
|
|
|
:aria-pressed="row.isActive"
|
|
|
|
|
|
:aria-label="row.isActive ? 'فعال' : 'غیرفعال'"
|
|
|
|
|
|
@click="toggle(row)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
<p>{{ scheduleLabel(row) }}</p>
|
|
|
|
|
|
<p class="muted">
|
|
|
|
|
|
{{ MESSAGE_AUTOMATION_TARGET_LABELS[row.notifyTarget] }}
|
|
|
|
|
|
·
|
|
|
|
|
|
{{ MESSAGE_AUTOMATION_CHANNEL_LABELS[row.channel] }}
|
|
|
|
|
|
</p>
|
2026-09-14 16:49:43 +03:30
|
|
|
|
<p class="preview">{{ row.description || row.body }}</p>
|
2026-09-13 20:11:48 +03:30
|
|
|
|
<div class="card-actions">
|
|
|
|
|
|
<button class="btn ghost" type="button" @click="startEdit(row)">ویرایش</button>
|
2026-09-15 14:10:52 +03:30
|
|
|
|
<button class="btn danger" type="button" @click="askRemove(row)">حذف</button>
|
2026-09-13 20:11:48 +03:30
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</div>
|
2026-09-13 19:11:47 +03:30
|
|
|
|
<p v-if="!loading && !items.length" class="empty">هنوز قانون پیامی نیست.</p>
|
|
|
|
|
|
</div>
|
2026-09-15 14:10:52 +03:30
|
|
|
|
|
|
|
|
|
|
<div v-if="pendingDelete" class="sheet-layer">
|
|
|
|
|
|
<button class="dim" type="button" aria-label="انصراف" @click="cancelRemove" />
|
|
|
|
|
|
<section class="confirm-box" role="dialog" aria-modal="true" aria-labelledby="delete-confirm-title">
|
|
|
|
|
|
<h3 id="delete-confirm-title">حذف قانون</h3>
|
|
|
|
|
|
<p>آیا از حذف مطمئن هستید؟</p>
|
|
|
|
|
|
<div class="confirm-actions">
|
|
|
|
|
|
<button class="btn danger" type="button" @click="confirmRemove">حذف</button>
|
|
|
|
|
|
<button class="btn ghost" type="button" @click="cancelRemove">انصراف</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
2026-09-13 19:11:47 +03:30
|
|
|
|
</section>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.page {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
min-width: 0;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
width: 100%;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 1rem;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.head-copy {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.head-actions,
|
|
|
|
|
|
.form-actions,
|
|
|
|
|
|
.card-actions,
|
|
|
|
|
|
.ops {
|
2026-09-13 20:11:48 +03:30
|
|
|
|
display: flex;
|
2026-09-14 16:49:43 +03:30
|
|
|
|
gap: 0.5rem;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
flex-wrap: wrap;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h1 {
|
|
|
|
|
|
margin: 0 0 0.3rem;
|
|
|
|
|
|
font-size: clamp(1.45rem, 2.5vw, 1.85rem);
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.lead,
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.muted {
|
2026-09-13 19:11:47 +03:30
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.composer {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
align-items: start;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.stack {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.85rem;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.block,
|
|
|
|
|
|
.intro,
|
2026-09-13 20:11:48 +03:30
|
|
|
|
.list-wrap {
|
2026-09-14 16:49:43 +03:30
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1px solid #e8eef3;
|
|
|
|
|
|
border-radius: 22px;
|
|
|
|
|
|
min-width: 0;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.block {
|
2026-09-13 20:11:48 +03:30
|
|
|
|
display: grid;
|
2026-09-14 16:49:43 +03:30
|
|
|
|
gap: 0.85rem;
|
|
|
|
|
|
padding: 1.15rem 1.2rem 1.2rem;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.block-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 0.8rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.block-head h2 {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.block-head h2 b {
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.block-head p {
|
|
|
|
|
|
margin: 0.2rem 0 0;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.block-ico {
|
|
|
|
|
|
width: 2.15rem;
|
|
|
|
|
|
height: 2.15rem;
|
|
|
|
|
|
border-radius: 999px;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
display: grid;
|
2026-09-14 16:49:43 +03:30
|
|
|
|
place-items: center;
|
|
|
|
|
|
background: #ecfdf5;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.block-ico.clock {
|
|
|
|
|
|
background: #ecfeff;
|
|
|
|
|
|
color: #0e7490;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.block-ico.funnel {
|
|
|
|
|
|
background: #f5f3ff;
|
|
|
|
|
|
color: #6d28d9;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.block-ico.chat {
|
|
|
|
|
|
background: #ecfeff;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.block-ico.users {
|
|
|
|
|
|
background: #ecfdf5;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.block-ico.eye {
|
|
|
|
|
|
background: #ecfeff;
|
|
|
|
|
|
color: #0e7490;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.field {
|
|
|
|
|
|
display: grid;
|
2026-09-14 16:49:43 +03:30
|
|
|
|
gap: 0.35rem;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
color: #64748b;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
font-size: 0.86rem;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.field em {
|
|
|
|
|
|
color: #ef4444;
|
|
|
|
|
|
font-style: normal;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
input,
|
|
|
|
|
|
select,
|
|
|
|
|
|
textarea {
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
2026-09-14 16:49:43 +03:30
|
|
|
|
border-radius: 12px;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
min-height: var(--control-h);
|
|
|
|
|
|
padding: 0 0.85rem;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
textarea {
|
2026-09-14 16:49:43 +03:30
|
|
|
|
min-height: 4.4rem;
|
|
|
|
|
|
padding: 0.65rem 0.85rem;
|
|
|
|
|
|
resize: vertical;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.choice {
|
|
|
|
|
|
position: relative;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
display: flex;
|
2026-09-14 16:49:43 +03:30
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 0.65rem;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.choice input {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.choice i {
|
|
|
|
|
|
width: 1.15rem;
|
|
|
|
|
|
height: 1.15rem;
|
|
|
|
|
|
margin-top: 0.1rem;
|
|
|
|
|
|
border: 2px solid #cbd5e1;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.choice.on i {
|
|
|
|
|
|
border-color: #14b8a6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.choice.on i::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 3px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #14b8a6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.choice span {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.choice strong {
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 0.92rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.choice small {
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.schedule {
|
|
|
|
|
|
--choice-indent: calc(1.15rem + 0.65rem);
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.9rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-row {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
|
|
|
|
|
|
align-items: start;
|
|
|
|
|
|
gap: 0.75rem 1.2rem;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 720px) {
|
|
|
|
|
|
.schedule-row {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.split {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
gap: 0.65rem;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-row > .split:not(.flush) {
|
|
|
|
|
|
padding-inline-start: var(--choice-indent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.split.flush {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: 9rem 12.5rem;
|
|
|
|
|
|
justify-content: end;
|
|
|
|
|
|
column-gap: 1.35rem;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.split.flush .delay-controls {
|
|
|
|
|
|
justify-self: end;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.split.flush .cell-label {
|
|
|
|
|
|
justify-self: start;
|
|
|
|
|
|
margin-inline-start: var(--choice-indent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-row:first-child .split.flush {
|
|
|
|
|
|
align-items: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.split.off .delay-controls {
|
|
|
|
|
|
opacity: 0.45;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell-label {
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 0.92rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.delay-controls {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 0.86rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.delay-controls em {
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.delay-controls input,
|
|
|
|
|
|
.delay-controls select {
|
|
|
|
|
|
width: auto;
|
|
|
|
|
|
min-height: 2.15rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.delay-controls input {
|
|
|
|
|
|
width: 3.4rem;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.delay-controls select {
|
|
|
|
|
|
width: 5.8rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.msg-box {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.55rem;
|
|
|
|
|
|
padding: 0.75rem;
|
|
|
|
|
|
border: 1px solid #e8eef3;
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
background: #fafbfc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.msg-tools {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0.45rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pop-wrap {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pill,
|
|
|
|
|
|
.icon-pill {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
min-height: 2rem;
|
|
|
|
|
|
padding: 0 0.7rem;
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
font-size: 0.82rem;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pill b {
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.icon-pill {
|
|
|
|
|
|
width: 2rem;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pop {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: calc(100% + 6px);
|
|
|
|
|
|
inset-inline-start: 0;
|
|
|
|
|
|
z-index: 5;
|
|
|
|
|
|
min-width: 12rem;
|
|
|
|
|
|
padding: 0.35rem;
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.1);
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.15rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pop button {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 0.6rem;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
padding: 0.45rem 0.5rem;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pop button:hover {
|
|
|
|
|
|
background: #f1f5f9;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pop code,
|
|
|
|
|
|
.tip code,
|
|
|
|
|
|
.sheet code {
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pop.emojis {
|
|
|
|
|
|
grid-template-columns: repeat(6, 1fr);
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pop.emojis button {
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
font-size: 1.1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.counter {
|
|
|
|
|
|
justify-self: start;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.counter.over {
|
|
|
|
|
|
color: #b45309;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.side {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.85rem;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.intro {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 1.15rem;
|
|
|
|
|
|
padding: 1.35rem 1.15rem 1.35rem;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: #eef4fa;
|
|
|
|
|
|
box-shadow: 0 0 0 1px #eef2f6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hero {
|
|
|
|
|
|
text-align: start;
|
|
|
|
|
|
padding-bottom: 1.15rem;
|
|
|
|
|
|
border-bottom: 1px solid #e2e8f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hero-art {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
width: min(100%, 15.5rem);
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
margin: 0.15rem auto 1.65rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hero h3 {
|
|
|
|
|
|
margin: 0 0 0.45rem;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 1.02rem;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hero p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
line-height: 1.85;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.intro ul {
|
|
|
|
|
|
list-style: none;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
margin: 0.2rem 0 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 1.55rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.intro li {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
gap: 0.6rem;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.intro li :deep(.ico) {
|
|
|
|
|
|
color: #14b8a6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tip {
|
|
|
|
|
|
padding: 1rem 1.05rem 1rem;
|
|
|
|
|
|
border-radius: 22px;
|
|
|
|
|
|
background: #ecfdf5;
|
|
|
|
|
|
color: #475569;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tip strong {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
margin-bottom: 0.45rem;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
font-size: 1.02rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tip p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 0.88rem;
|
|
|
|
|
|
line-height: 1.9;
|
|
|
|
|
|
text-align: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.token {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
padding: 0 0.15rem;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
direction: ltr;
|
|
|
|
|
|
unicode-bidi: isolate;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tip button {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
margin-top: 0.75rem;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sheet-layer {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
inset: 0;
|
|
|
|
|
|
z-index: 90;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
padding: 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sheet-layer .dim {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 0;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: rgba(15, 23, 42, 0.32);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-15 14:10:52 +03:30
|
|
|
|
.confirm-box {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
width: min(22rem, 100%);
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.85rem;
|
|
|
|
|
|
padding: 1.15rem 1.2rem 1.1rem;
|
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
box-shadow: 0 18px 40px rgba(15, 23, 42, 0.16);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm-box h3 {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 1.05rem;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm-box p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #475569;
|
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.sheet {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
width: min(22rem, 100%);
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
padding: 1rem;
|
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sheet header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
margin-bottom: 0.4rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sheet header h3 {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sheet header button,
|
|
|
|
|
|
.sheet > button {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sheet header button {
|
|
|
|
|
|
width: 2rem;
|
|
|
|
|
|
height: 2rem;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.sheet > button {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 0.65rem 0.7rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.list-wrap {
|
|
|
|
|
|
padding: 0.85rem 1.1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 1.4rem 0.2rem;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
color: #64748b;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
gap: 0.35rem;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
min-height: var(--control-h);
|
|
|
|
|
|
padding: 0 0.95rem;
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn.primary {
|
|
|
|
|
|
background: #14b8a6;
|
|
|
|
|
|
color: #fff;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
box-shadow: 0 8px 18px rgba(20, 184, 166, 0.24);
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn.ghost {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
border-color: #99f6e4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn.danger {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
color: #b91c1c;
|
|
|
|
|
|
border-color: #fecaca;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-13 20:11:48 +03:30
|
|
|
|
.desk-table {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.desk-table th,
|
|
|
|
|
|
.desk-table td {
|
|
|
|
|
|
padding: 0.85rem 0.9rem;
|
|
|
|
|
|
text-align: start;
|
|
|
|
|
|
border-bottom: 1px solid #f1f5f9;
|
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.desk-table th {
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.desk-table td {
|
|
|
|
|
|
font-size: 0.88rem;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.desk-table td strong {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.desk-table tr.off {
|
|
|
|
|
|
opacity: 0.62;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-15 11:05:29 +03:30
|
|
|
|
.desk-table .ops {
|
2026-09-15 14:10:52 +03:30
|
|
|
|
display: table-cell;
|
2026-09-15 11:05:29 +03:30
|
|
|
|
width: 1%;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
white-space: nowrap;
|
2026-09-15 14:10:52 +03:30
|
|
|
|
vertical-align: middle;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ops-row {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.35rem;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-15 11:05:29 +03:30
|
|
|
|
.icon-btn {
|
2026-09-15 14:10:52 +03:30
|
|
|
|
display: inline-grid;
|
2026-09-15 11:05:29 +03:30
|
|
|
|
place-items: center;
|
2026-09-15 14:10:52 +03:30
|
|
|
|
flex: 0 0 2.75rem;
|
|
|
|
|
|
width: 2.75rem;
|
|
|
|
|
|
height: 2.75rem;
|
2026-09-15 11:05:29 +03:30
|
|
|
|
padding: 0;
|
2026-09-15 14:10:52 +03:30
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
background: transparent;
|
2026-09-15 11:05:29 +03:30
|
|
|
|
color: #64748b;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.icon-btn:hover {
|
2026-09-15 14:10:52 +03:30
|
|
|
|
background: transparent;
|
2026-09-15 11:05:29 +03:30
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-15 14:10:52 +03:30
|
|
|
|
.icon-btn.edit {
|
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.icon-btn.edit:hover {
|
|
|
|
|
|
color: #1d4ed8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-15 11:05:29 +03:30
|
|
|
|
.icon-btn.danger {
|
2026-09-15 14:10:52 +03:30
|
|
|
|
color: #dc2626;
|
2026-09-15 11:05:29 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.icon-btn.danger:hover {
|
2026-09-15 14:10:52 +03:30
|
|
|
|
color: #b91c1c;
|
|
|
|
|
|
background: transparent;
|
2026-09-15 11:05:29 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.icon-btn.status {
|
|
|
|
|
|
color: #dc2626;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.icon-btn.status.on {
|
|
|
|
|
|
color: #16a34a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-13 20:11:48 +03:30
|
|
|
|
.mobile-cards {
|
2026-09-13 19:11:47 +03:30
|
|
|
|
display: grid;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
padding: 0.95rem 1rem;
|
|
|
|
|
|
border: 1px solid #eef2f7;
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
background: #f8fafc;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card.off {
|
|
|
|
|
|
opacity: 0.7;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card header {
|
|
|
|
|
|
display: flex;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
align-items: flex-start;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-13 20:11:48 +03:30
|
|
|
|
.card .preview {
|
2026-09-13 19:11:47 +03:30
|
|
|
|
color: #334155;
|
|
|
|
|
|
font-size: 0.88rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-13 20:11:48 +03:30
|
|
|
|
.toggle {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 42px;
|
|
|
|
|
|
height: 24px;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 999px;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
background: #cbd5e1;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
cursor: pointer;
|
2026-09-13 20:11:48 +03:30
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
padding: 0;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-13 20:11:48 +03:30
|
|
|
|
.toggle::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 2px;
|
|
|
|
|
|
left: 2px;
|
|
|
|
|
|
width: 20px;
|
|
|
|
|
|
height: 20px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.16);
|
|
|
|
|
|
transition: left 0.16s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toggle.on {
|
|
|
|
|
|
background: #14b8a6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toggle.on::after {
|
|
|
|
|
|
left: 20px;
|
2026-09-13 19:11:47 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.error {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #b91c1c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ok {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
2026-09-13 20:11:48 +03:30
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.desk {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
2026-09-13 20:11:48 +03:30
|
|
|
|
|
2026-09-14 16:49:43 +03:30
|
|
|
|
.btn.wide {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-home,
|
|
|
|
|
|
.m-wiz {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-title {
|
|
|
|
|
|
text-align: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-title h1,
|
|
|
|
|
|
.m-wiz-head h1 {
|
|
|
|
|
|
margin: 0 0 0.35rem;
|
|
|
|
|
|
font-size: 1.45rem;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-title p,
|
|
|
|
|
|
.m-hero p,
|
|
|
|
|
|
.m-wiz-head p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.86rem;
|
|
|
|
|
|
line-height: 1.75;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-hero {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 0.15rem 0 0.1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-hero .hero-art {
|
|
|
|
|
|
width: min(100%, 18rem);
|
|
|
|
|
|
margin: 0.15rem auto 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-hero h2 {
|
|
|
|
|
|
margin: 0 0 0.35rem;
|
|
|
|
|
|
font-size: 1.08rem;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-hero .sub {
|
|
|
|
|
|
margin-bottom: 1.05rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-hero .btn {
|
|
|
|
|
|
background: #0f766e;
|
|
|
|
|
|
box-shadow: 0 10px 22px rgba(15, 118, 110, 0.22);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-tools {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-search {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
min-height: var(--control-h);
|
|
|
|
|
|
padding: 0 0.85rem;
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-search input {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
min-height: 0;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-tabs {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
padding: 0.25rem;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #eef2f6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-tabs button {
|
|
|
|
|
|
min-height: 2.2rem;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-tabs button.on {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.06);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-cards {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-card {
|
|
|
|
|
|
padding: 0.95rem 1rem 0.85rem;
|
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
box-shadow: 0 0 0 1px #eef2f7;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-card.off {
|
|
|
|
|
|
opacity: 0.78;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-card header {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
|
|
|
|
align-items: start;
|
|
|
|
|
|
gap: 0.65rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-copy strong {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-copy p {
|
|
|
|
|
|
margin: 0.25rem 0 0;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-menu {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-menu > button,
|
|
|
|
|
|
.m-back {
|
|
|
|
|
|
width: 2rem;
|
|
|
|
|
|
height: 2rem;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-menu .pop {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset-inline-start: 0;
|
|
|
|
|
|
top: 2.1rem;
|
|
|
|
|
|
z-index: 4;
|
|
|
|
|
|
min-width: 7.5rem;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
padding: 0.35rem;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.12);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-menu .pop button {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
text-align: start;
|
|
|
|
|
|
padding: 0.5rem 0.6rem;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-menu .pop .danger {
|
|
|
|
|
|
color: #b91c1c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-card footer {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
margin-top: 0.85rem;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-status {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-status i {
|
|
|
|
|
|
width: 0.5rem;
|
|
|
|
|
|
height: 0.5rem;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #cbd5e1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-status.on {
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-status.on i {
|
|
|
|
|
|
background: #14b8a6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-wiz-head {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.65rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-wiz-head h1 {
|
|
|
|
|
|
font-size: 1.15rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-wiz-head > div {
|
|
|
|
|
|
text-align: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-back {
|
|
|
|
|
|
transform: scaleX(-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-schedule {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.7rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 0.65rem;
|
|
|
|
|
|
padding: 0.85rem 0.9rem;
|
|
|
|
|
|
border: 1px solid #e8eef3;
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt.tight {
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
padding: 0.85rem 0.9rem 0.55rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt input {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt i {
|
|
|
|
|
|
width: 1.15rem;
|
|
|
|
|
|
height: 1.15rem;
|
|
|
|
|
|
margin-top: 0.15rem;
|
|
|
|
|
|
border: 2px solid #cbd5e1;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt.tight i {
|
|
|
|
|
|
margin-top: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt.on i,
|
|
|
|
|
|
.m-opt-box.on .m-opt i {
|
|
|
|
|
|
border-color: #14b8a6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt.on i::after,
|
|
|
|
|
|
.m-opt-box.on .m-opt i::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 3px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
background: #14b8a6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt span {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.12rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt strong {
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 0.92rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt small {
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt.on,
|
|
|
|
|
|
.m-opt-box.on {
|
|
|
|
|
|
border-color: #99f6e4;
|
|
|
|
|
|
background: #f0fdfa;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt-box {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
border: 1px solid #e8eef3;
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-opt-box .m-delay {
|
|
|
|
|
|
padding: 0 0.9rem 0.85rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-repeat {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
margin-top: 0.25rem;
|
|
|
|
|
|
padding-top: 0.85rem;
|
|
|
|
|
|
border-top: 1px solid #eef2f6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-delay {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
gap: 0.45rem;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 0.86rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-delay.off {
|
|
|
|
|
|
opacity: 0.45;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-delay em {
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-delay input,
|
|
|
|
|
|
.m-delay select {
|
|
|
|
|
|
width: auto;
|
|
|
|
|
|
min-height: 2.15rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-delay input[type='number'] {
|
|
|
|
|
|
width: 3.4rem;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-delay select {
|
|
|
|
|
|
width: 5.8rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-steps {
|
|
|
|
|
|
list-style: none;
|
|
|
|
|
|
margin: 0.2rem 0 0.3rem;
|
|
|
|
|
|
padding: 0.4rem 0 0;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(4, 1fr);
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-steps::before {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 1.25rem;
|
|
|
|
|
|
inset-inline: 12%;
|
|
|
|
|
|
height: 2px;
|
|
|
|
|
|
background: #e2e8f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-steps li {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
justify-items: center;
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.72rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-steps li b {
|
|
|
|
|
|
width: 1.85rem;
|
|
|
|
|
|
height: 1.85rem;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
background: #e2e8f0;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-steps li.on {
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-steps li.on b,
|
|
|
|
|
|
.m-steps li.done b {
|
|
|
|
|
|
background: #14b8a6;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-preview {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.85rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-bubble {
|
|
|
|
|
|
padding: 0.95rem 1rem 0.7rem;
|
|
|
|
|
|
border-radius: 18px;
|
|
|
|
|
|
background: #e8f7f8;
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-bubble p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
|
line-height: 1.9;
|
|
|
|
|
|
text-align: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-bubble time {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin-top: 0.7rem;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
|
text-align: end;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-sms-note {
|
|
|
|
|
|
padding: 0.85rem 0.95rem;
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
background: #eef8fb;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-sms-note strong {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
margin-bottom: 0.35rem;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 0.86rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-sms-note strong i {
|
|
|
|
|
|
width: 1.05rem;
|
|
|
|
|
|
height: 1.05rem;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
background: #bae6fd;
|
|
|
|
|
|
color: #0369a1;
|
|
|
|
|
|
font-size: 0.7rem;
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-sms-note p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
line-height: 1.75;
|
|
|
|
|
|
text-align: start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-wiz-foot {
|
|
|
|
|
|
position: sticky;
|
2026-09-17 17:36:21 +03:30
|
|
|
|
bottom: var(--mobile-tabbar-h);
|
2026-09-14 16:49:43 +03:30
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: 1.25fr 1fr;
|
|
|
|
|
|
gap: 0.65rem;
|
|
|
|
|
|
padding: 0.85rem 0 0.35rem;
|
|
|
|
|
|
background: linear-gradient(180deg, transparent, #fff 18%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.m-wiz-foot .btn.ghost {
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
border-color: #e2e8f0;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (min-width: 980px) {
|
|
|
|
|
|
.composer {
|
|
|
|
|
|
grid-template-columns: minmax(0, 1fr) 18rem;
|
|
|
|
|
|
}
|
2026-09-13 20:11:48 +03:30
|
|
|
|
|
|
|
|
|
|
.desk-table {
|
|
|
|
|
|
display: table;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mobile-cards {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-09-13 19:11:47 +03:30
|
|
|
|
</style>
|