From e578ba521d16a08646b0ee40c7de8d42935c77ee Mon Sep 17 00:00:00 2001 From: adonis Date: Tue, 18 Aug 2026 22:23:25 +0330 Subject: [PATCH] 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 --- components.d.ts | 6 +++ src/api/superadminSettings.ts | 41 ------------------ src/config/superadmin.ts | 3 -- src/models/PriceTrigger.ts | 2 + src/store/settingsStore.ts | 22 ++++------ .../base/priceTrigger/components/Item.vue | 9 +++- .../components/PriceTriggerForm.vue | 11 +++++ .../composables/usePriceTriggerPage.ts | 43 +++++++++++++------ 8 files changed, 66 insertions(+), 71 deletions(-) delete mode 100644 src/api/superadminSettings.ts diff --git a/components.d.ts b/components.d.ts index 7ee50d3..01c2bda 100644 --- a/components.d.ts +++ b/components.d.ts @@ -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'] } } diff --git a/src/api/superadminSettings.ts b/src/api/superadminSettings.ts deleted file mode 100644 index 04e30e9..0000000 --- a/src/api/superadminSettings.ts +++ /dev/null @@ -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 - 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 { - const domain = encodeURIComponent(resolveTenantDomain()) - const data = await fetchSuperAdminJson( - (base) => `${base}${SA_PROGRAM_SETTINGS_PATH}?domain=${domain}`, - ) - return parseSettingsPayload(data) -} diff --git a/src/config/superadmin.ts b/src/config/superadmin.ts index 6efb26d..daa4df8 100644 --- a/src/config/superadmin.ts +++ b/src/config/superadmin.ts @@ -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(/\/$/, '') } diff --git a/src/models/PriceTrigger.ts b/src/models/PriceTrigger.ts index e912873..2e2fa9e 100644 --- a/src/models/PriceTrigger.ts +++ b/src/models/PriceTrigger.ts @@ -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 diff --git a/src/store/settingsStore.ts b/src/store/settingsStore.ts index ba07723..3760fa3 100644 --- a/src/store/settingsStore.ts +++ b/src/store/settingsStore.ts @@ -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 diff --git a/src/views/base/priceTrigger/components/Item.vue b/src/views/base/priceTrigger/components/Item.vue index 512581f..02ce2c8 100644 --- a/src/views/base/priceTrigger/components/Item.vue +++ b/src/views/base/priceTrigger/components/Item.vue @@ -3,6 +3,10 @@

{{ usePrice(item.target_price) }}

+
+ محصول: + {{ productTitle }} +
جهت قیمت: {{ returnPriceCondition(item.price_condition) }} @@ -76,6 +80,7 @@
+
@@ -108,7 +113,7 @@ diff --git a/src/views/base/priceTrigger/composables/usePriceTriggerPage.ts b/src/views/base/priceTrigger/composables/usePriceTriggerPage.ts index 3252d5e..a4cab75 100644 --- a/src/views/base/priceTrigger/composables/usePriceTriggerPage.ts +++ b/src/views/base/priceTrigger/composables/usePriceTriggerPage.ts @@ -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 + products: Ref addTriggerVisible: Ref editId: Ref form: Ref @@ -27,6 +40,7 @@ export const PRICE_TRIGGER_PAGE_CONTEXT_KEY: InjectionKey([]) + const products = ref([]) const addTriggerVisible = ref(false) const editId = ref(undefined) const formRef = ref() @@ -37,12 +51,7 @@ export function usePriceTriggerPage() { lineHeight: '30px', }) - const form = ref({ - price_condition: '', - is_active: true, - action: '', - target_price: '', - }) + const form = ref(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,