feat(ui): standardize design system, modals, sidebar, and form inputs
- Implement unified design system tokens, 10px radius, and IRANSans typography - Modernize sidebar with Gemini-style collapsed expander, square 1:1 items, and elevated profile flyout - Standardize all modal dialogs with 16px padding, fixed header/footer, 16px medium title, and sticky bottom actions - Refactor DealFormView and CustomFieldsSection with standard UiField, UiInput, and UiSelect components - Add draggable bottom-left QuickCreate floating action button
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { api } from '@/lib/api';
|
||||
import JalaliDateInput from '@/components/JalaliDateInput.vue';
|
||||
import { UiField, UiInput, UiSelect } from '@/components/ui';
|
||||
import type {
|
||||
CustomFieldDefinitionDto,
|
||||
CustomFieldEntity,
|
||||
@@ -58,27 +59,28 @@ onMounted(load);
|
||||
<template>
|
||||
<div v-if="loading" class="muted">بارگذاری فیلدهای سفارشی…</div>
|
||||
<div v-else-if="fields.length" class="custom-block">
|
||||
<h3>فیلدهای سفارشی</h3>
|
||||
<div class="grid">
|
||||
<div v-for="field in fields" :key="field.id" class="field">
|
||||
<label :for="`cf-${field.key}`">
|
||||
{{ field.label }}
|
||||
<span v-if="field.required" class="req">*</span>
|
||||
</label>
|
||||
|
||||
<select
|
||||
<h3 class="custom-title">فیلدهای سفارشی</h3>
|
||||
<div class="crm-form-grid-2">
|
||||
<UiField
|
||||
v-for="field in fields"
|
||||
:key="field.id"
|
||||
:label="field.label"
|
||||
:required="field.required"
|
||||
:for-id="`cf-${field.key}`"
|
||||
>
|
||||
<UiSelect
|
||||
v-if="field.fieldType === 'select'"
|
||||
:id="`cf-${field.key}`"
|
||||
:value="String(displayValue(field.key))"
|
||||
@change="setValue(field.key, ($event.target as HTMLSelectElement).value)"
|
||||
:model-value="String(displayValue(field.key))"
|
||||
@update:model-value="setValue(field.key, $event)"
|
||||
>
|
||||
<option value="">انتخاب کنید</option>
|
||||
<option v-for="opt in field.options" :key="opt" :value="opt">
|
||||
{{ opt }}
|
||||
</option>
|
||||
</select>
|
||||
</UiSelect>
|
||||
|
||||
<label v-else-if="field.fieldType === 'boolean'" class="check">
|
||||
<label v-else-if="field.fieldType === 'boolean'" class="cf-check">
|
||||
<input
|
||||
:id="`cf-${field.key}`"
|
||||
type="checkbox"
|
||||
@@ -87,20 +89,18 @@ onMounted(load);
|
||||
setValue(field.key, ($event.target as HTMLInputElement).checked)
|
||||
"
|
||||
/>
|
||||
فعال
|
||||
<span>فعال</span>
|
||||
</label>
|
||||
|
||||
<input
|
||||
<UiInput
|
||||
v-else-if="field.fieldType === 'number'"
|
||||
:id="`cf-${field.key}`"
|
||||
type="number"
|
||||
:value="String(displayValue(field.key))"
|
||||
@input="
|
||||
:model-value="props.modelValue[field.key] != null ? Number(props.modelValue[field.key]) : ''"
|
||||
@update:model-value="
|
||||
setValue(
|
||||
field.key,
|
||||
($event.target as HTMLInputElement).value === ''
|
||||
? null
|
||||
: Number(($event.target as HTMLInputElement).value),
|
||||
$event === '' || $event == null ? null : Number($event),
|
||||
)
|
||||
"
|
||||
/>
|
||||
@@ -112,66 +112,55 @@ onMounted(load);
|
||||
@update:model-value="setValue(field.key, $event || null)"
|
||||
/>
|
||||
|
||||
<input
|
||||
<UiInput
|
||||
v-else
|
||||
:id="`cf-${field.key}`"
|
||||
type="text"
|
||||
:value="String(displayValue(field.key))"
|
||||
@input="setValue(field.key, ($event.target as HTMLInputElement).value)"
|
||||
:model-value="String(displayValue(field.key))"
|
||||
@update:model-value="setValue(field.key, $event)"
|
||||
/>
|
||||
</div>
|
||||
</UiField>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-block {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
padding-top: 0.4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--sp-3);
|
||||
padding-top: var(--sp-4);
|
||||
border-top: 1px dashed var(--line);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h3 {
|
||||
.custom-title {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.9rem;
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: var(--fw-semibold);
|
||||
color: var(--ink-secondary);
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
font-size: var(--fs-xs);
|
||||
}
|
||||
|
||||
.req {
|
||||
color: #b42318;
|
||||
}
|
||||
|
||||
.check {
|
||||
display: flex;
|
||||
.cf-check {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: 500;
|
||||
gap: var(--sp-2);
|
||||
min-height: var(--control-h-md);
|
||||
font-size: var(--fs-base);
|
||||
font-weight: var(--fw-medium);
|
||||
color: var(--ink);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select,
|
||||
input[type='text'],
|
||||
input[type='number'] {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
padding: 0.8rem 0.95rem;
|
||||
background: #fff;
|
||||
font: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.cf-check input[type='checkbox'] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: var(--accent);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user