Add checks list page and richer order/check admin UX.

Replace the checks redirect with a real list, expand per-order check management, and add a row actions menu on the orders table.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-09 16:17:52 +03:30
co-authored by Cursor
parent f46b02efa8
commit c69c358a4d
10 changed files with 1071 additions and 170 deletions
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
import AdminIcon from '@/components/icons/AdminIcon.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
import BaseModal from '@/components/ui/BaseModal.vue'
@@ -7,6 +8,7 @@ import OrderAddModal from '@/views/orders/components/OrderAddModal.vue'
import { useOrdersPageContext } from '@/composables/useOrdersPage'
import { ORDER_TABS } from '@/utils/orderStatus'
import { formatCurrency, formatDateTime, formatMeterage } from '@/utils/format'
import type { Order } from '@/types'
const {
activeTab,
@@ -26,6 +28,52 @@ const {
openAddModal,
} = useOrdersPageContext()
const menuOrder = ref<Order | null>(null)
const menuStyle = ref<Record<string, string>>({})
function closeMenu() {
menuOrder.value = null
}
async function toggleMenu(order: Order, event: MouseEvent) {
event.stopPropagation()
if (menuOrder.value?.id === order.id) {
closeMenu()
return
}
const btn = event.currentTarget as HTMLElement
const rect = btn.getBoundingClientRect()
menuOrder.value = order
await nextTick()
const menuWidth = 168
const left = Math.min(
Math.max(8, rect.right - menuWidth),
window.innerWidth - menuWidth - 8,
)
menuStyle.value = {
position: 'fixed',
top: `${rect.bottom + 4}px`,
left: `${left}px`,
width: `${menuWidth}px`,
}
}
function onDocumentClick() {
closeMenu()
}
onMounted(() => {
document.addEventListener('click', onDocumentClick)
window.addEventListener('scroll', closeMenu, true)
window.addEventListener('resize', closeMenu)
})
onUnmounted(() => {
document.removeEventListener('click', onDocumentClick)
window.removeEventListener('scroll', closeMenu, true)
window.removeEventListener('resize', closeMenu)
})
function handlePricing() {
if (!selectedOrder.value) return
closeDetailModal()
@@ -43,6 +91,25 @@ function handleCashReceipt() {
closeDetailModal()
goCashReceipt(selectedOrder.value)
}
function runMenuAction(action: 'detail' | 'pricing' | 'checks' | 'cash') {
const order = menuOrder.value
if (!order) return
closeMenu()
if (action === 'detail') {
openOrderDetail(order)
return
}
if (action === 'pricing') {
goPricing(order)
return
}
if (action === 'checks') {
goChecks(order)
return
}
goCashReceipt(order)
}
</script>
<template>
@@ -116,8 +183,15 @@ function handleCashReceipt() {
<td>{{ formatMeterage(order.meterage) }}</td>
<td><StatusBadge :status="order.status" /></td>
<td>{{ formatDateTime(order.createdAt) }}</td>
<td>
<button type="button" class="orders-page__more-btn" @click.stop>
<td class="orders-page__actions-cell">
<button
type="button"
class="orders-page__more-btn"
:aria-expanded="menuOrder?.id === order.id"
aria-haspopup="menu"
aria-label="عملیات سفارش"
@click="toggleMenu(order, $event)"
>
<AdminIcon name="more" :size="18" />
</button>
</td>
@@ -138,6 +212,51 @@ function handleCashReceipt() {
</div>
</div>
<Teleport to="body">
<div
v-if="menuOrder"
class="orders-page__menu"
role="menu"
:style="menuStyle"
@click.stop
>
<button
type="button"
role="menuitem"
class="orders-page__menu-item"
@click="runMenuAction('detail')"
>
جزئیات سفارش
</button>
<button
type="button"
role="menuitem"
class="orders-page__menu-item"
@click="runMenuAction('pricing')"
>
قیمت‌گذاری
</button>
<button
v-if="menuOrder.settlementMethod === 'check'"
type="button"
role="menuitem"
class="orders-page__menu-item"
@click="runMenuAction('checks')"
>
مدیریت چک‌ها
</button>
<button
v-if="menuOrder.settlementMethod === 'cash'"
type="button"
role="menuitem"
class="orders-page__menu-item"
@click="runMenuAction('cash')"
>
رسید پرداخت
</button>
</div>
</Teleport>
<BaseModal
:open="showDetailModal && !!selectedOrder"
title="جزئیات سفارش"
@@ -231,12 +350,23 @@ function handleCashReceipt() {
outline: none;
}
.orders-page__actions-cell {
width: 48px;
}
.orders-page__more-btn {
border: none;
background: transparent;
color: var(--color-text-muted);
padding: 4px;
border-radius: var(--radius-sm);
cursor: pointer;
}
.orders-page__more-btn:hover,
.orders-page__more-btn[aria-expanded='true'] {
color: var(--color-text);
background: var(--color-border-light);
}
.admin-table tbody tr {
@@ -251,3 +381,32 @@ function handleCashReceipt() {
margin: 0 0 16px;
}
</style>
<style>
.orders-page__menu {
z-index: 300000;
min-width: 160px;
padding: 6px;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-surface);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
display: grid;
gap: 2px;
}
.orders-page__menu-item {
border: none;
background: transparent;
color: var(--color-text);
text-align: start;
padding: 8px 10px;
border-radius: var(--radius-sm);
font-size: 0.8125rem;
cursor: pointer;
}
.orders-page__menu-item:hover {
background: var(--color-primary-soft);
}
</style>