Add SMS auto-block threshold input on tenant page.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-08 14:12:30 +03:30
co-authored by Cursor
parent 8e696bb591
commit 196166e1e5
2 changed files with 47 additions and 3 deletions
+15 -3
View File
@@ -10,6 +10,7 @@ import {
/** /**
* Money field bound to **rial** storage values (API/DB). * Money field bound to **rial** storage values (API/DB).
* User types and sees **toman** (÷10 / ×10 at the edges). * User types and sees **toman** (÷10 / ×10 at the edges).
* With `nullable`, empty input emits `null` (for optional thresholds).
*/ */
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@@ -18,23 +19,33 @@ const props = withDefaults(
placeholder?: string placeholder?: string
allowNegative?: boolean allowNegative?: boolean
disabled?: boolean disabled?: boolean
nullable?: boolean
}>(), }>(),
{ {
size: 'large', size: 'large',
placeholder: '', placeholder: '',
allowNegative: false, allowNegative: false,
disabled: false, disabled: false,
nullable: false,
}, },
) )
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:value', value: number): void (e: 'update:value', value: number | null): void
}>() }>()
const display = computed(() => toGroupedDisplay(rialToToman(props.value ?? 0))) const display = computed(() => {
if (props.nullable && (props.value == null || props.value === undefined)) return ''
return toGroupedDisplay(rialToToman(props.value ?? 0))
})
function onInput(raw: string | number) { function onInput(raw: string | number) {
const toman = fromGroupedInput(String(raw ?? ''), props.allowNegative) const text = String(raw ?? '').trim()
if (props.nullable && !text) {
emit('update:value', null)
return
}
const toman = fromGroupedInput(text, props.allowNegative)
emit('update:value', tomanToRial(toman)) emit('update:value', tomanToRial(toman))
} }
</script> </script>
@@ -47,6 +58,7 @@ function onInput(raw: string | number) {
:placeholder="placeholder" :placeholder="placeholder"
:disabled="disabled" :disabled="disabled"
:value="display" :value="display"
allow-clear
@update:value="onInput" @update:value="onInput"
/> />
</template> </template>
+32
View File
@@ -50,6 +50,7 @@ const summary = ref<SmsSummary | null>(null)
const form = reactive({ const form = reactive({
pricePerPartRials: 2500, pricePerPartRials: 2500,
isBlocked: false, isBlocked: false,
autoBlockBelowRials: null as number | null,
alertCriticalRials: 10000, alertCriticalRials: 10000,
alertWarningRials: 100000, alertWarningRials: 100000,
}) })
@@ -113,6 +114,8 @@ async function loadDetail() {
Object.assign(form, { Object.assign(form, {
pricePerPartRials: data.account.pricePerPartRials, pricePerPartRials: data.account.pricePerPartRials,
isBlocked: data.account.isBlocked, isBlocked: data.account.isBlocked,
autoBlockBelowRials:
data.account.autoBlockBelowRials == null ? null : data.account.autoBlockBelowRials,
alertCriticalRials: data.account.alertCriticalRials, alertCriticalRials: data.account.alertCriticalRials,
alertWarningRials: data.account.alertWarningRials, alertWarningRials: data.account.alertWarningRials,
}) })
@@ -150,6 +153,7 @@ async function saveThresholds() {
await api.patch(`/sms/tenants/${tenantId.value}`, { await api.patch(`/sms/tenants/${tenantId.value}`, {
alertCriticalRials: form.alertCriticalRials, alertCriticalRials: form.alertCriticalRials,
alertWarningRials: form.alertWarningRials, alertWarningRials: form.alertWarningRials,
autoBlockBelowRials: form.autoBlockBelowRials,
}) })
message.success('آستانه‌ها ذخیره شد') message.success('آستانه‌ها ذخیره شد')
await loadDetail() await loadDetail()
@@ -511,6 +515,27 @@ onMounted(async () => {
<a-button type="primary" size="large" :loading="saving" @click="saveThresholds">ذخیره آستانه‌ها</a-button> <a-button type="primary" size="large" :loading="saving" @click="saveThresholds">ذخیره آستانه‌ها</a-button>
</div> </div>
</div> </div>
<div class="subcard auto-block">
<h3>مسدودسازی خودکار ارسال</h3>
<p class="hint">
اگر مبلغی بگذارید و موجودی از آن کمتر شود، ارسال پیامک خودکار قطع می‌شود (کلید مسدودسازی
روشن نمی‌شود). خالی بگذارید تا فقط با کلید دستی مسدود شود.
</p>
<div class="threshold-grid">
<label>
<span class="tone-label">آستانه مسدودسازی خودکار (تومان)</span>
<MoneyInput
v-model:value="form.autoBlockBelowRials"
nullable
placeholder="خالی = بدون مسدودسازی خودکار"
/>
</label>
<a-button type="primary" size="large" :loading="saving" @click="saveThresholds">
ذخیره آستانه
</a-button>
</div>
</div>
</section> </section>
<section class="panel-card"> <section class="panel-card">
@@ -845,6 +870,13 @@ onMounted(async () => {
font-size: 0.92rem; font-size: 0.92rem;
} }
.subcard .hint {
margin: 8px 0 12px;
color: #64748b;
font-size: 0.85rem;
line-height: 1.7;
}
.subcard.sky { .subcard.sky {
background: linear-gradient(160deg, #f0f9ff, #fff); background: linear-gradient(160deg, #f0f9ff, #fff);
border-color: #bae6fd; border-color: #bae6fd;