From 1c3c494a434592e1d803e266780cfb8abd98c9ae Mon Sep 17 00:00:00 2001
From: safavimohadeseh78
Date: Sun, 9 Aug 2026 14:32:09 +0200
Subject: [PATCH] Improve order detail UX, confirm check actions, and fix
notification deep links.
Normalize inbox /orders/:id links, open related admin pages on click, and keep Jalali dates consistent.
Co-authored-by: Cursor
---
src/assets/styles/responsive.css | 84 ++--
src/components/layout/AdminHeader.vue | 40 +-
src/components/ui/BaseModal.vue | 31 +-
src/composables/useOrdersPage.ts | 45 ++-
src/data/mockData.ts | 26 +-
src/router/index.ts | 9 +
src/types/index.ts | 3 +
src/utils/format.ts | 56 ++-
src/utils/inboxLinks.ts | 112 ++++++
src/views/checks/CheckManagementView.vue | 60 ++-
src/views/orders/OrderPricingView.vue | 15 +-
.../orders/components/CompactOrdersPage.vue | 84 +++-
.../orders/components/OrderDetailContent.vue | 369 ++++++++++++------
src/views/shipping/ShippingView.vue | 15 +-
14 files changed, 737 insertions(+), 212 deletions(-)
create mode 100644 src/utils/inboxLinks.ts
diff --git a/src/assets/styles/responsive.css b/src/assets/styles/responsive.css
index e444aaa..ebef51c 100644
--- a/src/assets/styles/responsive.css
+++ b/src/assets/styles/responsive.css
@@ -297,49 +297,22 @@
font-weight: 700;
}
-/* Responsive overrides for shared admin components */
+/* Tablet / compact desktop: keep multi-column layouts usable in modals */
@media (max-width: 1279px) {
.admin-shell__content {
padding: var(--page-padding);
}
- .admin-page-header {
- flex-direction: column;
- align-items: stretch;
- }
-
- .admin-page-header .admin-btn,
- .admin-page-header .admin-action-group {
- width: 100%;
- }
-
- .admin-page-header .admin-action-group {
- flex-wrap: wrap;
- }
-
- .admin-page-header .admin-action-group .admin-btn {
- flex: 1;
- min-width: fit-content;
- }
-
.admin-summary-grid {
grid-template-columns: repeat(2, 1fr);
}
.admin-form-grid {
- grid-template-columns: 1fr;
+ grid-template-columns: repeat(2, 1fr);
}
.admin-info-grid {
- grid-template-columns: 1fr;
- }
-
- .admin-footer-actions {
- flex-direction: column;
- }
-
- .admin-footer-actions .admin-btn {
- width: 100%;
+ grid-template-columns: repeat(2, 1fr);
}
.admin-pagination {
@@ -352,17 +325,53 @@
justify-content: center;
flex-wrap: wrap;
}
+
+ .admin-tab {
+ padding: 10px 12px;
+ font-size: 0.8125rem;
+ }
+
+ .admin-page-header .admin-action-group {
+ flex-wrap: wrap;
+ gap: 8px;
+ }
}
+/* Phone only: stack to single column / action sheet */
@media (max-width: 767px) {
:root {
--header-height: 60px;
}
- .admin-summary-grid {
+ .admin-page-header {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .admin-page-header .admin-btn,
+ .admin-page-header .admin-action-group {
+ width: 100%;
+ }
+
+ .admin-page-header .admin-action-group .admin-btn {
+ flex: 1;
+ min-width: fit-content;
+ }
+
+ .admin-summary-grid,
+ .admin-form-grid,
+ .admin-info-grid {
grid-template-columns: 1fr;
}
+ .admin-footer-actions {
+ flex-direction: column;
+ }
+
+ .admin-footer-actions .admin-btn {
+ width: 100%;
+ }
+
.admin-form__row {
grid-template-columns: 1fr;
}
@@ -397,16 +406,3 @@
width: 100%;
}
}
-
-@media (max-width: 1279px) {
- .admin-tab {
- padding: 10px 12px;
- font-size: 0.8125rem;
- }
-}
-
-@media (min-width: 768px) and (max-width: 1279px) {
- .admin-form-grid {
- grid-template-columns: repeat(2, 1fr);
- }
-}
diff --git a/src/components/layout/AdminHeader.vue b/src/components/layout/AdminHeader.vue
index 39f335c..8d06128 100644
--- a/src/components/layout/AdminHeader.vue
+++ b/src/components/layout/AdminHeader.vue
@@ -5,7 +5,9 @@ import { onClickOutside, useEventListener } from '@vueuse/core'
import AdminIcon from '@/components/icons/AdminIcon.vue'
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import { dashboardApi, notificationsApi } from '@/services/api'
+import { adminNotifications, internalMessages } from '@/data/mockData'
import { formatDateTime } from '@/utils/format'
+import { resolveAdminInboxLink } from '@/utils/inboxLinks'
import { useAuthStore } from '@/stores/auth'
import { useThemeStore, type ThemePreference } from '@/stores/theme'
import type { AdminNotification, InternalMessage } from '@/types'
@@ -26,8 +28,8 @@ const theme = useThemeStore()
const openPanel = ref(null)
const actionsRef = ref(null)
-const messageList = ref([])
-const notificationList = ref([])
+const messageList = ref(internalMessages.map((m) => ({ ...m })))
+const notificationList = ref(adminNotifications.map((n) => ({ ...n })))
const unreadMessages = computed(() => messageList.value.filter((m) => !m.isRead).length)
const unreadNotifications = computed(() => notificationList.value.filter((n) => !n.isRead).length)
@@ -53,7 +55,7 @@ void dashboardApi
if (data.notifications?.length) notificationList.value = data.notifications
})
.catch(() => {
- /* keep mock fallback */
+ /* keep mock fallback seeded above */
})
onClickOutside(actionsRef, () => {
@@ -103,12 +105,34 @@ function markAllNotificationsRead() {
})
}
+function navigateInboxTarget(target: string | null) {
+ closePanel()
+ if (!target) return
+ void router.push(target)
+}
+
+function openMessage(message: InternalMessage) {
+ markMessageRead(message)
+ navigateInboxTarget(
+ resolveAdminInboxLink({
+ link: message.link,
+ orderId: message.orderId,
+ title: message.subject,
+ body: message.preview,
+ }),
+ )
+}
+
function openNotification(notification: AdminNotification) {
markNotificationRead(notification)
- closePanel()
- if (notification.link) {
- router.push(notification.link)
- }
+ navigateInboxTarget(
+ resolveAdminInboxLink({
+ link: notification.link,
+ orderId: notification.orderId,
+ title: notification.title,
+ body: notification.body,
+ }),
+ )
}
function selectTheme(value: ThemePreference) {
@@ -176,7 +200,7 @@ function selectTheme(value: ThemePreference) {
class="admin-header__panel-item"
:class="{ unread: !message.isRead }"
>
-
- تاریخ راس: {{ order.checkRassDate.slice(0, 10) }}
+ تاریخ راس: {{ formatDate(order.checkRassDate) }}
@@ -164,7 +212,7 @@ async function approveAll() {
{{ formatCurrency(check.amount) }}
{{ check.bankName }}
{{ check.sayadId }}
-
{{ check.dueDate.slice(0, 10) }}
+
{{ formatDate(check.dueDate) }}
@@ -201,7 +249,7 @@ async function approveAll() {
بانک {{ check.bankName }}
- سررسید {{ check.dueDate.slice(0, 10) }}
+ سررسید {{ formatDate(check.dueDate) }}
{{ check.sayadId }}
diff --git a/src/views/orders/OrderPricingView.vue b/src/views/orders/OrderPricingView.vue
index 764333d..33963cf 100644
--- a/src/views/orders/OrderPricingView.vue
+++ b/src/views/orders/OrderPricingView.vue
@@ -526,7 +526,7 @@ async function sendInvoice() {
.pricing-page__compact-fields {
display: grid;
- grid-template-columns: 1fr 1fr;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
margin-top: 12px;
}
@@ -537,6 +537,13 @@ async function sendInvoice() {
gap: 6px;
font-size: 0.75rem;
color: var(--color-text-muted);
+ min-width: 0;
+}
+
+.pricing-page__compact-fields :deep(.admin-input),
+.pricing-page__compact-fields :deep(input) {
+ width: 100%;
+ min-width: 0;
}
.pricing-page__invoice-hint {
@@ -553,4 +560,10 @@ async function sendInvoice() {
font-weight: 700;
color: var(--color-text);
}
+
+@media (max-width: 479px) {
+ .pricing-page__compact-fields {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/views/orders/components/CompactOrdersPage.vue b/src/views/orders/components/CompactOrdersPage.vue
index 2dfb88a..a5714c5 100644
--- a/src/views/orders/components/CompactOrdersPage.vue
+++ b/src/views/orders/components/CompactOrdersPage.vue
@@ -1,12 +1,16 @@
@@ -77,7 +113,7 @@ const {
type="button"
class="admin-card-item"
:class="{ selected: selectedOrderId === order.id }"
- @click="selectOrder(order, true)"
+ @click="openDetail(order)"
>
-
+
+
+
+
+
+ سفارشها / جزئیات سفارش
+
+
+
+
@@ -149,6 +210,7 @@ const {
background: transparent;
font-size: 0.875rem;
outline: none;
+ color: var(--color-text);
}
.admin-card-item {
@@ -162,4 +224,16 @@ const {
height: 100%;
background: var(--color-bg);
}
+
+.compact-orders__modal-breadcrumb {
+ margin: 0 0 16px;
+ font-size: 0.8125rem;
+ color: var(--color-text-muted);
+}
+
+@media (min-width: 768px) {
+ .compact-orders__mobile-panel {
+ display: none !important;
+ }
+}
diff --git a/src/views/orders/components/OrderDetailContent.vue b/src/views/orders/components/OrderDetailContent.vue
index 9b4b5a4..e2bedf2 100644
--- a/src/views/orders/components/OrderDetailContent.vue
+++ b/src/views/orders/components/OrderDetailContent.vue
@@ -6,12 +6,15 @@ import { formatCurrency, formatMeterage, formatNumber } from '@/utils/format'
import { RAW_ORDER_STATUS_LABELS, rawStatusLabel } from '@/utils/orderStatus'
import type { Order, RawOrderStatus } from '@/types'
-const props = defineProps<{
- order: Order
- settlementLabel: (method?: string | null) => string
- layout?: 'desktop' | 'compact'
- statusSaving?: boolean
-}>()
+const props = withDefaults(
+ defineProps<{
+ order: Order
+ settlementLabel: (method?: string | null) => string
+ layout?: 'desktop' | 'compact'
+ statusSaving?: boolean
+ }>(),
+ { layout: 'compact' },
+)
const emit = defineEmits<{
pricing: []
@@ -47,6 +50,8 @@ const canSubmitStatus = computed(
() => !!nextStatus.value && (!noteRequired.value || statusNote.value.trim().length > 0),
)
+const useTable = computed(() => props.layout === 'desktop')
+
watch([() => props.order.id, manualMode], () => {
nextStatus.value = ''
statusNote.value = ''
@@ -68,78 +73,104 @@ function submitStatus() {
-
-
- {{ order.orderNumber }}
-
- {{ rawStatusLabel(order.rawStatus) }}
-
-
-
-
اطلاعات مشتری
-
-
-
-
شرکت:
-
{{ order.customerName }}
-
-
-
-
تلفن:
-
{{ order.customerPhone ?? '—' }}
-
-
-
-
شناسه ملی:
-
{{ order.customerNationalId ?? '—' }}
-
-
-
-
نوع مشتری:
-
{{ order.customerType ?? '—' }}
+
-
-
اطلاعات سفارش
-
-
- شرکت:
- {{ order.companyName }}
-
-
- روش تسویه:
-
- {{ settlementLabel(order.settlementMethod) }}
-
-
-
- متراژ کل:
- {{ formatMeterage(order.meterage) }}
-
-
- مبلغ کل:
-
- {{ order.totalAmount != null ? formatCurrency(order.totalAmount) : 'پس از قیمتگذاری' }}
-
-
+
+
+ قیمتگذاری سفارش
+
+
+ مدیریت چکها
+
+
+ رسید پرداخت
+
+
+
+
+
+ اطلاعات مشتری
+
+
+
+
مشتری
+
{{ order.customerName }}
+
+
+
+
تلفن
+
{{ order.customerPhone ?? '—' }}
+
+
+
+
شناسه ملی
+
{{ order.customerNationalId ?? '—' }}
+
+
+
+
نوع مشتری
+
{{ order.customerType ?? '—' }}
+
+
+
+
+
+ اطلاعات سفارش
+
+
+ شرکت
+ {{ order.companyName }}
+
+
+ روش تسویه
+ {{ settlementLabel(order.settlementMethod) }}
+
+
+ متراژ کل
+ {{ formatMeterage(order.meterage) }}
+
+
+ مبلغ کل
+
+ {{ order.totalAmount != null ? formatCurrency(order.totalAmount) : 'پس از قیمتگذاری' }}
+
+
+
+
-
+
محصولات سفارش
-
+
+
کالیته
محصول
- رنگ / نوع
+ رنگ
متراژ
- قیمت تأیید شده
- مبلغ کل
- وضعیت
+ قیمت
+ مبلغ
@@ -150,23 +181,30 @@ function submitStatus() {
{{ formatNumber(item.confirmedMeterage ?? item.requestedMeterage) }}
{{ item.supplyPrice != null ? formatCurrency(item.supplyPrice) : '—' }}
{{ item.totalAmount != null ? formatCurrency(item.totalAmount) : '—' }}
-
+
- {{ item.productName }}
- {{ item.catalogCode }} · {{ item.color }}
- {{ formatMeterage(item.confirmedMeterage ?? item.requestedMeterage) }}
- قیمت: {{ formatCurrency(item.supplyPrice) }}
- مبلغ: {{ formatCurrency(item.totalAmount) }}
+
+ {{ item.productName }}
+ {{ item.catalogCode }}
+
+
+ {{ item.color }}
+ {{ formatMeterage(item.confirmedMeterage ?? item.requestedMeterage) }}
+ {{ formatCurrency(item.supplyPrice) }}
+
+ {{ formatCurrency(item.totalAmount) }}
+
+
-
+
-
+
تغییر وضعیت
@@ -176,13 +214,13 @@ function submitStatus() {
- این گذر خارج از روال استاندارد است؛ ثبت یادداشت اجباری است و در تاریخچهٔ سفارش ذخیره میشود.
+ این گذر خارج از روال استاندارد است؛ ثبت یادداشت اجباری است.
در این وضعیت گذر خودکاری وجود ندارد. برای تغییر، حالت دستی را فعال کنید.
-
+
+
diff --git a/src/views/shipping/ShippingView.vue b/src/views/shipping/ShippingView.vue
index be84d0b..01ab904 100644
--- a/src/views/shipping/ShippingView.vue
+++ b/src/views/shipping/ShippingView.vue
@@ -1,5 +1,6 @@