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;