Add SMS auto-block threshold input on tenant page.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user