From c21738950ddfddc88045f733112b540732a7383c Mon Sep 17 00:00:00 2001 From: safavimohadeseh78 Date: Tue, 3 Nov 2026 13:04:09 +0330 Subject: [PATCH] feat(convert): show history and let customers edit pending requests Desktop history sits beside the form. Pending requests can be updated or deleted, and the shared wallet surface drops its extra border and padding. Co-authored-by: Cursor --- .../abshode-domain-lookup/learned-notes.md | 4 + src/api/copperConvert.ts | 31 +++++ .../components/CopperConvertCalculator.vue | 41 +++--- .../components/CopperConvertHistory.vue | 126 ++++++++++++++++++ .../composables/useCopperConvertPage.ts | 109 ++++++++++++++- .../convert/desktop/DesktopConvertView.vue | 90 ++++--------- .../base/convert/mobile/MobileConvertView.vue | 4 +- .../wallet/desktop/styles/desktop-wallet.less | 2 - 8 files changed, 320 insertions(+), 87 deletions(-) create mode 100644 src/views/base/convert/components/CopperConvertHistory.vue diff --git a/.cursor/skills/abshode-domain-lookup/learned-notes.md b/.cursor/skills/abshode-domain-lookup/learned-notes.md index e0a7ea3..92a437a 100644 --- a/.cursor/skills/abshode-domain-lookup/learned-notes.md +++ b/.cursor/skills/abshode-domain-lookup/learned-notes.md @@ -62,3 +62,7 @@ - 2026-11-02 | سه زبانه تبدیل از اول | همان `CopperConvertCalculator.vue` | زبانه ۱.۵ و ۲.۵ دیگر پشت `remainderAmount > 0` نیستند؛ زیر زبانه مقدار، قبل از نتیجه - 2026-11-02 | زبانه سیم مکمل | `useCopperConvertPage.ts` | بودجه = ارزش مقدار انتخاب‌شده؛ نصف به ۱.۵ و بقیه به ۲.۵؛ جابه‌جایی یکی دیگری را پر می‌کند - 2026-11-01 | تاریخچه برداشت کیف پول | `wallet/mobile/MobileWalletView.vue` + `desktop/DesktopWalletView.vue` + `useWalletPageData.ts` | `walletWithdrawItems` از قبل لود می‌شد؛ زیر راهنمای برداشت و زیر حساب‌های دسکتاپ نشان داده می‌شود +- 2026-11-03 | تاریخچه تبدیل کاربر | `convert/components/CopperConvertHistory.vue` + `useCopperConvertPage.ts` + سرور `Customer/CopperConvertRequestController@index` | GET `/customer/copper-convert-request` فقط ردیف‌های همان مشتری؛ زیر فرم موبایل و دسکتاپ +- 2026-11-03 | ویرایش/حذف تبدیل در انتظار | همان تاریخچه + `CopperConvertRequestService::update/remove` | فقط status=pending؛ ویرایش فرم را با همان حلقه‌ها پر می‌کند و PUT می‌زند +- 2026-11-03 | چیدمان دسکتاپ تبدیل | `convert/desktop/DesktopConvertView.vue` | مثل کیف پول: `dwallet-grid`؛ فرم راست، تاریخچه چپ با `variant=side` +- 2026-11-03 | مداد ویرایش تبدیل | `convert/components/CopperConvertHistory.vue` | فقط pending؛ آیکون `vuesax-linear:edit` کنار وضعیت، همان `beginEdit` diff --git a/src/api/copperConvert.ts b/src/api/copperConvert.ts index 5804cb6..1db59d2 100644 --- a/src/api/copperConvert.ts +++ b/src/api/copperConvert.ts @@ -1,6 +1,18 @@ import useAxios from '@/utils/useAxios' import type { CopperConvertSource } from '@/utils/copperWireConvert' +export interface CopperConvertHistoryRow { + id: number + status: string + source: string + asset_unit: string + consumed_amount: number + leftover_amount: number + wire15_rings: number + wire25_rings: number + created_at?: string +} + export function createCopperConvertRequest(payload: { source: CopperConvertSource rings15: number @@ -8,3 +20,22 @@ export function createCopperConvertRequest(payload: { }) { return useAxios.post('/customer/copper-convert-request', payload) } + +export function getCopperConvertRequests() { + return useAxios.get('/customer/copper-convert-request') +} + +export function updateCopperConvertRequest( + id: number, + payload: { + source: CopperConvertSource + rings15: number + rings25: number + }, +) { + return useAxios.put(`/customer/copper-convert-request/${id}`, payload) +} + +export function deleteCopperConvertRequest(id: number) { + return useAxios.delete(`/customer/copper-convert-request/${id}`) +} diff --git a/src/views/base/convert/components/CopperConvertCalculator.vue b/src/views/base/convert/components/CopperConvertCalculator.vue index ebf8b5d..041a74c 100644 --- a/src/views/base/convert/components/CopperConvertCalculator.vue +++ b/src/views/base/convert/components/CopperConvertCalculator.vue @@ -1,11 +1,11 @@ @@ -227,6 +232,8 @@ const { leftoverAmount, submitting, submitRequest, + editingId, + cancelEdit, result, priceRows, } = useCopperConvertPageContext() diff --git a/src/views/base/convert/components/CopperConvertHistory.vue b/src/views/base/convert/components/CopperConvertHistory.vue new file mode 100644 index 0000000..3696f08 --- /dev/null +++ b/src/views/base/convert/components/CopperConvertHistory.vue @@ -0,0 +1,126 @@ + + + diff --git a/src/views/base/convert/composables/useCopperConvertPage.ts b/src/views/base/convert/composables/useCopperConvertPage.ts index 146f82b..eb4f085 100644 --- a/src/views/base/convert/composables/useCopperConvertPage.ts +++ b/src/views/base/convert/composables/useCopperConvertPage.ts @@ -1,6 +1,7 @@ import { computed, inject, + nextTick, onMounted, onUnmounted, provide, @@ -10,9 +11,15 @@ import { type InjectionKey, type Ref, } from 'vue' -import { createCopperConvertRequest } from '@/api/copperConvert' +import { + createCopperConvertRequest, + deleteCopperConvertRequest, + getCopperConvertRequests, + updateCopperConvertRequest, + type CopperConvertHistoryRow, +} from '@/api/copperConvert' import { getProducts } from '@/api/products' -import { message } from 'ant-design-vue' +import { message, Modal } from 'ant-design-vue' import { useWalletAssets } from '@/composables/useWalletAssets' import type { Product } from '@/models/products' import type { WalletInventoryCard } from '@/models/walletAssets' @@ -47,6 +54,13 @@ export interface CopperConvertPageContext { leftoverAmount: ComputedRef submitting: Ref submitRequest: () => Promise + editingId: Ref + beginEdit: (row: CopperConvertHistoryRow) => Promise + cancelEdit: () => void + deleteRequest: (id: number) => void + deletingId: Ref + history: Ref + historyLoading: Ref prices: ComputedRef result: ComputedRef priceRows: ComputedRef<{ label: string; value: string; missing?: boolean }[]> @@ -142,6 +156,10 @@ export function useCopperConvertPage() { const wire25Rings = ref(0) const submitting = ref(false) const catalog = ref([]) + const history = ref([]) + const historyLoading = ref(true) + const editingId = ref(null) + const deletingId = ref(0) const { assetsSummary, loadWalletAssets } = useWalletAssets() const assetCards = computed(() => { @@ -310,24 +328,97 @@ export function useCopperConvertPage() { writeRings(rings15For(next25), next25) }, { flush: 'sync' }) + function hasPendingRequest() { + return history.value.some((row) => row.status === 'pending') + } + + async function beginEdit(row: CopperConvertHistoryRow) { + if (row.status !== 'pending' || submitting.value) return + editingId.value = row.id + if (row.source === 'katod' || row.source === 'maftoul' || row.source === 'shamsh') { + source.value = row.source + } + await nextTick() + const rings15 = Math.max(0, Math.min(maxRings15.value, Math.floor(Number(row.wire15_rings) || 0))) + const rings25 = Math.min(Math.floor(Number(row.wire25_rings) || 0), rings25For(rings15)) + writeRings(rings15, Math.max(0, rings25)) + const forms = document.querySelectorAll('[data-convert-form]') + const visible = [...forms].find((node) => node instanceof HTMLElement && node.getClientRects().length > 0) + visible?.scrollIntoView({ behavior: 'smooth', block: 'start' }) + } + + function cancelEdit() { + if (submitting.value) return + editingId.value = null + applyDefaultSplit() + } + + function deleteRequest(id: number) { + if (deletingId.value || submitting.value) return + const row = history.value.find((item) => item.id === id) + if (!row || row.status !== 'pending') return + Modal.confirm({ + title: 'حذف درخواست', + content: 'این درخواست در انتظار تایید حذف شود؟', + okText: 'حذف', + cancelText: 'انصراف', + okType: 'danger', + async onOk() { + deletingId.value = id + try { + await deleteCopperConvertRequest(id) + if (editingId.value === id) editingId.value = null + message.success('درخواست تبدیل حذف شد') + await loadHistory() + } finally { + deletingId.value = 0 + } + }, + }) + } + async function submitRequest() { if (submitting.value) return if (wire15Rings.value + wire25Rings.value < 1) return + if (!editingId.value && hasPendingRequest()) { + message.warning('یک درخواست در انتظار تایید دارید. همان را ویرایش یا حذف کنید.') + return + } submitting.value = true try { - await createCopperConvertRequest({ + const payload = { source: source.value, rings15: wire15Rings.value, rings25: wire25Rings.value, - }) + } + if (editingId.value) { + await updateCopperConvertRequest(editingId.value, payload) + editingId.value = null + message.success('درخواست تبدیل به‌روز شد') + } else { + await createCopperConvertRequest(payload) + message.success('درخواست تبدیل برای تأیید ادمین ثبت شد') + } wire15Rings.value = 0 wire25Rings.value = 0 - message.success('درخواست تبدیل برای تأیید ادمین ثبت شد') + void loadHistory() } finally { submitting.value = false } } + async function loadHistory() { + historyLoading.value = true + try { + const rows = await getCopperConvertRequests() + history.value = Array.isArray(rows) ? rows : [] + } catch { + // خطای شبکه را اینترسپتور نشان می‌دهد؛ لیست قبلی می‌ماند + } finally { + historyLoading.value = false + } + } + const priceRows = computed(() => { const p = prices.value const row = (label: string, amount: number) => ({ @@ -352,6 +443,7 @@ export function useCopperConvertPage() { getLastPrices(), getProducts(), loadWalletAssets(), + loadHistory(), ]) catalog.value = Array.isArray(list) ? list : [] } catch { @@ -385,6 +477,13 @@ export function useCopperConvertPage() { leftoverAmount, submitting, submitRequest, + editingId, + beginEdit, + cancelEdit, + deleteRequest, + deletingId, + history, + historyLoading, prices, result, priceRows, diff --git a/src/views/base/convert/desktop/DesktopConvertView.vue b/src/views/base/convert/desktop/DesktopConvertView.vue index fce20ff..90a60a2 100644 --- a/src/views/base/convert/desktop/DesktopConvertView.vue +++ b/src/views/base/convert/desktop/DesktopConvertView.vue @@ -1,77 +1,43 @@ diff --git a/src/views/base/convert/mobile/MobileConvertView.vue b/src/views/base/convert/mobile/MobileConvertView.vue index bc9236d..02ced17 100644 --- a/src/views/base/convert/mobile/MobileConvertView.vue +++ b/src/views/base/convert/mobile/MobileConvertView.vue @@ -1,10 +1,11 @@ @@ -12,4 +13,5 @@ diff --git a/src/views/base/wallet/desktop/styles/desktop-wallet.less b/src/views/base/wallet/desktop/styles/desktop-wallet.less index 000f8bf..45af2a0 100644 --- a/src/views/base/wallet/desktop/styles/desktop-wallet.less +++ b/src/views/base/wallet/desktop/styles/desktop-wallet.less @@ -120,13 +120,11 @@ .dwallet-surface { background: #fff; - border: 1px solid rgba(15, 23, 42, 0.06); border-radius: 16px; box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); } .dwallet-form { - padding: 24px; &__title { font-size: 16px;