Add customers gold credit menu with request and active tabs.

Gate the submenu on enable_gold_credit and wire settings UI for SuperAdmin and tenant credit options.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 00:17:03 +03:30
co-authored by Cursor
parent 14d2bb603e
commit 084b449177
15 changed files with 1536 additions and 19 deletions
@@ -0,0 +1,164 @@
<template>
<BasicModal
v-model:visible="modalVisible"
title="تنظیمات اعتبار طلایی"
width="48rem"
ok-text="بستن"
@ok="modalVisible = false"
>
<a-spin :spinning="loading">
<div class="space-y-4">
<div class="text-sm text-gray-500 mb-2">
این تنظیمات در اپ کاربر اعمال می‌شوند. روش‌های پرداخت غیرفعال در اپ نمایش داده نمی‌شوند؛
ادمین همچنان می‌تواند از همه روش‌ها استفاده کند.
</div>
<div
v-for="row in rows"
:key="row.id ?? row.key"
class="rounded-xl border border-slate-100 bg-slate-50/60 p-3"
>
<div class="font-medium text-sm text-gray-800 mb-1">{{ row.title_fa || row.key }}</div>
<div v-if="row.description" class="text-xs text-gray-500 mb-2 whitespace-pre-wrap">
{{ displayDescription(row) }}
</div>
<SettingValueField
:model-value="drafts[row.id!] ?? ''"
:setting-key="row.key"
:setting-type="row.type"
:description="row.description"
:rows="row.key === 'gold_credit_tiers' ? 6 : 4"
:disabled="savingIds.has(row.id!)"
@update:value="onDraftChange(row, $event)"
/>
<div v-if="savedFlashIds.has(row.id!)" class="text-xs text-green-600 mt-1">ذخیره شد</div>
</div>
</div>
</a-spin>
</BasicModal>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { message } from 'ant-design-vue'
import { getConfiguration, setConfiguration } from '@/api/configuration'
import type { Const } from '@/model/configuration'
import SettingValueField from '@/pages/menuPages/configuration/SettingValueField.vue'
import { formatValueForSave, resolveSettingFieldType } from '@/pages/menuPages/configuration/settingTypes'
const GOLD_CREDIT_KEYS = [
'gold_credit_pay_gold_enabled',
'gold_credit_pay_rial_enabled',
'gold_credit_pay_mixed_enabled',
'gold_credit_min_balance_gram',
'gold_credit_default_months',
'gold_credit_info_text',
'gold_credit_request_success_text',
'gold_credit_tiers',
'gold_credit_penalty_type',
'gold_credit_penalty_amount',
'gold_credit_grace_hours',
]
const props = defineProps<{ visible?: boolean }>()
const emit = defineEmits<{ (e: 'update:visible', value: boolean): void }>()
const modalVisible = computed({
get: () => props.visible ?? false,
set: (v: boolean) => emit('update:visible', v),
})
const loading = ref(false)
const rows = ref<Const[]>([])
const drafts = ref<Record<number, string>>({})
const savingIds = ref(new Set<number>())
const savedFlashIds = ref(new Set<number>())
const saveTimers = new Map<number, ReturnType<typeof setTimeout>>()
const debounceTimers = new Map<number, ReturnType<typeof setTimeout>>()
function displayDescription(row: Const) {
if (row.type === 'select' && row.description?.includes('|')) {
return row.description.split('|')[0]
}
return row.description
}
watch(
() => props.visible,
async (open) => {
if (!open) return
loading.value = true
try {
const all = (await getConfiguration()) ?? []
rows.value = all
.filter((r) => r.key && GOLD_CREDIT_KEYS.includes(r.key))
.sort(
(a, b) =>
GOLD_CREDIT_KEYS.indexOf(a.key!) - GOLD_CREDIT_KEYS.indexOf(b.key!)
)
const next: Record<number, string> = {}
for (const row of rows.value) {
if (row.id != null) next[row.id] = String(row.value ?? '')
}
drafts.value = next
} finally {
loading.value = false
}
}
)
function onDraftChange(row: Const, value: string) {
if (row.id == null) return
drafts.value[row.id] = value
scheduleSave(row)
}
function scheduleSave(row: Const) {
if (row.id == null) return
if (debounceTimers.has(row.id)) clearTimeout(debounceTimers.get(row.id))
debounceTimers.set(
row.id,
setTimeout(() => {
void saveRow(row)
}, 600)
)
}
async function saveRow(row: Const) {
if (row.id == null) return
const type = resolveSettingFieldType(row)
const raw = drafts.value[row.id] ?? row.value ?? ''
const value = formatValueForSave(raw, type)
if (value === (row.value ?? '')) return
savingIds.value.add(row.id)
savedFlashIds.value.delete(row.id)
try {
await setConfiguration({
id: row.id,
key: row.key,
value,
description: row.description ?? '',
is_active: row.is_active,
title_fa: row.title_fa ?? '',
type,
})
row.value = value
savedFlashIds.value.add(row.id)
if (saveTimers.has(row.id)) clearTimeout(saveTimers.get(row.id))
saveTimers.set(
row.id,
setTimeout(() => {
savedFlashIds.value.delete(row.id!)
savedFlashIds.value = new Set(savedFlashIds.value)
}, 2000)
)
} catch {
drafts.value[row.id] = String(row.value ?? '')
message.error(`ذخیره «${row.title_fa || row.key}» ناموفق بود`)
} finally {
savingIds.value.delete(row.id)
savingIds.value = new Set(savingIds.value)
}
}
</script>