2026-08-29 21:06:30 +03:30
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
|
|
|
|
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
|
|
|
|
|
import axios from 'axios';
|
|
|
|
|
|
import { api } from '@/lib/api';
|
2026-09-02 21:29:00 +03:30
|
|
|
|
import {
|
|
|
|
|
|
calendarDaysBetween,
|
|
|
|
|
|
formatClockFa,
|
|
|
|
|
|
formatJalaliDay,
|
|
|
|
|
|
startOfLocalDay,
|
|
|
|
|
|
toFaDigits,
|
|
|
|
|
|
} from '@/lib/jalali';
|
2026-09-12 01:33:07 +03:30
|
|
|
|
import {
|
|
|
|
|
|
UiButton,
|
|
|
|
|
|
UiModal,
|
|
|
|
|
|
UiPageHeader,
|
|
|
|
|
|
UiPagination,
|
|
|
|
|
|
} from '@/components/ui';
|
2026-09-02 21:29:00 +03:30
|
|
|
|
import UiIcon from '@/components/UiIcon.vue';
|
2026-09-10 01:22:24 +03:30
|
|
|
|
import ActivityFormView from '@/views/ActivityFormView.vue';
|
2026-09-02 21:29:00 +03:30
|
|
|
|
import type {
|
|
|
|
|
|
ActivityDto,
|
|
|
|
|
|
ActivityFilter,
|
|
|
|
|
|
ActivityType,
|
2026-09-15 17:33:05 +03:30
|
|
|
|
ImportContactsResult,
|
2026-09-02 21:29:00 +03:30
|
|
|
|
PaginatedResponse,
|
|
|
|
|
|
} from '@mento/shared';
|
2026-09-15 17:33:05 +03:30
|
|
|
|
import { brand } from '@mento/shared';
|
2026-09-02 21:29:00 +03:30
|
|
|
|
|
|
|
|
|
|
type TypeFilter = 'all' | ActivityType;
|
|
|
|
|
|
type UiStatus = 'done' | 'progress' | 'planned' | 'overdue' | 'canceled';
|
|
|
|
|
|
|
|
|
|
|
|
const TABS: Array<{ key: ActivityFilter; label: string }> = [
|
|
|
|
|
|
{ key: 'today', label: 'امروز' },
|
|
|
|
|
|
{ key: 'overdue', label: 'عقبافتاده' },
|
|
|
|
|
|
{ key: 'upcoming', label: 'بعداً' },
|
|
|
|
|
|
{ key: 'done', label: 'انجامشده' },
|
|
|
|
|
|
{ key: 'all', label: 'همه' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const TYPE_FILTERS: Array<{ key: TypeFilter; label: string }> = [
|
|
|
|
|
|
{ key: 'all', label: 'همه انواع' },
|
|
|
|
|
|
{ key: 'call', label: 'تماس' },
|
|
|
|
|
|
{ key: 'meeting', label: 'جلسه' },
|
|
|
|
|
|
{ key: 'task', label: 'وظیفه' },
|
|
|
|
|
|
{ key: 'note', label: 'پیام' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const PAGE_SIZE = 6;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
|
|
const filter = computed<ActivityFilter>(() => {
|
|
|
|
|
|
const value = typeof route.query.filter === 'string' ? route.query.filter : 'today';
|
|
|
|
|
|
if (['today', 'overdue', 'upcoming', 'done', 'all'].includes(value)) {
|
|
|
|
|
|
return value as ActivityFilter;
|
|
|
|
|
|
}
|
|
|
|
|
|
return 'today';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
const page = computed(() => {
|
|
|
|
|
|
const raw = Number(route.query.page);
|
|
|
|
|
|
return Number.isFinite(raw) && raw > 0 ? raw : 1;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-08-29 21:06:30 +03:30
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
const error = ref('');
|
|
|
|
|
|
const data = ref<PaginatedResponse<ActivityDto> | null>(null);
|
|
|
|
|
|
const q = ref(typeof route.query.q === 'string' ? route.query.q : '');
|
2026-09-02 21:29:00 +03:30
|
|
|
|
const typeFilter = ref<TypeFilter>('all');
|
|
|
|
|
|
const filterOpen = ref(false);
|
|
|
|
|
|
const ideasOpen = ref(false);
|
2026-09-10 01:22:24 +03:30
|
|
|
|
const activityModal = ref<{
|
|
|
|
|
|
type?: ActivityType;
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
} | null>(null);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
const menuId = ref<string | null>(null);
|
2026-09-15 17:33:05 +03:30
|
|
|
|
const busy = ref(false);
|
|
|
|
|
|
const importMsg = ref('');
|
|
|
|
|
|
const fileInput = ref<HTMLInputElement | null>(null);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
const counts = ref<Record<ActivityFilter, number>>({
|
|
|
|
|
|
today: 0,
|
|
|
|
|
|
overdue: 0,
|
|
|
|
|
|
upcoming: 0,
|
|
|
|
|
|
done: 0,
|
|
|
|
|
|
all: 0,
|
|
|
|
|
|
});
|
2026-08-29 21:06:30 +03:30
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
const typeLabel: Record<ActivityType, string> = {
|
2026-08-29 21:06:30 +03:30
|
|
|
|
call: 'تماس',
|
|
|
|
|
|
meeting: 'جلسه',
|
|
|
|
|
|
task: 'وظیفه',
|
2026-09-02 21:29:00 +03:30
|
|
|
|
note: 'پیام',
|
2026-08-29 21:06:30 +03:30
|
|
|
|
};
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
const typeIcon: Record<ActivityType, 'phone' | 'users' | 'check' | 'chat'> = {
|
|
|
|
|
|
call: 'phone',
|
|
|
|
|
|
meeting: 'users',
|
|
|
|
|
|
task: 'check',
|
|
|
|
|
|
note: 'chat',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const statusLabel: Record<UiStatus, string> = {
|
|
|
|
|
|
done: 'انجام شد',
|
|
|
|
|
|
progress: 'در حال انجام',
|
|
|
|
|
|
planned: 'برنامهریزی شده',
|
|
|
|
|
|
overdue: 'عقبافتاده',
|
|
|
|
|
|
canceled: 'لغو شده',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const statusIcon: Record<UiStatus, 'check' | 'clock' | 'calendar' | 'warning'> = {
|
|
|
|
|
|
done: 'check',
|
|
|
|
|
|
progress: 'clock',
|
|
|
|
|
|
planned: 'calendar',
|
|
|
|
|
|
overdue: 'warning',
|
|
|
|
|
|
canceled: 'warning',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const items = computed(() => {
|
|
|
|
|
|
const rows = data.value?.items ?? [];
|
|
|
|
|
|
if (typeFilter.value === 'all') return rows;
|
|
|
|
|
|
return rows.filter((row) => row.type === typeFilter.value);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const total = computed(() => data.value?.total ?? 0);
|
|
|
|
|
|
const pageCount = computed(() => Math.max(1, Math.ceil(total.value / PAGE_SIZE)));
|
|
|
|
|
|
const filterLabel = computed(() => TABS.find((t) => t.key === filter.value)?.label ?? 'امروز');
|
|
|
|
|
|
const trulyEmpty = computed(() => !loading.value && counts.value.all === 0 && !q.value.trim());
|
|
|
|
|
|
const listEmpty = computed(() => !loading.value && !items.value.length);
|
|
|
|
|
|
|
2026-09-09 13:12:13 +03:30
|
|
|
|
function isLastItem(id: string) {
|
|
|
|
|
|
const rows = items.value;
|
|
|
|
|
|
return rows.length > 0 && rows[rows.length - 1]?.id === id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
function isOverdue(item: ActivityDto) {
|
|
|
|
|
|
if (item.status !== 'open' || !item.dueAt) return false;
|
|
|
|
|
|
return startOfLocalDay(new Date(item.dueAt)).getTime() < startOfLocalDay().getTime();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function uiStatus(item: ActivityDto): UiStatus {
|
|
|
|
|
|
if (item.status === 'done') return 'done';
|
|
|
|
|
|
if (item.status === 'canceled') return 'canceled';
|
|
|
|
|
|
if (isOverdue(item)) return 'overdue';
|
|
|
|
|
|
if (item.dueAt && calendarDaysBetween(new Date(item.dueAt), new Date()) === 0) {
|
|
|
|
|
|
return 'progress';
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
2026-09-02 21:29:00 +03:30
|
|
|
|
return 'planned';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function clockText(item: ActivityDto) {
|
|
|
|
|
|
if (!item.dueAt) return '—';
|
|
|
|
|
|
return formatClockFa(new Date(item.dueAt));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function dayText(item: ActivityDto) {
|
|
|
|
|
|
if (!item.dueAt) return 'بدون موعد';
|
|
|
|
|
|
const days = calendarDaysBetween(new Date(item.dueAt), new Date());
|
|
|
|
|
|
if (days === 0) return 'امروز';
|
|
|
|
|
|
if (days === 1) return 'دیروز';
|
|
|
|
|
|
if (days === -1) return 'فردا';
|
|
|
|
|
|
return formatJalaliDay(new Date(item.dueAt));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function dateText(item: ActivityDto) {
|
|
|
|
|
|
if (!item.dueAt) return 'بدون موعد';
|
|
|
|
|
|
return formatJalaliDay(new Date(item.dueAt));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function subtitle(item: ActivityDto) {
|
|
|
|
|
|
return item.deal?.title || item.description || '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadCounts() {
|
|
|
|
|
|
const keys: ActivityFilter[] = ['today', 'overdue', 'upcoming', 'done', 'all'];
|
|
|
|
|
|
const results = await Promise.all(
|
|
|
|
|
|
keys.map((key) =>
|
|
|
|
|
|
api.get<PaginatedResponse<ActivityDto>>('/activities', {
|
|
|
|
|
|
params: { filter: key, limit: 1 },
|
|
|
|
|
|
}),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
const next = { ...counts.value };
|
|
|
|
|
|
keys.forEach((key, i) => {
|
|
|
|
|
|
next[key] = results[i]?.data.total ?? 0;
|
|
|
|
|
|
});
|
|
|
|
|
|
counts.value = next;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
error.value = '';
|
2026-09-02 21:29:00 +03:30
|
|
|
|
menuId.value = null;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
try {
|
|
|
|
|
|
const { data: res } = await api.get<PaginatedResponse<ActivityDto>>('/activities', {
|
2026-09-02 21:29:00 +03:30
|
|
|
|
params: {
|
|
|
|
|
|
filter: filter.value,
|
|
|
|
|
|
q: q.value || undefined,
|
|
|
|
|
|
page: page.value,
|
|
|
|
|
|
limit: PAGE_SIZE,
|
|
|
|
|
|
},
|
2026-08-29 21:06:30 +03:30
|
|
|
|
});
|
|
|
|
|
|
data.value = res;
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
if (axios.isAxiosError(e) && !e.response) {
|
|
|
|
|
|
error.value = 'اتصال به API برقرار نشد.';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
error.value = 'بارگذاری فعالیتها ناموفق بود.';
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function setFilter(next: ActivityFilter) {
|
2026-09-02 21:29:00 +03:30
|
|
|
|
filterOpen.value = false;
|
|
|
|
|
|
void router.replace({
|
2026-08-29 21:06:30 +03:30
|
|
|
|
name: 'activities',
|
|
|
|
|
|
query: {
|
|
|
|
|
|
...route.query,
|
|
|
|
|
|
filter: next === 'today' ? undefined : next,
|
2026-09-02 21:29:00 +03:30
|
|
|
|
page: undefined,
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function setPage(next: number) {
|
|
|
|
|
|
const safe = Math.min(Math.max(1, next), pageCount.value);
|
|
|
|
|
|
void router.replace({
|
|
|
|
|
|
name: 'activities',
|
|
|
|
|
|
query: {
|
|
|
|
|
|
...route.query,
|
|
|
|
|
|
page: safe === 1 ? undefined : String(safe),
|
2026-08-29 21:06:30 +03:30
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onSearch() {
|
2026-09-02 21:29:00 +03:30
|
|
|
|
void router.replace({
|
2026-08-29 21:06:30 +03:30
|
|
|
|
name: 'activities',
|
|
|
|
|
|
query: {
|
|
|
|
|
|
...route.query,
|
|
|
|
|
|
q: q.value.trim() || undefined,
|
2026-09-02 21:29:00 +03:30
|
|
|
|
page: undefined,
|
2026-08-29 21:06:30 +03:30
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
function setTypeFilter(next: TypeFilter) {
|
|
|
|
|
|
typeFilter.value = next;
|
|
|
|
|
|
filterOpen.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-29 21:06:30 +03:30
|
|
|
|
async function complete(id: string) {
|
2026-09-02 21:29:00 +03:30
|
|
|
|
menuId.value = null;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
try {
|
|
|
|
|
|
await api.patch(`/activities/${id}/complete`);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
await Promise.all([load(), loadCounts()]);
|
2026-08-29 21:06:30 +03:30
|
|
|
|
} catch {
|
|
|
|
|
|
error.value = 'ثبت انجامشدن ناموفق بود.';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
function closePopovers() {
|
|
|
|
|
|
filterOpen.value = false;
|
|
|
|
|
|
menuId.value = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-15 17:33:05 +03:30
|
|
|
|
async function exportExcel() {
|
|
|
|
|
|
busy.value = true;
|
|
|
|
|
|
importMsg.value = '';
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.get<Blob>('/activities/export', { responseType: 'blob' });
|
|
|
|
|
|
const url = URL.createObjectURL(data);
|
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
|
a.href = url;
|
|
|
|
|
|
a.download = `${brand.filePrefix}-activities.xlsx`;
|
|
|
|
|
|
a.click();
|
|
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
error.value = 'خروجی اکسل ناموفق بود.';
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
busy.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function pickImportFile() {
|
|
|
|
|
|
fileInput.value?.click();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function onImportFile(ev: Event) {
|
|
|
|
|
|
const input = ev.target as HTMLInputElement;
|
|
|
|
|
|
const file = input.files?.[0];
|
|
|
|
|
|
input.value = '';
|
|
|
|
|
|
if (!file) return;
|
|
|
|
|
|
busy.value = true;
|
|
|
|
|
|
importMsg.value = '';
|
|
|
|
|
|
error.value = '';
|
|
|
|
|
|
try {
|
|
|
|
|
|
const form = new FormData();
|
|
|
|
|
|
form.append('file', file);
|
|
|
|
|
|
const { data } = await api.post<ImportContactsResult>('/activities/import', form, {
|
|
|
|
|
|
headers: { 'Content-Type': undefined },
|
|
|
|
|
|
});
|
|
|
|
|
|
importMsg.value = `وارد شد: ${data.created} · تکراری رد شد: ${data.skipped}${
|
|
|
|
|
|
data.errors.length ? ` · خطا: ${data.errors.length}` : ''
|
|
|
|
|
|
}`;
|
|
|
|
|
|
if (data.errors.length) {
|
|
|
|
|
|
error.value = data.errors
|
|
|
|
|
|
.slice(0, 3)
|
|
|
|
|
|
.map((e) => `ردیف ${e.row}: ${e.message}`)
|
|
|
|
|
|
.join(' | ');
|
|
|
|
|
|
}
|
|
|
|
|
|
await Promise.all([load(), loadCounts()]);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
if (axios.isAxiosError(e)) {
|
|
|
|
|
|
const msg = (e.response?.data as { message?: string | string[] })?.message;
|
|
|
|
|
|
error.value = Array.isArray(msg) ? msg.join('، ') : (msg ?? 'ایمپورت ناموفق بود');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
error.value = 'ایمپورت ناموفق بود';
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
busy.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-29 21:06:30 +03:30
|
|
|
|
watch(
|
2026-09-02 21:29:00 +03:30
|
|
|
|
() => [route.query.filter, route.query.q, route.query.page],
|
2026-08-29 21:06:30 +03:30
|
|
|
|
() => {
|
|
|
|
|
|
q.value = typeof route.query.q === 'string' ? route.query.q : '';
|
|
|
|
|
|
void load();
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
void load();
|
|
|
|
|
|
void loadCounts();
|
|
|
|
|
|
});
|
2026-08-29 21:06:30 +03:30
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-09-02 21:29:00 +03:30
|
|
|
|
<section class="page" @click="closePopovers">
|
|
|
|
|
|
<nav class="crumbs" aria-label="مسیر">
|
|
|
|
|
|
<RouterLink :to="{ name: 'home' }">خانه</RouterLink>
|
|
|
|
|
|
<span>‹</span>
|
|
|
|
|
|
<span>فعالیتها</span>
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
2026-09-12 01:33:07 +03:30
|
|
|
|
<UiPageHeader
|
|
|
|
|
|
title="فعالیتها"
|
|
|
|
|
|
lead="تماس، جلسه و وظیفه — امروز و عقبافتادهها یکجا."
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #actions>
|
2026-09-17 16:46:00 +03:30
|
|
|
|
<UiButton variant="ghost" type="button" :disabled="busy" @click.stop="exportExcel">
|
2026-09-15 17:33:05 +03:30
|
|
|
|
<span class="btn-full">خروجی اکسل</span>
|
|
|
|
|
|
<span class="btn-short">خروجی</span>
|
2026-09-17 16:46:00 +03:30
|
|
|
|
</UiButton>
|
|
|
|
|
|
<UiButton variant="ghost" type="button" :disabled="busy" @click.stop="pickImportFile">
|
2026-09-15 17:33:05 +03:30
|
|
|
|
<span class="btn-full">ورود از اکسل</span>
|
|
|
|
|
|
<span class="btn-short">ورود</span>
|
2026-09-17 16:46:00 +03:30
|
|
|
|
</UiButton>
|
2026-09-15 17:33:05 +03:30
|
|
|
|
<input
|
|
|
|
|
|
ref="fileInput"
|
|
|
|
|
|
type="file"
|
|
|
|
|
|
accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
|
|
|
|
hidden
|
|
|
|
|
|
@change="onImportFile"
|
|
|
|
|
|
/>
|
2026-09-12 01:33:07 +03:30
|
|
|
|
<UiButton variant="primary" @click="activityModal = {}">
|
2026-09-10 01:22:24 +03:30
|
|
|
|
<UiIcon name="plus" :size="16" />
|
2026-09-02 21:29:00 +03:30
|
|
|
|
فعالیت جدید
|
2026-09-10 01:22:24 +03:30
|
|
|
|
</UiButton>
|
2026-09-12 01:33:07 +03:30
|
|
|
|
<RouterLink :to="{ name: 'activities-calendar' }">
|
|
|
|
|
|
<UiButton variant="ghost">
|
|
|
|
|
|
<UiIcon name="calendar" :size="16" />
|
|
|
|
|
|
تقویم
|
|
|
|
|
|
</UiButton>
|
2026-09-02 21:29:00 +03:30
|
|
|
|
</RouterLink>
|
2026-09-12 01:33:07 +03:30
|
|
|
|
</template>
|
|
|
|
|
|
</UiPageHeader>
|
2026-08-29 21:06:30 +03:30
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
<div class="tabs" role="tablist" aria-label="فیلتر فعالیت">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="tab in TABS"
|
|
|
|
|
|
:key="tab.key"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="tab"
|
|
|
|
|
|
:class="{ on: filter === tab.key }"
|
|
|
|
|
|
:aria-selected="filter === tab.key"
|
|
|
|
|
|
@click="setFilter(tab.key)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>{{ tab.label }}</span>
|
|
|
|
|
|
<em>{{ toFaDigits(counts[tab.key]) }}</em>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="tools">
|
2026-08-29 21:06:30 +03:30
|
|
|
|
<form class="search" @submit.prevent="onSearch">
|
2026-09-02 21:29:00 +03:30
|
|
|
|
<UiIcon name="search" :size="18" />
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="q"
|
|
|
|
|
|
type="search"
|
|
|
|
|
|
placeholder="جستجو در عنوان، مشتری، نوع فعالیت..."
|
|
|
|
|
|
/>
|
2026-08-29 21:06:30 +03:30
|
|
|
|
</form>
|
2026-09-02 21:29:00 +03:30
|
|
|
|
<div class="filter-wrap" @click.stop>
|
|
|
|
|
|
<button class="btn ghost filter-btn" type="button" @click="filterOpen = !filterOpen">
|
|
|
|
|
|
<UiIcon name="filter" :size="16" />
|
|
|
|
|
|
فیلتر
|
|
|
|
|
|
<UiIcon name="chevron" :size="14" class="chev" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div v-if="filterOpen" class="popover">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="opt in TYPE_FILTERS"
|
|
|
|
|
|
:key="opt.key"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
:class="{ on: typeFilter === opt.key }"
|
|
|
|
|
|
@click="setTypeFilter(opt.key)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ opt.label }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-08-29 21:06:30 +03:30
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p v-if="error" class="error">{{ error }}</p>
|
2026-09-15 17:33:05 +03:30
|
|
|
|
<p v-if="importMsg" class="ok">{{ importMsg }}</p>
|
|
|
|
|
|
<p v-if="loading" class="muted">در حال بارگذاری…</p>
|
2026-08-29 21:06:30 +03:30
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
<div v-else-if="listEmpty" class="empty-card">
|
|
|
|
|
|
<div class="art" aria-hidden="true">
|
|
|
|
|
|
<svg viewBox="0 0 168 148" fill="none">
|
|
|
|
|
|
<rect x="38" y="18" width="92" height="112" rx="16" fill="#E7F6F3" />
|
|
|
|
|
|
<rect x="50" y="32" width="68" height="84" rx="10" fill="#fff" />
|
|
|
|
|
|
<path d="M62 52h28" stroke="#14b8a6" stroke-width="4" stroke-linecap="round" />
|
|
|
|
|
|
<path d="M62 68h36" stroke="#99f6e4" stroke-width="4" stroke-linecap="round" />
|
|
|
|
|
|
<path d="M62 84h22" stroke="#99f6e4" stroke-width="4" stroke-linecap="round" />
|
|
|
|
|
|
<circle cx="118" cy="112" r="20" fill="#14b8a6" />
|
|
|
|
|
|
<path d="M118 104v16M110 112h16" stroke="#fff" stroke-width="3" stroke-linecap="round" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<h2>{{ trulyEmpty ? 'هنوز فعالیتی ثبت نشده!' : 'فعالیتی در این فهرست نیست' }}</h2>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
{{
|
|
|
|
|
|
trulyEmpty
|
|
|
|
|
|
? 'اولین تماس، جلسه یا وظیفهات رو ثبت کن تا همهچیز رو منظم دنبال کنی.'
|
|
|
|
|
|
: 'فیلتر یا جستجو را عوض کنید، یا یک فعالیت جدید بسازید.'
|
|
|
|
|
|
}}
|
|
|
|
|
|
</p>
|
2026-09-10 01:22:24 +03:30
|
|
|
|
<UiButton block type="button" @click="activityModal = {}">
|
2026-09-02 21:29:00 +03:30
|
|
|
|
{{ trulyEmpty ? 'اولین فعالیتت رو ثبت کن' : 'فعالیت جدید' }}
|
2026-09-10 01:22:24 +03:30
|
|
|
|
</UiButton>
|
2026-09-02 21:29:00 +03:30
|
|
|
|
<button class="btn ghost wide" type="button" @click="ideasOpen = !ideasOpen">
|
|
|
|
|
|
<UiIcon name="lightbulb" :size="18" />
|
|
|
|
|
|
ایدههایی برای شروع
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<div v-if="ideasOpen" class="idea-list">
|
2026-09-10 01:22:24 +03:30
|
|
|
|
<button type="button" @click="activityModal = { type: 'call', title: 'تماس پیگیری' }">
|
2026-09-02 21:29:00 +03:30
|
|
|
|
تماس پیگیری
|
2026-09-10 01:22:24 +03:30
|
|
|
|
</button>
|
|
|
|
|
|
<button type="button" @click="activityModal = { type: 'meeting', title: 'جلسه دمو' }">
|
2026-09-02 21:29:00 +03:30
|
|
|
|
جلسه دمو
|
2026-09-10 01:22:24 +03:30
|
|
|
|
</button>
|
|
|
|
|
|
<button type="button" @click="activityModal = { type: 'task', title: 'وظیفه یادآوری' }">
|
2026-09-02 21:29:00 +03:30
|
|
|
|
وظیفه یادآوری
|
2026-09-10 01:22:24 +03:30
|
|
|
|
</button>
|
2026-09-02 21:29:00 +03:30
|
|
|
|
</div>
|
2026-08-29 21:06:30 +03:30
|
|
|
|
</div>
|
2026-09-02 21:29:00 +03:30
|
|
|
|
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<div class="list-head">
|
|
|
|
|
|
<strong>{{ filterLabel }} — {{ toFaDigits(items.length) }} فعالیت</strong>
|
|
|
|
|
|
<RouterLink class="cal-mini" :to="{ name: 'activities-calendar' }" aria-label="تقویم">
|
|
|
|
|
|
<UiIcon name="calendar" :size="16" />
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="table desktop-only" role="table">
|
|
|
|
|
|
<div class="thead" role="row">
|
|
|
|
|
|
<span>مشتری / عنوان فعالیت</span>
|
|
|
|
|
|
<span>نوع فعالیت</span>
|
|
|
|
|
|
<span>زمان شروع</span>
|
|
|
|
|
|
<span>کارشناس</span>
|
|
|
|
|
|
<span>وضعیت</span>
|
|
|
|
|
|
<span />
|
|
|
|
|
|
</div>
|
2026-09-09 13:12:13 +03:30
|
|
|
|
<article
|
|
|
|
|
|
v-for="item in items"
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
class="trow"
|
|
|
|
|
|
:class="{ 'menu-open': menuId === item.id }"
|
|
|
|
|
|
role="row"
|
|
|
|
|
|
>
|
2026-09-02 21:29:00 +03:30
|
|
|
|
<div class="title-cell">
|
|
|
|
|
|
<UiIcon name="star" :size="16" class="star" />
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<RouterLink class="title" :to="{ name: 'activity-detail', params: { id: item.id } }">
|
|
|
|
|
|
{{ item.title }}
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
<p v-if="subtitle(item)" class="sub">{{ subtitle(item) }}</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="type-cell">
|
|
|
|
|
|
<span class="type-pill" :class="item.type">
|
|
|
|
|
|
<UiIcon :name="typeIcon[item.type]" :size="13" />
|
|
|
|
|
|
{{ typeLabel[item.type] }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="when" :class="{ late: uiStatus(item) === 'overdue' }">
|
|
|
|
|
|
<strong>{{ clockText(item) }}</strong>
|
|
|
|
|
|
<span>{{ dayText(item) }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="who">
|
|
|
|
|
|
<strong>{{ item.assignee?.fullName || '—' }}</strong>
|
|
|
|
|
|
<span v-if="item.company || item.person">
|
|
|
|
|
|
<UiIcon name="building" :size="12" />
|
|
|
|
|
|
{{ item.company?.name || item.person?.fullName }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span class="status" :class="uiStatus(item)">
|
|
|
|
|
|
<UiIcon :name="statusIcon[uiStatus(item)]" :size="13" />
|
|
|
|
|
|
{{ statusLabel[uiStatus(item)] }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="row-more" @click.stop>
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="icon-more"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-label="عملیات"
|
|
|
|
|
|
@click="menuId = menuId === item.id ? null : item.id"
|
|
|
|
|
|
>
|
|
|
|
|
|
<UiIcon name="dots-v" :size="16" />
|
|
|
|
|
|
</button>
|
2026-09-09 13:12:13 +03:30
|
|
|
|
<div
|
|
|
|
|
|
v-if="menuId === item.id"
|
|
|
|
|
|
class="popover"
|
|
|
|
|
|
:class="{ 'drop-up': isLastItem(item.id) }"
|
|
|
|
|
|
>
|
2026-09-02 21:29:00 +03:30
|
|
|
|
<RouterLink :to="{ name: 'activity-detail', params: { id: item.id } }" @click="menuId = null">
|
|
|
|
|
|
مشاهده
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
<RouterLink :to="{ name: 'activity-edit', params: { id: item.id } }" @click="menuId = null">
|
|
|
|
|
|
ویرایش
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
<button v-if="item.status === 'open'" type="button" @click="complete(item.id)">
|
|
|
|
|
|
انجام شد
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="cards mobile-only">
|
|
|
|
|
|
<article v-for="item in items" :key="item.id" class="card">
|
|
|
|
|
|
<div class="card-top">
|
|
|
|
|
|
<div class="card-main">
|
|
|
|
|
|
<time :class="item.type">{{ clockText(item) }}</time>
|
|
|
|
|
|
<RouterLink class="title" :to="{ name: 'activity-detail', params: { id: item.id } }">
|
|
|
|
|
|
{{ item.title }}
|
|
|
|
|
|
<UiIcon name="star" :size="14" class="star" />
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
<p v-if="subtitle(item)" class="sub">{{ subtitle(item) }}</p>
|
|
|
|
|
|
<p class="meta-line">
|
|
|
|
|
|
<span v-if="item.person">
|
|
|
|
|
|
<UiIcon name="user" :size="13" />
|
|
|
|
|
|
{{ item.person.fullName }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span v-if="item.company">
|
|
|
|
|
|
<UiIcon name="building" :size="13" />
|
|
|
|
|
|
{{ item.company.name }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="card-side">
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="icon-more"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-label="عملیات"
|
|
|
|
|
|
@click.stop="menuId = menuId === item.id ? null : item.id"
|
|
|
|
|
|
>
|
|
|
|
|
|
<UiIcon name="dots-v" :size="16" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<span class="type-ico" :class="item.type">
|
|
|
|
|
|
<UiIcon :name="typeIcon[item.type]" :size="18" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="type-pill" :class="item.type">{{ typeLabel[item.type] }}</span>
|
2026-09-09 13:12:13 +03:30
|
|
|
|
<div
|
|
|
|
|
|
v-if="menuId === item.id"
|
|
|
|
|
|
class="popover"
|
|
|
|
|
|
:class="{ 'drop-up': isLastItem(item.id) }"
|
|
|
|
|
|
@click.stop
|
|
|
|
|
|
>
|
2026-09-02 21:29:00 +03:30
|
|
|
|
<RouterLink :to="{ name: 'activity-detail', params: { id: item.id } }" @click="menuId = null">
|
|
|
|
|
|
مشاهده
|
|
|
|
|
|
</RouterLink>
|
|
|
|
|
|
<button v-if="item.status === 'open'" type="button" @click="complete(item.id)">
|
|
|
|
|
|
انجام شد
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<footer class="card-foot">
|
|
|
|
|
|
<span>
|
|
|
|
|
|
<UiIcon name="calendar" :size="14" />
|
|
|
|
|
|
{{ dateText(item) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-if="item.status === 'open'"
|
|
|
|
|
|
class="done-btn"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
@click="complete(item.id)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<UiIcon name="check" :size="14" />
|
|
|
|
|
|
انجام شد
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<span v-else class="status" :class="uiStatus(item)">
|
|
|
|
|
|
{{ statusLabel[uiStatus(item)] }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-09-12 01:33:07 +03:30
|
|
|
|
<UiPagination
|
|
|
|
|
|
v-if="total"
|
|
|
|
|
|
:page="page"
|
|
|
|
|
|
:total-pages="pageCount"
|
|
|
|
|
|
:total-items="total"
|
|
|
|
|
|
@update:page="setPage"
|
|
|
|
|
|
/>
|
2026-09-02 21:29:00 +03:30
|
|
|
|
</template>
|
2026-09-10 01:22:24 +03:30
|
|
|
|
|
|
|
|
|
|
<UiModal
|
|
|
|
|
|
:open="Boolean(activityModal)"
|
|
|
|
|
|
title="فعالیت جدید"
|
|
|
|
|
|
@close="activityModal = null"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ActivityFormView
|
|
|
|
|
|
v-if="activityModal"
|
|
|
|
|
|
embedded
|
|
|
|
|
|
:initial-type="activityModal.type"
|
|
|
|
|
|
:initial-title="activityModal.title"
|
|
|
|
|
|
@saved="activityModal = null; void load(); void loadCounts()"
|
|
|
|
|
|
@cancel="activityModal = null"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</UiModal>
|
2026-08-29 21:06:30 +03:30
|
|
|
|
</section>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.page {
|
|
|
|
|
|
display: grid;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
gap: 1rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.crumbs {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.crumbs a {
|
|
|
|
|
|
color: inherit;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-29 21:06:30 +03:30
|
|
|
|
.page-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.eyebrow {
|
|
|
|
|
|
margin: 0;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
color: #0f766e;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
font-weight: 700;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
font-size: 0.8rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h1 {
|
2026-09-02 21:29:00 +03:30
|
|
|
|
margin: 0.15rem 0 0.3rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
font-size: 1.85rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.lead,
|
|
|
|
|
|
.muted,
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.sub,
|
|
|
|
|
|
.error {
|
2026-08-29 21:06:30 +03:30
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.lead,
|
|
|
|
|
|
.muted,
|
|
|
|
|
|
.sub {
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.error {
|
|
|
|
|
|
color: #b91c1c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-15 17:33:05 +03:30
|
|
|
|
.ok {
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.head-actions .excel-btn .btn-full {
|
|
|
|
|
|
display: inline;
|
|
|
|
|
|
}
|
|
|
|
|
|
.head-actions .excel-btn .btn-short {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.head-actions,
|
|
|
|
|
|
.tools {
|
2026-08-29 21:06:30 +03:30
|
|
|
|
display: flex;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
gap: 0.55rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
align-items: center;
|
2026-09-15 17:33:05 +03:30
|
|
|
|
flex-wrap: wrap;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 0.35rem;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
height: var(--control-h);
|
2026-09-10 23:45:11 +03:30
|
|
|
|
min-height: var(--control-h);
|
2026-09-12 01:33:07 +03:30
|
|
|
|
padding: 0 0.85rem;
|
2026-09-10 23:45:11 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-12 01:33:07 +03:30
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
font-size: var(--fs-sm);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
text-decoration: none;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
transition: background var(--transition-fast), border-color var(--transition-fast);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn.primary {
|
2026-09-12 01:33:07 +03:30
|
|
|
|
background: var(--accent);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn.ghost {
|
|
|
|
|
|
background: #fff;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
color: var(--ink-secondary);
|
|
|
|
|
|
border-color: var(--line);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn.ghost:hover {
|
|
|
|
|
|
background: var(--surface-subtle);
|
|
|
|
|
|
border-color: var(--line-strong);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn.wide {
|
|
|
|
|
|
width: min(100%, 320px);
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tabs {
|
|
|
|
|
|
display: flex;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
gap: 0.2rem;
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
border-bottom: 1px solid #e2e8f0;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tabs button {
|
2026-09-02 21:29:00 +03:30
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.4rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
border: 0;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
background: none;
|
|
|
|
|
|
padding: 0.7rem 0.85rem;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-weight: 700;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
white-space: nowrap;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
box-shadow: inset 0 -2px 0 transparent;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.tabs button em {
|
|
|
|
|
|
font-style: normal;
|
|
|
|
|
|
font-size: 0.72rem;
|
|
|
|
|
|
background: #f1f5f9;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
padding: 0.05rem 0.4rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tabs button.on {
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
box-shadow: inset 0 -2px 0 #14b8a6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tabs button.on em {
|
|
|
|
|
|
background: #ccfbf1;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search {
|
2026-09-12 01:33:07 +03:30
|
|
|
|
flex: 1 1 200px;
|
|
|
|
|
|
min-width: 180px;
|
|
|
|
|
|
max-width: 400px;
|
|
|
|
|
|
height: var(--control-h);
|
|
|
|
|
|
min-height: var(--control-h);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
|
background: #fff;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
padding: 0 0.75rem;
|
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search:focus-within {
|
|
|
|
|
|
border-color: var(--accent);
|
|
|
|
|
|
box-shadow: 0 0 0 2px var(--accent-soft);
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search input {
|
|
|
|
|
|
flex: 1;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
min-width: 0;
|
|
|
|
|
|
border: 0;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
outline: none;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
background: none;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
padding: 0;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
font: inherit;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
font-size: var(--fs-sm);
|
|
|
|
|
|
color: var(--ink);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.filter-wrap,
|
|
|
|
|
|
.row-more,
|
|
|
|
|
|
.card-side {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.filter-btn .chev {
|
|
|
|
|
|
transform: rotate(90deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.popover {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: calc(100% + 6px);
|
|
|
|
|
|
inset-inline-end: 0;
|
|
|
|
|
|
min-width: 11rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
background: #fff;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.14);
|
|
|
|
|
|
padding: 0.35rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
display: grid;
|
2026-09-09 13:12:13 +03:30
|
|
|
|
z-index: 20;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.popover.drop-up {
|
|
|
|
|
|
top: auto;
|
|
|
|
|
|
bottom: calc(100% + 6px);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.popover a,
|
|
|
|
|
|
.popover button {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
text-align: start;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
padding: 0.6rem 0.7rem;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
font: inherit;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.popover a:hover,
|
|
|
|
|
|
.popover button:hover,
|
|
|
|
|
|
.popover button.on {
|
|
|
|
|
|
background: #f0fdfa;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-card {
|
|
|
|
|
|
background: #fff;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.05);
|
|
|
|
|
|
padding: 2.4rem 1.2rem 2rem;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
justify-items: center;
|
|
|
|
|
|
text-align: center;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
gap: 0.7rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.art svg {
|
|
|
|
|
|
width: 148px;
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
display: block;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.empty-card h2 {
|
|
|
|
|
|
margin: 0.2rem 0 0;
|
|
|
|
|
|
font-size: 1.15rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.empty-card > p {
|
|
|
|
|
|
max-width: 22rem;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
line-height: 1.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.idea-list {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
width: min(100%, 320px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-10 01:22:24 +03:30
|
|
|
|
.idea-list a,
|
|
|
|
|
|
.idea-list button {
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
text-align: right;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
cursor: pointer;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
background: #f8fafc;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
padding: 0.65rem 0.8rem;
|
|
|
|
|
|
color: #0f766e;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.list-head {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
color: #0f766e;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.cal-mini {
|
|
|
|
|
|
color: #0f766e;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.table {
|
|
|
|
|
|
background: #fff;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-09 13:12:13 +03:30
|
|
|
|
overflow: visible;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.thead,
|
|
|
|
|
|
.trow {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(0, 2.2fr) 7.6rem 7.2rem 8.4rem 8.6rem 2.4rem;
|
|
|
|
|
|
gap: 0.8rem;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 0.95rem 1.1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.thead {
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.78rem;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
font-weight: 600;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
border-bottom: 1px solid #f1f5f9;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius) var(--radius) 0 0;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.trow {
|
2026-09-09 13:12:13 +03:30
|
|
|
|
position: relative;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
border-bottom: 1px solid #f8fafc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-09 13:12:13 +03:30
|
|
|
|
.trow:last-child {
|
|
|
|
|
|
border-bottom: 0;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: 0 0 var(--radius) var(--radius);
|
2026-09-09 13:12:13 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.trow.menu-open {
|
|
|
|
|
|
z-index: 12;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.title-cell {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0.55rem;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.star {
|
|
|
|
|
|
color: #cbd5e1;
|
|
|
|
|
|
margin-top: 0.2rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.title {
|
2026-08-29 21:06:30 +03:30
|
|
|
|
color: inherit;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
font-weight: 600;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
overflow-wrap: anywhere;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.sub {
|
|
|
|
|
|
margin: 0.2rem 0 0;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-cell {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-pill {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.28rem;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
padding: 0.12rem 0.5rem;
|
|
|
|
|
|
font-size: 0.72rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-pill.call,
|
|
|
|
|
|
.type-ico.call,
|
|
|
|
|
|
time.call {
|
|
|
|
|
|
background: #ecfdf5;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-pill.meeting,
|
|
|
|
|
|
.type-ico.meeting,
|
|
|
|
|
|
time.meeting {
|
|
|
|
|
|
background: #f5f3ff;
|
|
|
|
|
|
color: #6d28d9;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-pill.task,
|
|
|
|
|
|
.type-ico.task,
|
|
|
|
|
|
time.task {
|
|
|
|
|
|
background: #fff7ed;
|
|
|
|
|
|
color: #c2410c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-pill.note,
|
|
|
|
|
|
.type-ico.note,
|
|
|
|
|
|
time.note {
|
|
|
|
|
|
background: #eff6ff;
|
|
|
|
|
|
color: #2563eb;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.when,
|
|
|
|
|
|
.who {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.15rem;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.when strong,
|
|
|
|
|
|
.who strong {
|
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.when span,
|
|
|
|
|
|
.who span {
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.25rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.when.late {
|
|
|
|
|
|
color: #dc2626;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.when.late span {
|
|
|
|
|
|
color: #ef4444;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.28rem;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
padding: 0.28rem 0.55rem;
|
|
|
|
|
|
font-size: 0.74rem;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status.done {
|
|
|
|
|
|
background: #dcfce7;
|
|
|
|
|
|
color: #166534;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status.progress {
|
|
|
|
|
|
background: #dbeafe;
|
|
|
|
|
|
color: #1d4ed8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status.planned {
|
|
|
|
|
|
background: #ffedd5;
|
|
|
|
|
|
color: #c2410c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status.overdue,
|
|
|
|
|
|
.status.canceled {
|
|
|
|
|
|
background: #fee2e2;
|
|
|
|
|
|
color: #b91c1c;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.icon-more {
|
|
|
|
|
|
width: 32px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
color: #64748b;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cards {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1px solid #eef2f6;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
padding: 0.9rem 0.95rem 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-top {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0.7rem;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-main {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-main time {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
background: none;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
font-weight: 600;
|
2026-09-02 21:29:00 +03:30
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
|
margin-bottom: 0.2rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-main .title {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.meta-line {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 0.7rem;
|
|
|
|
|
|
margin: 0.45rem 0 0;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.meta-line span {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.25rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-side {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
justify-items: center;
|
|
|
|
|
|
gap: 0.35rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.type-ico {
|
|
|
|
|
|
width: 42px;
|
|
|
|
|
|
height: 42px;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
place-items: center;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.card-foot {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-top: 0.75rem;
|
|
|
|
|
|
padding-top: 0.7rem;
|
|
|
|
|
|
border-top: 1px solid #f1f5f9;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-foot span {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.done-btn {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: #ecfdf5;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
padding: 0.38rem 0.7rem;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pager {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 0.8rem;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pages {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0.3rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pages button {
|
|
|
|
|
|
min-width: 32px;
|
|
|
|
|
|
height: 32px;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: #fff;
|
2026-09-12 01:33:07 +03:30
|
|
|
|
border-radius: var(--radius);
|
2026-09-02 21:29:00 +03:30
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pages button.on {
|
|
|
|
|
|
background: #14b8a6;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pages button:disabled {
|
|
|
|
|
|
opacity: 0.4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mobile-only {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 899px) {
|
|
|
|
|
|
.crumbs,
|
|
|
|
|
|
.desktop-only,
|
|
|
|
|
|
.tabs button em,
|
|
|
|
|
|
.pager .per {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.mobile-only {
|
|
|
|
|
|
display: grid;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-head {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.head-actions {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.head-actions .btn {
|
2026-09-02 21:29:00 +03:30
|
|
|
|
flex: 1;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-15 17:33:05 +03:30
|
|
|
|
.head-actions .excel-btn .btn-full {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.head-actions .excel-btn .btn-short {
|
|
|
|
|
|
display: inline;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
h1 {
|
|
|
|
|
|
font-size: 1.55rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tabs {
|
2026-09-02 21:29:00 +03:30
|
|
|
|
border-bottom: 0;
|
|
|
|
|
|
gap: 0.4rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tabs button {
|
2026-09-02 21:29:00 +03:30
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
padding: 0.48rem 0.8rem;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.tabs button.on {
|
|
|
|
|
|
background: #ccfbf1;
|
|
|
|
|
|
box-shadow: none;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.tools {
|
2026-08-29 21:06:30 +03:30
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-02 21:29:00 +03:30
|
|
|
|
.search {
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.list-head {
|
|
|
|
|
|
padding-top: 0.2rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 599px) {
|
|
|
|
|
|
.thead,
|
|
|
|
|
|
.trow {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
2026-08-29 21:06:30 +03:30
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|