Refining the speech-to-text conversion structure
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
} from '@/lib/jalali';
|
||||
import {
|
||||
UiButton,
|
||||
UiDropdownMenu,
|
||||
UiModal,
|
||||
UiPageHeader,
|
||||
UiPagination,
|
||||
@@ -129,11 +130,6 @@ const filterLabel = computed(() => TABS.find((t) => t.key === filter.value)?.lab
|
||||
const trulyEmpty = computed(() => !loading.value && counts.value.all === 0 && !q.value.trim());
|
||||
const listEmpty = computed(() => !loading.value && !items.value.length);
|
||||
|
||||
function isLastItem(id: string) {
|
||||
const rows = items.value;
|
||||
return rows.length > 0 && rows[rows.length - 1]?.id === id;
|
||||
}
|
||||
|
||||
function isOverdue(item: ActivityDto) {
|
||||
if (item.status !== 'open' || !item.dueAt) return false;
|
||||
return startOfLocalDay(new Date(item.dueAt)).getTime() < startOfLocalDay().getTime();
|
||||
@@ -480,7 +476,6 @@ onMounted(() => {
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
class="trow"
|
||||
:class="{ 'menu-open': menuId === item.id }"
|
||||
role="row"
|
||||
>
|
||||
<div class="title-cell">
|
||||
@@ -516,29 +511,31 @@ onMounted(() => {
|
||||
</span>
|
||||
</div>
|
||||
<div class="row-more" @click.stop>
|
||||
<button
|
||||
class="icon-more"
|
||||
type="button"
|
||||
aria-label="عملیات"
|
||||
@click="menuId = menuId === item.id ? null : item.id"
|
||||
<UiDropdownMenu
|
||||
:open="menuId === item.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? item.id : null)"
|
||||
>
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
<div
|
||||
v-if="menuId === item.id"
|
||||
class="popover"
|
||||
:class="{ 'drop-up': isLastItem(item.id) }"
|
||||
>
|
||||
<RouterLink :to="{ name: 'activity-detail', params: { id: item.id } }" @click="menuId = null">
|
||||
<template #trigger="{ toggle }">
|
||||
<button
|
||||
class="icon-more"
|
||||
type="button"
|
||||
aria-label="عملیات"
|
||||
@click.stop="toggle"
|
||||
>
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
</template>
|
||||
<RouterLink :to="{ name: 'activity-detail', params: { id: item.id } }">
|
||||
مشاهده
|
||||
</RouterLink>
|
||||
<RouterLink :to="{ name: 'activity-edit', params: { id: item.id } }" @click="menuId = null">
|
||||
<RouterLink :to="{ name: 'activity-edit', params: { id: item.id } }">
|
||||
ویرایش
|
||||
</RouterLink>
|
||||
<button v-if="item.status === 'open'" type="button" @click="complete(item.id)">
|
||||
انجام شد
|
||||
</button>
|
||||
</div>
|
||||
</UiDropdownMenu>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
@@ -565,31 +562,32 @@ onMounted(() => {
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-side">
|
||||
<button
|
||||
class="icon-more"
|
||||
type="button"
|
||||
aria-label="عملیات"
|
||||
@click.stop="menuId = menuId === item.id ? null : item.id"
|
||||
<UiDropdownMenu
|
||||
:open="menuId === item.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? item.id : null)"
|
||||
>
|
||||
<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>
|
||||
<div
|
||||
v-if="menuId === item.id"
|
||||
class="popover"
|
||||
:class="{ 'drop-up': isLastItem(item.id) }"
|
||||
@click.stop
|
||||
>
|
||||
<RouterLink :to="{ name: 'activity-detail', params: { id: item.id } }" @click="menuId = null">
|
||||
<template #trigger="{ toggle }">
|
||||
<button
|
||||
class="icon-more"
|
||||
type="button"
|
||||
aria-label="عملیات"
|
||||
@click.stop="toggle"
|
||||
>
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
</template>
|
||||
<RouterLink :to="{ name: 'activity-detail', params: { id: item.id } }">
|
||||
مشاهده
|
||||
</RouterLink>
|
||||
<button v-if="item.status === 'open'" type="button" @click="complete(item.id)">
|
||||
انجام شد
|
||||
</button>
|
||||
</div>
|
||||
</UiDropdownMenu>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-foot">
|
||||
@@ -849,11 +847,6 @@ h1 {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.popover.drop-up {
|
||||
top: auto;
|
||||
bottom: calc(100% + 6px);
|
||||
}
|
||||
|
||||
.popover a,
|
||||
.popover button {
|
||||
display: block;
|
||||
@@ -968,10 +961,6 @@ h1 {
|
||||
border-radius: 0 0 var(--radius) var(--radius);
|
||||
}
|
||||
|
||||
.trow.menu-open {
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
.title-cell {
|
||||
display: flex;
|
||||
gap: 0.55rem;
|
||||
|
||||
@@ -9,6 +9,7 @@ import ExcelImportFlow from '@/components/ExcelImportFlow.vue';
|
||||
import PhoneContactsImport from '@/components/PhoneContactsImport.vue';
|
||||
import {
|
||||
UiButton,
|
||||
UiDropdownMenu,
|
||||
UiModal,
|
||||
UiPageHeader,
|
||||
UiPagination,
|
||||
@@ -372,9 +373,27 @@ onMounted(load);
|
||||
<RouterLink class="name" :to="{ name: 'person-detail', params: { id: p.id } }">
|
||||
{{ p.fullName }}
|
||||
</RouterLink>
|
||||
<button class="more" type="button" @click.stop="menuId = menuId === p.id ? null : p.id">
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
<UiDropdownMenu
|
||||
:open="menuId === p.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? p.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button class="more" type="button" @click.stop="toggle">
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
</template>
|
||||
<RouterLink :to="{ name: 'person-detail', params: { id: p.id } }">پرونده</RouterLink>
|
||||
<RouterLink :to="{ name: 'person-edit', params: { id: p.id } }">ویرایش</RouterLink>
|
||||
<button
|
||||
v-for="mood in PERSON_MOODS"
|
||||
:key="mood"
|
||||
type="button"
|
||||
@click.stop="setMoodFor(p, mood)"
|
||||
>
|
||||
{{ moodLabel(mood) }}
|
||||
</button>
|
||||
</UiDropdownMenu>
|
||||
</div>
|
||||
<p class="role">
|
||||
{{ [p.position, p.company?.name].filter(Boolean).join(' • ') || 'بدون سمت' }}
|
||||
@@ -389,17 +408,6 @@ onMounted(load);
|
||||
</p>
|
||||
<span class="badge" :class="moodOf(p)">{{ moodLabel(moodOf(p)) }}</span>
|
||||
</div>
|
||||
<ul v-if="menuId === p.id" class="menu row-menu">
|
||||
<li>
|
||||
<RouterLink :to="{ name: 'person-detail', params: { id: p.id } }">پرونده</RouterLink>
|
||||
</li>
|
||||
<li>
|
||||
<RouterLink :to="{ name: 'person-edit', params: { id: p.id } }">ویرایش</RouterLink>
|
||||
</li>
|
||||
<li v-for="mood in PERSON_MOODS" :key="mood">
|
||||
<button type="button" @click.stop="setMoodFor(p, mood)">{{ moodLabel(mood) }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
@@ -449,21 +457,28 @@ onMounted(load);
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ formatDate(p.createdAt) }}</td>
|
||||
<td class="ops">
|
||||
<button type="button" class="more" @click.stop="menuId = menuId === p.id ? null : p.id">
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
<ul v-if="menuId === p.id" class="menu row-menu">
|
||||
<li>
|
||||
<RouterLink :to="{ name: 'person-detail', params: { id: p.id } }">پرونده</RouterLink>
|
||||
</li>
|
||||
<li>
|
||||
<RouterLink :to="{ name: 'person-edit', params: { id: p.id } }">ویرایش</RouterLink>
|
||||
</li>
|
||||
<li v-for="mood in PERSON_MOODS" :key="mood">
|
||||
<button type="button" @click.stop="setMoodFor(p, mood)">{{ moodLabel(mood) }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
<td class="ops" @click.stop>
|
||||
<UiDropdownMenu
|
||||
:open="menuId === p.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? p.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button type="button" class="more" @click.stop="toggle">
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
</template>
|
||||
<RouterLink :to="{ name: 'person-detail', params: { id: p.id } }">پرونده</RouterLink>
|
||||
<RouterLink :to="{ name: 'person-edit', params: { id: p.id } }">ویرایش</RouterLink>
|
||||
<button
|
||||
v-for="mood in PERSON_MOODS"
|
||||
:key="mood"
|
||||
type="button"
|
||||
@click.stop="setMoodFor(p, mood)"
|
||||
>
|
||||
{{ moodLabel(mood) }}
|
||||
</button>
|
||||
</UiDropdownMenu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -1005,12 +1020,6 @@ strong.newcomer {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.row-menu {
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: 0;
|
||||
top: 2.4rem;
|
||||
}
|
||||
|
||||
.empty {
|
||||
margin: 0;
|
||||
padding: 1.4rem;
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||
import {
|
||||
UiButton,
|
||||
UiDropdownMenu,
|
||||
UiModal,
|
||||
UiPageHeader,
|
||||
UiPagination,
|
||||
@@ -522,12 +523,18 @@ onMounted(load);
|
||||
<RouterLink class="icon-btn sm" :to="r.href" title="ویرایش">
|
||||
<UiIcon name="pencil" :size="15" />
|
||||
</RouterLink>
|
||||
<button type="button" class="icon-btn sm" @click="menuId = menuId === r.id ? null : r.id">
|
||||
<UiIcon name="dots-v" :size="15" />
|
||||
</button>
|
||||
<div v-if="menuId === r.id" class="menu left">
|
||||
<UiDropdownMenu
|
||||
:open="menuId === r.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? r.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button type="button" class="icon-btn sm" @click.stop="toggle">
|
||||
<UiIcon name="dots-v" :size="15" />
|
||||
</button>
|
||||
</template>
|
||||
<RouterLink :to="r.href">باز کردن</RouterLink>
|
||||
</div>
|
||||
</UiDropdownMenu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -548,14 +555,20 @@ onMounted(load);
|
||||
{{ r.statusLabel }}
|
||||
</span>
|
||||
<div class="mob-aside">
|
||||
<button type="button" class="icon-btn sm ghost" @click="menuId = menuId === r.id ? null : r.id">
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
<UiDropdownMenu
|
||||
:open="menuId === r.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? r.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button type="button" class="icon-btn sm ghost" @click.stop="toggle">
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
</template>
|
||||
<RouterLink :to="r.href">باز کردن</RouterLink>
|
||||
</UiDropdownMenu>
|
||||
<span class="avatar">{{ r.customerName.slice(0, 1) }}</span>
|
||||
</div>
|
||||
<div v-if="menuId === r.id" class="menu left mob-menu">
|
||||
<RouterLink :to="r.href">باز کردن</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mob-card-bottom">
|
||||
<em :class="{ neg: r.amount < 0 }">{{ money(r.amount, r.currency) }}</em>
|
||||
@@ -835,7 +848,6 @@ tbody tr:nth-child(even) { background: #fcfdff; }
|
||||
.mob-info { text-decoration: none; color: inherit; display: grid; gap: 0.12rem; min-width: 0; }
|
||||
.mob-info .mono, .mob-info .muted { font-size: 0.8rem; color: var(--muted); }
|
||||
.mob-aside { display: grid; justify-items: center; gap: 0.35rem; }
|
||||
.mob-menu { top: 2.2rem; }
|
||||
.mob-card-bottom { display: flex; justify-content: flex-start; }
|
||||
.mob-card-bottom em { font-style: normal; font-weight: 600; font-size: 0.92rem; }
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { api } from '@/lib/api';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { canManageSettings } from '@/lib/roles';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import { UiDropdownMenu } from '@/components/ui';
|
||||
import { formatRelativeContact, toFaDigits } from '@/lib/jalali';
|
||||
import type {
|
||||
CreateLoyaltyRewardRequest,
|
||||
@@ -785,14 +786,20 @@ onBeforeUnmount(() => {
|
||||
<button class="act minus" type="button" title="کسر امتیاز" @click="openAdjust(m, -1)">
|
||||
<UiIcon name="minus" :size="14" />
|
||||
</button>
|
||||
<button class="act more" type="button" @click="menuId = menuId === m.id ? null : m.id">
|
||||
<UiIcon name="dots" :size="14" />
|
||||
</button>
|
||||
<div v-if="menuId === m.id" class="menu">
|
||||
<UiDropdownMenu
|
||||
:open="menuId === m.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? m.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button class="act more" type="button" @click.stop="toggle">
|
||||
<UiIcon name="dots" :size="14" />
|
||||
</button>
|
||||
</template>
|
||||
<RouterLink :to="`/contacts/people/${m.id}`">پروفایل</RouterLink>
|
||||
<button type="button" @click="openLedger(m)">تاریخچه امتیاز</button>
|
||||
<button type="button" @click="openRedeem(undefined, m)">ثبت پاداش</button>
|
||||
</div>
|
||||
</UiDropdownMenu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -815,9 +822,20 @@ onBeforeUnmount(() => {
|
||||
<b class="mob-pts"><span>امتیاز</span><em>{{ faNum(m.loyaltyPoints) }}</em></b>
|
||||
<div class="mob-side">
|
||||
<div class="ops">
|
||||
<button class="act more" type="button" @click.stop="menuId = menuId === m.id ? null : m.id">
|
||||
<UiIcon name="dots" :size="14" />
|
||||
</button>
|
||||
<UiDropdownMenu
|
||||
:open="menuId === m.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? m.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button class="act more" type="button" @click.stop="toggle">
|
||||
<UiIcon name="dots" :size="14" />
|
||||
</button>
|
||||
</template>
|
||||
<RouterLink :to="`/contacts/people/${m.id}`">پروفایل</RouterLink>
|
||||
<button type="button" @click="openLedger(m)">تاریخچه امتیاز</button>
|
||||
<button type="button" @click="openRedeem(undefined, m)">ثبت پاداش</button>
|
||||
</UiDropdownMenu>
|
||||
<button class="act plus" type="button" @click="openAdjust(m, 1)">
|
||||
<UiIcon name="plus" :size="14" />
|
||||
</button>
|
||||
@@ -825,11 +843,6 @@ onBeforeUnmount(() => {
|
||||
<UiIcon name="minus" :size="14" />
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="menuId === m.id" class="menu" @click.stop>
|
||||
<RouterLink :to="`/contacts/people/${m.id}`">پروفایل</RouterLink>
|
||||
<button type="button" @click="openLedger(m)">تاریخچه امتیاز</button>
|
||||
<button type="button" @click="openRedeem(undefined, m)">ثبت پاداش</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1287,17 +1300,6 @@ th { background: #f8fafc; color: #64748b; font-weight: 700; }
|
||||
.act.plus { background: #dcfce7; color: #15803d; }
|
||||
.act.minus { background: #fee2e2; color: #dc2626; }
|
||||
.act.more { background: #f1f5f9; color: #64748b; }
|
||||
.menu {
|
||||
position: absolute; inset-inline-end: 0; top: 34px; z-index: 5;
|
||||
background: #fff; border: 1px solid #e2e8f0; border-radius: var(--radius);
|
||||
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12);
|
||||
display: grid; min-width: 140px; overflow: hidden;
|
||||
}
|
||||
.menu a, .menu button {
|
||||
background: none; border: 0; text-align: right; padding: 0.55rem 0.75rem;
|
||||
font: inherit; cursor: pointer; color: var(--ink); text-decoration: none;
|
||||
}
|
||||
.menu a:hover, .menu button:hover { background: #f8fafc; }
|
||||
|
||||
.mob-list { display: none; list-style: none; margin: 0; padding: 0; gap: 0.65rem; }
|
||||
.mob-list li {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import { UiDropdownMenu } from '@/components/ui';
|
||||
import { formatClockFa, formatJalaliNumeric, toFaDigits } from '@/lib/jalali';
|
||||
import type {
|
||||
CreateMessageAutomationRuleRequest,
|
||||
@@ -414,17 +415,23 @@ onUnmounted(() => {
|
||||
>
|
||||
<header>
|
||||
<div class="m-menu">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="عملیات"
|
||||
@click="menuId = menuId === row.id ? null : row.id"
|
||||
<UiDropdownMenu
|
||||
:open="menuId === row.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? row.id : null)"
|
||||
>
|
||||
<UiIcon name="dots-v" :size="18" />
|
||||
</button>
|
||||
<div v-if="menuId === row.id" class="pop">
|
||||
<template #trigger="{ toggle }">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="عملیات"
|
||||
@click.stop="toggle"
|
||||
>
|
||||
<UiIcon name="dots-v" :size="18" />
|
||||
</button>
|
||||
</template>
|
||||
<button type="button" @click="openMobileEdit(row)">ویرایش</button>
|
||||
<button type="button" class="danger" @click="askRemove(row)">حذف</button>
|
||||
</div>
|
||||
</UiDropdownMenu>
|
||||
</div>
|
||||
<div class="m-copy">
|
||||
<strong>{{ row.name }}</strong>
|
||||
@@ -2144,7 +2151,7 @@ textarea {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.m-menu > button,
|
||||
.m-menu :deep(button),
|
||||
.m-back {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
@@ -2157,32 +2164,6 @@ textarea {
|
||||
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;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { formatMoney } from '@/lib/money';
|
||||
import {
|
||||
UiBadge,
|
||||
UiButton,
|
||||
UiDropdownMenu,
|
||||
UiEmptyState,
|
||||
UiInput,
|
||||
UiModal,
|
||||
@@ -272,22 +273,22 @@ onMounted(load);
|
||||
<UiIcon name="plus" :size="16" />
|
||||
محصول جدید
|
||||
</UiButton>
|
||||
<div class="more-wrap" @click.stop>
|
||||
<UiButton
|
||||
variant="ghost"
|
||||
size="md"
|
||||
aria-label="عملیات بیشتر"
|
||||
@click="moreOpen = !moreOpen"
|
||||
>
|
||||
<UiIcon name="dots" :size="18" />
|
||||
</UiButton>
|
||||
<div v-if="moreOpen" class="menu">
|
||||
<button type="button" :disabled="!selected.size" @click="removeSelected">
|
||||
حذف انتخابشدهها
|
||||
</button>
|
||||
<button type="button" @click="load">بروزرسانی لیست</button>
|
||||
</div>
|
||||
</div>
|
||||
<UiDropdownMenu v-model:open="moreOpen" align="end">
|
||||
<template #trigger="{ toggle }">
|
||||
<UiButton
|
||||
variant="ghost"
|
||||
size="md"
|
||||
aria-label="عملیات بیشتر"
|
||||
@click.stop="toggle"
|
||||
>
|
||||
<UiIcon name="dots" :size="18" />
|
||||
</UiButton>
|
||||
</template>
|
||||
<button type="button" :disabled="!selected.size" @click="removeSelected">
|
||||
حذف انتخابشدهها
|
||||
</button>
|
||||
<button type="button" @click="load">بروزرسانی لیست</button>
|
||||
</UiDropdownMenu>
|
||||
</template>
|
||||
</UiPageHeader>
|
||||
|
||||
@@ -312,23 +313,23 @@ onMounted(load);
|
||||
<option value="active">فعال</option>
|
||||
<option value="inactive">غیرفعال</option>
|
||||
</UiSelect>
|
||||
<div class="sort-wrap">
|
||||
<UiButton variant="outline" @click="sortOpen = !sortOpen">
|
||||
<UiIcon name="filter" :size="16" />
|
||||
مرتبسازی
|
||||
</UiButton>
|
||||
<div v-if="sortOpen" class="menu">
|
||||
<button type="button" :class="{ on: sort === 'name' }" @click="sort = 'name'; sortOpen = false">
|
||||
نام
|
||||
</button>
|
||||
<button type="button" :class="{ on: sort === 'newest' }" @click="sort = 'newest'; sortOpen = false">
|
||||
جدیدترین
|
||||
</button>
|
||||
<button type="button" :class="{ on: sort === 'price' }" @click="sort = 'price'; sortOpen = false">
|
||||
قیمت
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<UiDropdownMenu v-model:open="sortOpen" align="end">
|
||||
<template #trigger="{ toggle }">
|
||||
<UiButton variant="outline" @click.stop="toggle">
|
||||
<UiIcon name="filter" :size="16" />
|
||||
مرتبسازی
|
||||
</UiButton>
|
||||
</template>
|
||||
<button type="button" :class="{ on: sort === 'name' }" @click="sort = 'name'">
|
||||
نام
|
||||
</button>
|
||||
<button type="button" :class="{ on: sort === 'newest' }" @click="sort = 'newest'">
|
||||
جدیدترین
|
||||
</button>
|
||||
<button type="button" :class="{ on: sort === 'price' }" @click="sort = 'price'">
|
||||
قیمت
|
||||
</button>
|
||||
</UiDropdownMenu>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
@@ -398,23 +399,27 @@ onMounted(load);
|
||||
<small>{{ formatTime(p.createdAt) }}</small>
|
||||
</td>
|
||||
<td class="ops" @click.stop>
|
||||
<div class="row-more">
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn"
|
||||
aria-label="عملیات"
|
||||
@click="menuId = menuId === p.id ? null : p.id"
|
||||
>
|
||||
<UiIcon name="dots" :size="16" />
|
||||
</button>
|
||||
<div v-if="menuId === p.id" class="menu">
|
||||
<button type="button" @click="goEdit(p.id)">ویرایش</button>
|
||||
<button type="button" @click="toggleActive(p)">
|
||||
{{ p.isActive ? 'غیرفعال کردن' : 'فعال کردن' }}
|
||||
<UiDropdownMenu
|
||||
:open="menuId === p.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? p.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn"
|
||||
aria-label="عملیات"
|
||||
@click.stop="toggle"
|
||||
>
|
||||
<UiIcon name="dots" :size="16" />
|
||||
</button>
|
||||
<button type="button" class="danger" @click="removeProduct(p)">حذف</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<button type="button" @click="goEdit(p.id)">ویرایش</button>
|
||||
<button type="button" @click="toggleActive(p)">
|
||||
{{ p.isActive ? 'غیرفعال کردن' : 'فعال کردن' }}
|
||||
</button>
|
||||
<button type="button" class="danger" @click="removeProduct(p)">حذف</button>
|
||||
</UiDropdownMenu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -443,23 +448,27 @@ onMounted(load);
|
||||
</span>
|
||||
</div>
|
||||
<div class="mob-side">
|
||||
<div class="row-more">
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn"
|
||||
aria-label="عملیات"
|
||||
@click="menuId = menuId === p.id ? null : p.id"
|
||||
>
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
<div v-if="menuId === p.id" class="menu left">
|
||||
<button type="button" @click="goEdit(p.id)">ویرایش</button>
|
||||
<button type="button" @click="toggleActive(p)">
|
||||
{{ p.isActive ? 'غیرفعال کردن' : 'فعال کردن' }}
|
||||
<UiDropdownMenu
|
||||
:open="menuId === p.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? p.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn"
|
||||
aria-label="عملیات"
|
||||
@click.stop="toggle"
|
||||
>
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
<button type="button" class="danger" @click="removeProduct(p)">حذف</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<button type="button" @click="goEdit(p.id)">ویرایش</button>
|
||||
<button type="button" @click="toggleActive(p)">
|
||||
{{ p.isActive ? 'غیرفعال کردن' : 'فعال کردن' }}
|
||||
</button>
|
||||
<button type="button" class="danger" @click="removeProduct(p)">حذف</button>
|
||||
</UiDropdownMenu>
|
||||
<UiBadge :variant="p.isActive ? 'success' : 'neutral'" size="sm" dot>
|
||||
{{ p.isActive ? 'فعال' : 'غیرفعال' }}
|
||||
</UiBadge>
|
||||
|
||||
@@ -15,6 +15,7 @@ import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||
import MoneyInput from '@/components/MoneyInput.vue';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import {
|
||||
UiDropdownMenu,
|
||||
UiPageHeader,
|
||||
UiPagination,
|
||||
UiChart,
|
||||
@@ -649,13 +650,19 @@ onMounted(() => {
|
||||
<button type="button" class="icon-btn sm" title="ویرایش" @click="openEdit(g)">
|
||||
<UiIcon name="pencil" :size="15" />
|
||||
</button>
|
||||
<button type="button" class="icon-btn sm" @click="menuId = menuId === g.id ? null : g.id">
|
||||
<UiIcon name="dots-v" :size="15" />
|
||||
</button>
|
||||
<div v-if="menuId === g.id" class="menu">
|
||||
<UiDropdownMenu
|
||||
:open="menuId === g.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? g.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button type="button" class="icon-btn sm" @click.stop="toggle">
|
||||
<UiIcon name="dots-v" :size="15" />
|
||||
</button>
|
||||
</template>
|
||||
<button type="button" @click="openEdit(g)">ویرایش</button>
|
||||
<button type="button" class="danger" @click="removeGoal(g.id)">حذف</button>
|
||||
</div>
|
||||
</UiDropdownMenu>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -672,13 +679,19 @@ onMounted(() => {
|
||||
</button>
|
||||
</div>
|
||||
<article v-for="g in filteredGoals" :key="g.id" class="goal-card" @click.stop>
|
||||
<button type="button" class="dots" @click="menuId = menuId === g.id ? null : g.id">
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
<div v-if="menuId === g.id" class="menu left">
|
||||
<UiDropdownMenu
|
||||
:open="menuId === g.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuId = v ? g.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button type="button" class="dots" @click.stop="toggle">
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
</template>
|
||||
<button type="button" @click="openEdit(g)">ویرایش</button>
|
||||
<button type="button" class="danger" @click="removeGoal(g.id)">حذف</button>
|
||||
</div>
|
||||
</UiDropdownMenu>
|
||||
<div class="goal-body">
|
||||
<div>
|
||||
<strong>{{ g.title }}</strong>
|
||||
@@ -872,18 +885,6 @@ th { background: #f8fafc; color: #64748b; font-weight: 700; white-space: nowrap;
|
||||
display: inline-flex; align-items: center; justify-content: center; cursor: pointer; color: #64748b;
|
||||
}
|
||||
.icon-btn.sm { width: 30px; height: 30px; }
|
||||
.menu {
|
||||
position: absolute; top: calc(100% + 4px); left: 0; z-index: 20; min-width: 120px;
|
||||
background: #fff; border: 1px solid #e2e8f0; border-radius: var(--radius); box-shadow: 0 10px 24px rgba(15,23,42,.12);
|
||||
padding: 0.3rem; display: grid;
|
||||
}
|
||||
.menu.left { right: 0; left: auto; }
|
||||
.menu button {
|
||||
border: 0; background: transparent; text-align: right; padding: 0.5rem 0.65rem; border-radius: var(--radius);
|
||||
cursor: pointer; font: inherit;
|
||||
}
|
||||
.menu button:hover { background: var(--teal-soft); color: var(--teal); }
|
||||
.menu button.danger { color: #b91c1c; }
|
||||
|
||||
.pager { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.75rem; align-items: center; }
|
||||
.pager-meta { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; }
|
||||
@@ -907,8 +908,11 @@ th { background: #f8fafc; color: #64748b; font-weight: 700; white-space: nowrap;
|
||||
display: grid; place-items: center; cursor: pointer;
|
||||
}
|
||||
.goal-card { padding: 0.9rem; display: grid; gap: 0.65rem; position: relative; }
|
||||
.goal-card :deep(.ui-dd-anchor) {
|
||||
position: absolute; top: 0.6rem; left: 0.5rem;
|
||||
}
|
||||
.goal-card .dots {
|
||||
position: absolute; top: 0.6rem; left: 0.5rem; border: 0; background: transparent; color: #94a3b8; cursor: pointer;
|
||||
border: 0; background: transparent; color: #94a3b8; cursor: pointer;
|
||||
}
|
||||
.goal-body { display: grid; grid-template-columns: 1fr auto; gap: 0.65rem; align-items: center; }
|
||||
.goal-body strong { display: block; }
|
||||
|
||||
@@ -5,7 +5,7 @@ import axios from 'axios';
|
||||
import { api } from '@/lib/api';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import UiIcon from '@/components/UiIcon.vue';
|
||||
import UiModal from '@/components/ui/UiModal.vue';
|
||||
import { UiDropdownMenu, UiModal } from '@/components/ui';
|
||||
import {
|
||||
ROLE_PERMISSION_ITEMS,
|
||||
type InviteUserRequest,
|
||||
@@ -754,16 +754,20 @@ onUnmounted(() => {
|
||||
</td>
|
||||
<td class="muted">{{ formatLastLogin(user.lastLoginAt) }}</td>
|
||||
<td class="ops" @click.stop>
|
||||
<div class="kebab">
|
||||
<button type="button" class="ghost-ico" aria-label="عملیات" @click.stop="menuUserId = menuUserId === user.id ? null : user.id">
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
<div v-if="menuUserId === user.id" class="menu" @click.stop>
|
||||
<button type="button" @click="toggleStatus(user)">
|
||||
{{ user.isActive === false ? 'فعال کردن' : 'غیرفعال کردن' }}
|
||||
<UiDropdownMenu
|
||||
:open="menuUserId === user.id"
|
||||
align="end"
|
||||
@update:open="(v) => (menuUserId = v ? user.id : null)"
|
||||
>
|
||||
<template #trigger="{ toggle }">
|
||||
<button type="button" class="ghost-ico" aria-label="عملیات" @click.stop="toggle">
|
||||
<UiIcon name="dots-v" :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<button type="button" @click="toggleStatus(user)">
|
||||
{{ user.isActive === false ? 'فعال کردن' : 'غیرفعال کردن' }}
|
||||
</button>
|
||||
</UiDropdownMenu>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!loading && !pagedUsers.length">
|
||||
@@ -1218,39 +1222,6 @@ tbody tr + tr td {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.kebab {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: absolute;
|
||||
inset-inline-start: 0;
|
||||
top: 100%;
|
||||
z-index: 5;
|
||||
min-width: 9rem;
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 12px 28px rgba(15, 23, 42, 0.12);
|
||||
padding: 0.35rem;
|
||||
}
|
||||
|
||||
.menu button {
|
||||
width: 100%;
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: 0;
|
||||
text-align: right;
|
||||
padding: 0.45rem 0.55rem;
|
||||
border-radius: 8px;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu button:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.ghost-ico {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
Reference in New Issue
Block a user