feat: add product select to price alerts
Customer can pick a product when creating an alert and see it on the card. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Vendored
+6
@@ -13,15 +13,20 @@ declare module 'vue' {
|
||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||
AForm: typeof import('ant-design-vue/es')['Form']
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
AImage: typeof import('ant-design-vue/es')['Image']
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
||||
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
ASlider: typeof import('ant-design-vue/es')['Slider']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
BaseDatePicker: typeof import('./src/components/BasicPacks/BaseDatePicker/src/BaseDatePicker.vue')['default']
|
||||
Basic: typeof import('./src/components/BasicPacks/BaseDatePicker/src/datePick/demo/basic.vue')['default']
|
||||
@@ -73,5 +78,6 @@ declare module 'vue' {
|
||||
TransactionSkeleton: typeof import('./src/components/TransactionSkeleton.vue')['default']
|
||||
VanActionSheet: typeof import('vant/es')['ActionSheet']
|
||||
VanDialog: typeof import('vant/es')['Dialog']
|
||||
VanField: typeof import('vant/es')['Field']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import {
|
||||
fetchSuperAdminJson,
|
||||
resolveTenantDomain,
|
||||
SA_PROGRAM_SETTINGS_PATH,
|
||||
} from '@/config/superadmin'
|
||||
|
||||
export type SuperAdminSettingRow = {
|
||||
id?: number
|
||||
key?: string
|
||||
value?: string | number
|
||||
title_fa?: string
|
||||
description?: string
|
||||
is_active?: boolean | number | string
|
||||
type?: string
|
||||
can_edit?: number | boolean | string
|
||||
}
|
||||
|
||||
function parseSettingsPayload(data: unknown): SuperAdminSettingRow[] {
|
||||
if (Array.isArray(data)) return data as SuperAdminSettingRow[]
|
||||
if (data && typeof data === 'object') {
|
||||
const obj = data as Record<string, unknown>
|
||||
if (Array.isArray(obj.settings)) return obj.settings as SuperAdminSettingRow[]
|
||||
if (Array.isArray(obj.items)) return obj.items as SuperAdminSettingRow[]
|
||||
if (Array.isArray(obj.data)) return obj.data as SuperAdminSettingRow[]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/** کلید قفلشده در get_settings محلی — باید از سوپرادمین بیاید */
|
||||
export function isLockedCanEdit(canEdit: unknown): boolean {
|
||||
return canEdit === 0 || canEdit === false || canEdit === '0'
|
||||
}
|
||||
|
||||
/** تنظیمات پیشرفته نسخه از سوپرادمین — فقط GET /api/public/program-settings?domain= */
|
||||
export async function fetchSuperAdminProgramSettings(): Promise<SuperAdminSettingRow[]> {
|
||||
const domain = encodeURIComponent(resolveTenantDomain())
|
||||
const data = await fetchSuperAdminJson(
|
||||
(base) => `${base}${SA_PROGRAM_SETTINGS_PATH}?domain=${domain}`,
|
||||
)
|
||||
return parseSettingsPayload(data)
|
||||
}
|
||||
@@ -8,9 +8,6 @@ export const SUPERADMIN_BASES = [
|
||||
/** هر API: سه دامنه × سه دور */
|
||||
export const SA_RETRY_ROUNDS = 3
|
||||
|
||||
/** تنظیمات پیشرفته نسخه — همان دادهٔ /get_settings بدون نیاز به nginx جدا */
|
||||
export const SA_PROGRAM_SETTINGS_PATH = '/api/public/program-settings'
|
||||
|
||||
function normalizeBase(url: string) {
|
||||
return String(url || '').trim().replace(/\/$/, '')
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export interface PriceTrigger {
|
||||
id?: number
|
||||
product_id?: number
|
||||
product?: { id: number; title?: string }
|
||||
target_price: string
|
||||
price_condition: string
|
||||
action: string
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { getSettings } from '@/api/configuration'
|
||||
import { fetchSuperAdminProgramSettings, isLockedCanEdit } from '@/api/superadminSettings'
|
||||
import { resolveMeltedGoldLabel, resolveMetalTypeLabel } from '@/utils/metalLabels'
|
||||
import { resolveTradeWeightUnit } from '@/utils/weightUnit'
|
||||
|
||||
@@ -13,7 +12,6 @@ export interface AppSetting {
|
||||
description?: string
|
||||
is_active?: boolean
|
||||
type?: string
|
||||
can_edit?: number | boolean | string
|
||||
}
|
||||
|
||||
export const useSettingsStore = defineStore('settings', () => {
|
||||
@@ -67,19 +65,17 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
|
||||
fetchPromise = (async () => {
|
||||
try {
|
||||
const localRaw = await getSettings().catch(() => [])
|
||||
const remote = await fetchSuperAdminProgramSettings()
|
||||
const local = Array.isArray(localRaw) ? localRaw : []
|
||||
const editable = local.filter((row) => !isLockedCanEdit(row?.can_edit))
|
||||
settings.value = []
|
||||
mergeSettings(remote as AppSetting[])
|
||||
mergeSettings(editable)
|
||||
loaded.value = true
|
||||
return settings.value
|
||||
const data = await getSettings()
|
||||
if (Array.isArray(data)) {
|
||||
mergeSettings(data)
|
||||
}
|
||||
} catch (error) {
|
||||
fetchPromise = null
|
||||
throw error
|
||||
console.log(error)
|
||||
} finally {
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
return settings.value
|
||||
})()
|
||||
|
||||
return fetchPromise
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
<p class="dptr-card__price">{{ usePrice(item.target_price) }}</p>
|
||||
|
||||
<div class="dptr-card__meta">
|
||||
<div class="dptr-card__row">
|
||||
<span class="dptr-card__label">محصول:</span>
|
||||
<span class="dptr-card__value">{{ productTitle }}</span>
|
||||
</div>
|
||||
<div class="dptr-card__row">
|
||||
<span class="dptr-card__label">جهت قیمت:</span>
|
||||
<span class="dptr-card__value">{{ returnPriceCondition(item.price_condition) }}</span>
|
||||
@@ -76,6 +80,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 mt-2">
|
||||
<BasicInfoItem is-line title="محصول" :value="productTitle" />
|
||||
<BasicInfoItem is-line title="جهت قیمت" :value="returnPriceCondition(item.price_condition)" />
|
||||
<BasicInfoItem is-line title="نوع هشدار" :value="returnAction(item.action)" />
|
||||
</div>
|
||||
@@ -108,7 +113,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { usePrice } from '../../../../utils/usePrice'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { toJalali } from '../../../../utils/useDateTime'
|
||||
import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
|
||||
import type { PriceTrigger } from '@/models/PriceTrigger'
|
||||
@@ -132,6 +137,8 @@ const props = withDefaults(
|
||||
|
||||
const emits = defineEmits(['edit', 'refresh'])
|
||||
|
||||
const productTitle = computed(() => props.item.product?.title || 'طلا')
|
||||
|
||||
onMounted(() => {
|
||||
isActive.value = props.item.is_active
|
||||
})
|
||||
|
||||
@@ -7,6 +7,16 @@
|
||||
@finish="ok()"
|
||||
>
|
||||
<div class="flex flex-col gap-4 w-full">
|
||||
<a-form-item :rules="[required()]" label="محصول" name="product_id">
|
||||
<BasicSelect
|
||||
v-model:value="form.product_id"
|
||||
:items="products"
|
||||
key-name="id"
|
||||
placeholder="آبشده، سکه، نقره و ..."
|
||||
size="large"
|
||||
value-name="title"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :rules="[required()]" label="جهت قیمت" name="price_condition">
|
||||
<BasicSelect
|
||||
v-model:value="form.price_condition"
|
||||
@@ -58,6 +68,7 @@ const {
|
||||
loading,
|
||||
radioStyle,
|
||||
typeItems,
|
||||
products,
|
||||
ok,
|
||||
} = usePriceTriggerPageContext()
|
||||
</script>
|
||||
|
||||
@@ -2,12 +2,25 @@ import { inject, onMounted, provide, reactive, ref, type InjectionKey, type Ref
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { type FormInstance, message } from 'ant-design-vue'
|
||||
import { createPriceTrigger, getPriceTriggers, updatePriceTrigger } from '@/api/triggerPrice'
|
||||
import { getProducts } from '@/api/products'
|
||||
import type { SelectItem } from '@/models/SelectItem'
|
||||
import type { PriceTrigger } from '@/models/PriceTrigger'
|
||||
import type { Product } from '@/models/products'
|
||||
import { loading } from '@/utils'
|
||||
|
||||
function emptyForm(): PriceTrigger {
|
||||
return {
|
||||
product_id: undefined,
|
||||
price_condition: '',
|
||||
is_active: true,
|
||||
action: '',
|
||||
target_price: '',
|
||||
}
|
||||
}
|
||||
|
||||
export interface PriceTriggerPageContext {
|
||||
items: Ref<PriceTrigger[]>
|
||||
products: Ref<Product[]>
|
||||
addTriggerVisible: Ref<boolean>
|
||||
editId: Ref<number | undefined>
|
||||
form: Ref<PriceTrigger>
|
||||
@@ -27,6 +40,7 @@ export const PRICE_TRIGGER_PAGE_CONTEXT_KEY: InjectionKey<PriceTriggerPageContex
|
||||
|
||||
export function usePriceTriggerPage() {
|
||||
const items = ref<PriceTrigger[]>([])
|
||||
const products = ref<Product[]>([])
|
||||
const addTriggerVisible = ref(false)
|
||||
const editId = ref<number | undefined>(undefined)
|
||||
const formRef = ref<FormInstance>()
|
||||
@@ -37,12 +51,7 @@ export function usePriceTriggerPage() {
|
||||
lineHeight: '30px',
|
||||
})
|
||||
|
||||
const form = ref<PriceTrigger>({
|
||||
price_condition: '',
|
||||
is_active: true,
|
||||
action: '',
|
||||
target_price: '',
|
||||
})
|
||||
const form = ref<PriceTrigger>(emptyForm())
|
||||
|
||||
const typeItems: SelectItem[] = [
|
||||
{ value: 'بزرگتر', key: '>' },
|
||||
@@ -51,12 +60,7 @@ export function usePriceTriggerPage() {
|
||||
]
|
||||
|
||||
function resetFields() {
|
||||
form.value = {
|
||||
price_condition: '',
|
||||
is_active: true,
|
||||
action: '',
|
||||
target_price: '',
|
||||
}
|
||||
form.value = emptyForm()
|
||||
}
|
||||
|
||||
function openAdd() {
|
||||
@@ -66,7 +70,10 @@ export function usePriceTriggerPage() {
|
||||
}
|
||||
|
||||
function openEdit(val: PriceTrigger) {
|
||||
form.value = cloneDeep(val)
|
||||
form.value = cloneDeep({
|
||||
...val,
|
||||
product_id: val.product_id ?? val.product?.id,
|
||||
})
|
||||
editId.value = form.value.id
|
||||
addTriggerVisible.value = true
|
||||
}
|
||||
@@ -82,6 +89,14 @@ export function usePriceTriggerPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProducts() {
|
||||
try {
|
||||
products.value = ((await getProducts()) as Product[]) ?? []
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
async function ok() {
|
||||
await formRef.value?.validateFields().then(async () => {
|
||||
try {
|
||||
@@ -104,10 +119,12 @@ export function usePriceTriggerPage() {
|
||||
|
||||
onMounted(() => {
|
||||
initItems()
|
||||
loadProducts()
|
||||
})
|
||||
|
||||
const context: PriceTriggerPageContext = {
|
||||
items,
|
||||
products,
|
||||
addTriggerVisible,
|
||||
editId,
|
||||
form,
|
||||
|
||||
Reference in New Issue
Block a user