Adopt ant-design-vue and a shared antdTheme for leads, pipeline, invoices, and shared form controls under strict single-page governance. Co-authored-by: Cursor <cursoragent@cursor.com>
134 lines
3.6 KiB
Vue
134 lines
3.6 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, ref, watch } from 'vue';
|
|
import {
|
|
Checkbox as ACheckbox,
|
|
Col as ACol,
|
|
FormItem as AFormItem,
|
|
Input as AInput,
|
|
InputNumber as AInputNumber,
|
|
Row as ARow,
|
|
Select as ASelect,
|
|
Spin as ASpin,
|
|
Typography,
|
|
} from 'ant-design-vue';
|
|
import { api } from '@/lib/api';
|
|
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
|
import type {
|
|
CustomFieldDefinitionDto,
|
|
CustomFieldEntity,
|
|
} from '@mento/shared';
|
|
|
|
const { Title: ATypographyTitle } = Typography;
|
|
|
|
const props = defineProps<{
|
|
entityType: CustomFieldEntity;
|
|
modelValue: Record<string, unknown>;
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: Record<string, unknown>];
|
|
}>();
|
|
|
|
const fields = ref<CustomFieldDefinitionDto[]>([]);
|
|
const loading = ref(false);
|
|
|
|
async function load() {
|
|
loading.value = true;
|
|
try {
|
|
fields.value = (
|
|
await api.get<CustomFieldDefinitionDto[]>('/custom-fields', {
|
|
params: { entityType: props.entityType, activeOnly: true },
|
|
})
|
|
).data;
|
|
} catch {
|
|
fields.value = [];
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
function setValue(key: string, value: unknown) {
|
|
emit('update:modelValue', { ...props.modelValue, [key]: value });
|
|
}
|
|
|
|
function displayValue(key: string) {
|
|
const v = props.modelValue[key];
|
|
if (v == null) return '';
|
|
if (typeof v === 'boolean') return v;
|
|
return String(v);
|
|
}
|
|
|
|
function selectOptions(field: CustomFieldDefinitionDto) {
|
|
return (field.options ?? []).map((opt) => ({ value: opt, label: opt }));
|
|
}
|
|
|
|
watch(
|
|
() => props.entityType,
|
|
() => {
|
|
void load();
|
|
},
|
|
);
|
|
|
|
onMounted(load);
|
|
</script>
|
|
|
|
<template>
|
|
<a-spin v-if="loading" />
|
|
<template v-else-if="fields.length">
|
|
<a-typography-title :level="5" :style="{ marginTop: '16px' }">
|
|
فیلدهای سفارشی
|
|
</a-typography-title>
|
|
<a-row :gutter="[16, 0]">
|
|
<a-col v-for="field in fields" :key="field.id" :xs="24" :md="12">
|
|
<a-form-item :label="field.label" :required="field.required">
|
|
<a-select
|
|
v-if="field.fieldType === 'select'"
|
|
:id="`cf-${field.key}`"
|
|
allow-clear
|
|
:value="String(displayValue(field.key)) || undefined"
|
|
:options="selectOptions(field)"
|
|
placeholder="انتخاب کنید"
|
|
style="width: 100%"
|
|
@update:value="setValue(field.key, $event ?? '')"
|
|
/>
|
|
|
|
<a-checkbox
|
|
v-else-if="field.fieldType === 'boolean'"
|
|
:id="`cf-${field.key}`"
|
|
:checked="Boolean(props.modelValue[field.key])"
|
|
@update:checked="setValue(field.key, $event)"
|
|
>
|
|
فعال
|
|
</a-checkbox>
|
|
|
|
<a-input-number
|
|
v-else-if="field.fieldType === 'number'"
|
|
:id="`cf-${field.key}`"
|
|
:value="
|
|
props.modelValue[field.key] != null
|
|
? Number(props.modelValue[field.key])
|
|
: undefined
|
|
"
|
|
style="width: 100%"
|
|
@update:value="setValue(field.key, $event == null ? null : Number($event))"
|
|
/>
|
|
|
|
<JalaliDateInput
|
|
v-else-if="field.fieldType === 'date'"
|
|
:id="`cf-${field.key}`"
|
|
:model-value="String(displayValue(field.key))"
|
|
@update:model-value="setValue(field.key, $event || null)"
|
|
/>
|
|
|
|
<a-input
|
|
v-else
|
|
:id="`cf-${field.key}`"
|
|
:value="String(displayValue(field.key))"
|
|
@update:value="setValue(field.key, $event)"
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
</a-row>
|
|
</template>
|
|
</template>
|