2026-10-22 11:53:26 +03:30
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
|
|
|
|
import type {
|
|
|
|
|
|
DataAccessPolicy,
|
|
|
|
|
|
DataAccessSection,
|
2026-11-04 10:02:31 +03:30
|
|
|
|
FinancialAccountDto,
|
2026-10-25 19:22:30 +03:30
|
|
|
|
PersonImportSourceStatDto,
|
2026-10-22 11:53:26 +03:30
|
|
|
|
RolePermissionKey,
|
|
|
|
|
|
SectionDataAccess,
|
2026-11-04 10:02:31 +03:30
|
|
|
|
UserTreasuryAccess,
|
2026-10-22 11:53:26 +03:30
|
|
|
|
} from '@mento/shared';
|
|
|
|
|
|
import {
|
|
|
|
|
|
DATA_ACCESS_SECTION_LABELS,
|
|
|
|
|
|
DATA_ACCESS_SECTIONS,
|
2026-11-04 10:02:31 +03:30
|
|
|
|
ROLE_PERMISSION_ITEMS,
|
2026-10-22 11:53:26 +03:30
|
|
|
|
emptySectionDataAccess,
|
|
|
|
|
|
} from '@mento/shared';
|
|
|
|
|
|
import { api } from '@/lib/api';
|
|
|
|
|
|
import DataAccessEditor, { type IdOption } from '@/components/DataAccessEditor.vue';
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{
|
|
|
|
|
|
modelValue: DataAccessPolicy;
|
|
|
|
|
|
/** فقط بخشهایی که این مجوزها را دارند نشان بده؛ خالی = همه */
|
|
|
|
|
|
enabledPermissions?: RolePermissionKey[];
|
|
|
|
|
|
disabled?: boolean;
|
2026-11-04 10:02:31 +03:30
|
|
|
|
/** تبهای عمق داده قفل شوند؛ تب مدیریت مالی جدا میماند */
|
|
|
|
|
|
dataLocked?: boolean;
|
|
|
|
|
|
showTreasury?: boolean;
|
|
|
|
|
|
treasuryAccess?: UserTreasuryAccess | null;
|
2026-10-22 11:53:26 +03:30
|
|
|
|
compact?: boolean;
|
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
'update:modelValue': [DataAccessPolicy];
|
2026-11-04 10:02:31 +03:30
|
|
|
|
'update:treasuryAccess': [UserTreasuryAccess | null];
|
2026-10-22 11:53:26 +03:30
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
|
|
/** بخشهایی که به RolePermissionKey وصلاند؛ بقیه (روتین/پروژه/تیکت) همیشه دیده میشوند */
|
|
|
|
|
|
const SECTION_PERM: Partial<Record<DataAccessSection, RolePermissionKey>> = {
|
|
|
|
|
|
customers: 'customers',
|
|
|
|
|
|
leads: 'leads',
|
|
|
|
|
|
sales: 'sales',
|
|
|
|
|
|
activities: 'activities',
|
|
|
|
|
|
notes: 'notes',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const visibleSections = computed(() => {
|
|
|
|
|
|
const perms = props.enabledPermissions;
|
|
|
|
|
|
if (!perms?.length) return DATA_ACCESS_SECTIONS;
|
|
|
|
|
|
return DATA_ACCESS_SECTIONS.filter((s) => {
|
|
|
|
|
|
const key = SECTION_PERM[s];
|
|
|
|
|
|
if (!key) return true;
|
|
|
|
|
|
return perms.includes(key);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-11-04 10:02:31 +03:30
|
|
|
|
const treasurySections = ROLE_PERMISSION_ITEMS.filter((item) => item.key.startsWith('treasury_'));
|
|
|
|
|
|
const active = ref<DataAccessSection | 'treasury'>('customers');
|
2026-10-22 11:53:26 +03:30
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
visibleSections,
|
|
|
|
|
|
(list) => {
|
2026-11-04 10:02:31 +03:30
|
|
|
|
if (active.value !== 'treasury' && !list.includes(active.value) && list[0]) active.value = list[0];
|
2026-10-22 11:53:26 +03:30
|
|
|
|
},
|
|
|
|
|
|
{ immediate: true },
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-10-22 13:13:59 +03:30
|
|
|
|
/** بدون parse سختگیرانه تا فیلتر در حال ویرایش پاک نشود */
|
2026-10-22 11:53:26 +03:30
|
|
|
|
function sectionValue(key: DataAccessSection): SectionDataAccess {
|
2026-10-22 12:18:05 +03:30
|
|
|
|
const raw = props.modelValue?.[key];
|
|
|
|
|
|
if (raw && typeof raw === 'object') {
|
|
|
|
|
|
return {
|
|
|
|
|
|
mode: raw.mode === 'rules' ? 'rules' : 'all',
|
|
|
|
|
|
rules: Array.isArray(raw.rules) ? raw.rules.map((r) => ({ ...r })) : [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return emptySectionDataAccess();
|
2026-10-22 11:53:26 +03:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function setSection(key: DataAccessSection, next: SectionDataAccess) {
|
|
|
|
|
|
emit('update:modelValue', {
|
2026-10-22 12:18:05 +03:30
|
|
|
|
...props.modelValue,
|
2026-10-22 11:53:26 +03:30
|
|
|
|
[key]: next,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-25 19:22:30 +03:30
|
|
|
|
const importSources = ref<Array<{ value: string; label: string }>>([]);
|
|
|
|
|
|
const importSourcesReady = ref(false);
|
|
|
|
|
|
const importSourcesFailed = ref(false);
|
2026-10-22 11:53:26 +03:30
|
|
|
|
const leadStages = ref<IdOption[]>([]);
|
|
|
|
|
|
const pipelines = ref<IdOption[]>([]);
|
|
|
|
|
|
const dealStages = ref<IdOption[]>([]);
|
|
|
|
|
|
const activityBoards = ref<IdOption[]>([]);
|
|
|
|
|
|
const activityStages = ref<IdOption[]>([]);
|
|
|
|
|
|
const activityTypes = ref<IdOption[]>([]);
|
|
|
|
|
|
const projectBoards = ref<IdOption[]>([]);
|
|
|
|
|
|
const projectStages = ref<IdOption[]>([]);
|
|
|
|
|
|
const routineCategories = ref<IdOption[]>([]);
|
2026-11-04 10:02:31 +03:30
|
|
|
|
const treasuryAccounts = ref<FinancialAccountDto[]>([]);
|
|
|
|
|
|
const treasuryAccountQuery = ref('');
|
|
|
|
|
|
|
|
|
|
|
|
const filteredTreasuryAccounts = computed(() => {
|
|
|
|
|
|
const query = treasuryAccountQuery.value.trim();
|
|
|
|
|
|
if (!query) return treasuryAccounts.value;
|
|
|
|
|
|
return treasuryAccounts.value.filter((account) => account.name.includes(query));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function emptyTreasuryAccess(): UserTreasuryAccess {
|
|
|
|
|
|
return { enabled: true, sections: [], ownAccounts: false, accountIds: [] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function patchTreasury(next: UserTreasuryAccess | null) {
|
|
|
|
|
|
emit('update:treasuryAccess', next);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleTreasurySection(key: RolePermissionKey) {
|
|
|
|
|
|
const current = props.treasuryAccess ?? emptyTreasuryAccess();
|
|
|
|
|
|
const sections = current.sections.includes(key)
|
|
|
|
|
|
? current.sections.filter((item) => item !== key)
|
|
|
|
|
|
: [...current.sections, key];
|
|
|
|
|
|
patchTreasury({ ...current, enabled: true, sections });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleTreasuryAccount(id: string) {
|
|
|
|
|
|
const current = props.treasuryAccess ?? emptyTreasuryAccess();
|
|
|
|
|
|
const accountIds = current.accountIds.includes(id)
|
|
|
|
|
|
? current.accountIds.filter((item) => item !== id)
|
|
|
|
|
|
: [...current.accountIds, id];
|
|
|
|
|
|
patchTreasury({ ...current, enabled: true, accountIds });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadTreasuryAccounts() {
|
|
|
|
|
|
if (!props.showTreasury) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.get<FinancialAccountDto[]>('/treasury/accounts');
|
|
|
|
|
|
treasuryAccounts.value = data;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
treasuryAccounts.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-10-22 11:53:26 +03:30
|
|
|
|
|
|
|
|
|
|
async function loadOptions() {
|
2026-10-25 19:22:30 +03:30
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.get<PersonImportSourceStatDto[]>('/people/source-stats');
|
|
|
|
|
|
importSources.value = data.map((row) => ({ value: row.source, label: row.label }));
|
|
|
|
|
|
importSourcesReady.value = true;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
importSourcesFailed.value = true;
|
|
|
|
|
|
}
|
2026-10-22 11:53:26 +03:30
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.get<Array<{ id: string; name: string }>>('/leads/stages');
|
|
|
|
|
|
leadStages.value = data.map((s) => ({ id: s.id, name: s.name }));
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
leadStages.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.get<
|
|
|
|
|
|
Array<{
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
stages?: Array<{ id: string; name: string }>;
|
|
|
|
|
|
}>
|
|
|
|
|
|
>('/pipelines');
|
|
|
|
|
|
pipelines.value = data.map((p) => ({ id: p.id, name: p.name }));
|
|
|
|
|
|
dealStages.value = data.flatMap((p) =>
|
|
|
|
|
|
(p.stages ?? []).map((s) => ({ id: s.id, name: `${p.name} · ${s.name}` })),
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
pipelines.value = [];
|
|
|
|
|
|
dealStages.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.get<Array<{ id: string; name: string }>>('/activities/boards');
|
|
|
|
|
|
activityBoards.value = data.map((b) => ({ id: b.id, name: b.name }));
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
activityBoards.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const stageLists = await Promise.all(
|
|
|
|
|
|
activityBoards.value.map(async (b) => {
|
|
|
|
|
|
const { data } = await api.get<Array<{ id: string; name: string }>>(
|
|
|
|
|
|
'/activities/stages',
|
|
|
|
|
|
{ params: { boardId: b.id } },
|
|
|
|
|
|
);
|
|
|
|
|
|
return data.map((s) => ({
|
|
|
|
|
|
id: s.id,
|
|
|
|
|
|
name: `${b.name} · ${s.name}`,
|
|
|
|
|
|
}));
|
|
|
|
|
|
}),
|
|
|
|
|
|
);
|
|
|
|
|
|
activityStages.value = stageLists.flat();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
activityStages.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.get<Array<{ id: string; name: string }>>('/activities/types');
|
|
|
|
|
|
activityTypes.value = data.map((t) => ({ id: t.id, name: t.name }));
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
activityTypes.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.get<
|
|
|
|
|
|
Array<{
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
stages?: Array<{ id: string; name: string }>;
|
|
|
|
|
|
}>
|
|
|
|
|
|
>('/project-boards');
|
|
|
|
|
|
projectBoards.value = data.map((b) => ({ id: b.id, name: b.name }));
|
|
|
|
|
|
projectStages.value = data.flatMap((b) =>
|
|
|
|
|
|
(b.stages ?? []).map((s) => ({ id: s.id, name: `${b.name} · ${s.name}` })),
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
projectBoards.value = [];
|
|
|
|
|
|
projectStages.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { data } = await api.get<Array<{ id: string; name: string }>>('/routines/categories');
|
|
|
|
|
|
routineCategories.value = data.map((c) => ({ id: c.id, name: c.name }));
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
routineCategories.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
void loadOptions();
|
2026-11-04 10:02:31 +03:30
|
|
|
|
void loadTreasuryAccounts();
|
2026-10-22 11:53:26 +03:30
|
|
|
|
});
|
|
|
|
|
|
|
2026-11-04 10:02:31 +03:30
|
|
|
|
const editorSection = computed<DataAccessSection>(() =>
|
|
|
|
|
|
active.value === 'treasury' ? 'customers' : active.value,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-10-22 11:53:26 +03:30
|
|
|
|
const stagesForActive = computed(() => {
|
|
|
|
|
|
if (active.value === 'leads') return leadStages.value;
|
|
|
|
|
|
if (active.value === 'sales') return dealStages.value;
|
|
|
|
|
|
if (active.value === 'activities') return activityStages.value;
|
|
|
|
|
|
if (active.value === 'projects') return projectStages.value;
|
|
|
|
|
|
return [];
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const boardsForActive = computed(() => {
|
|
|
|
|
|
if (active.value === 'activities') return activityBoards.value;
|
|
|
|
|
|
if (active.value === 'projects') return projectBoards.value;
|
|
|
|
|
|
return [];
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const typesForActive = computed(() => {
|
|
|
|
|
|
if (active.value === 'activities') return activityTypes.value;
|
|
|
|
|
|
if (active.value === 'routines') return routineCategories.value;
|
|
|
|
|
|
return [];
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="dap">
|
|
|
|
|
|
<p v-if="!visibleSections.length" class="muted">
|
|
|
|
|
|
برای تنظیم محدوده، ابتدا مجوز بخش مربوط را روشن کنید.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<div class="tabs" role="tablist">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="s in visibleSections"
|
|
|
|
|
|
:key="s"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="tab"
|
|
|
|
|
|
:class="{ on: active === s }"
|
|
|
|
|
|
role="tab"
|
|
|
|
|
|
:aria-selected="active === s"
|
|
|
|
|
|
@click="active = s"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ DATA_ACCESS_SECTION_LABELS[s] }}
|
|
|
|
|
|
</button>
|
2026-11-04 10:02:31 +03:30
|
|
|
|
<button
|
|
|
|
|
|
v-if="showTreasury"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="tab"
|
|
|
|
|
|
:class="{ on: active === 'treasury' }"
|
|
|
|
|
|
role="tab"
|
|
|
|
|
|
:aria-selected="active === 'treasury'"
|
|
|
|
|
|
@click="active = 'treasury'"
|
|
|
|
|
|
>
|
|
|
|
|
|
مدیریت مالی
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="active === 'treasury'" class="treasury">
|
|
|
|
|
|
<p class="muted">
|
|
|
|
|
|
این برگه فقط برای همین کاربر است. «مثل نقش» یعنی همان دسترسی مالی نقش بماند.
|
|
|
|
|
|
در حالت سفارشی، اگر هیچ بخش ریزی تیک نخورد همه بخشها بهجز حقوق باز میمانند و حقوق جداست.
|
|
|
|
|
|
اگر هیچ حسابی انتخاب نشود همه حسابها باز است.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<label class="choice">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="radio"
|
|
|
|
|
|
name="user-treasury"
|
|
|
|
|
|
:checked="treasuryAccess == null"
|
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
|
@change="patchTreasury(null)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
مثل نقش
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="choice">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="radio"
|
|
|
|
|
|
name="user-treasury"
|
|
|
|
|
|
:checked="treasuryAccess != null"
|
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
|
@change="patchTreasury(treasuryAccess ?? emptyTreasuryAccess())"
|
|
|
|
|
|
/>
|
|
|
|
|
|
سفارشی برای این کاربر
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<template v-if="treasuryAccess">
|
|
|
|
|
|
<label class="choice">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="treasuryAccess.enabled"
|
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
|
@change="patchTreasury({ ...treasuryAccess, enabled: !treasuryAccess.enabled })"
|
|
|
|
|
|
/>
|
|
|
|
|
|
دسترسی به مدیریت مالی
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<template v-if="treasuryAccess.enabled">
|
|
|
|
|
|
<label v-for="perm in treasurySections" :key="perm.key" class="choice sub">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="treasuryAccess.sections.includes(perm.key)"
|
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
|
@change="toggleTreasurySection(perm.key)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ perm.label }}
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label class="choice">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="treasuryAccess.ownAccounts"
|
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
|
@change="patchTreasury({ ...treasuryAccess, ownAccounts: !treasuryAccess.ownAccounts })"
|
|
|
|
|
|
/>
|
|
|
|
|
|
حسابهایی که خودش مسئول آنهاست
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<p class="muted">حسابهای دیگر</p>
|
|
|
|
|
|
<input v-model="treasuryAccountQuery" placeholder="جستجوی حساب" :disabled="disabled" />
|
|
|
|
|
|
<label v-for="account in filteredTreasuryAccounts" :key="account.id" class="choice">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="treasuryAccess.accountIds.includes(account.id)"
|
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
|
@change="toggleTreasuryAccount(account.id)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{{ account.name }}
|
|
|
|
|
|
<template v-if="account.responsibleName"> — مسئول: {{ account.responsibleName }}</template>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<p v-if="!treasuryAccounts.length" class="muted">حسابی برای انتخاب نیست.</p>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
2026-10-22 11:53:26 +03:30
|
|
|
|
</div>
|
|
|
|
|
|
<DataAccessEditor
|
2026-11-04 10:02:31 +03:30
|
|
|
|
v-else
|
2026-10-22 11:53:26 +03:30
|
|
|
|
:key="active"
|
2026-11-04 10:02:31 +03:30
|
|
|
|
:section="editorSection"
|
|
|
|
|
|
:model-value="sectionValue(editorSection)"
|
2026-10-22 11:53:26 +03:30
|
|
|
|
:stages="stagesForActive"
|
|
|
|
|
|
:pipelines="pipelines"
|
|
|
|
|
|
:boards="boardsForActive"
|
|
|
|
|
|
:activity-types="typesForActive"
|
2026-10-25 19:22:30 +03:30
|
|
|
|
:import-sources="importSources"
|
|
|
|
|
|
:import-sources-ready="importSourcesReady"
|
|
|
|
|
|
:import-sources-failed="importSourcesFailed"
|
2026-11-04 10:02:31 +03:30
|
|
|
|
:disabled="disabled || dataLocked"
|
2026-10-22 11:53:26 +03:30
|
|
|
|
:compact="compact"
|
2026-11-04 10:02:31 +03:30
|
|
|
|
@update:model-value="setSection(editorSection, $event)"
|
2026-10-22 11:53:26 +03:30
|
|
|
|
/>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.dap {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tabs {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 0.35rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tab {
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
|
padding: 0.3rem 0.7rem;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
color: #475569;
|
|
|
|
|
|
}
|
|
|
|
|
|
.tab.on {
|
|
|
|
|
|
border-color: #0f766e;
|
|
|
|
|
|
background: #f0fdfa;
|
|
|
|
|
|
color: #0f766e;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
.muted {
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
|
}
|
2026-11-04 10:02:31 +03:30
|
|
|
|
.treasury {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 0.45rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
.choice {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: auto 1fr;
|
|
|
|
|
|
gap: 0.4rem;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.choice.sub {
|
|
|
|
|
|
padding-inline-start: 1.2rem;
|
|
|
|
|
|
}
|
2026-10-22 11:53:26 +03:30
|
|
|
|
</style>
|