Unify the web UI on shared controls and let pipeline columns be managed on the board.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -37,11 +37,29 @@ type MenuKey = 'person' | 'company' | 'owner' | 'currency' | '';
|
||||
type ContactKind = 'person' | 'company';
|
||||
type DealHealth = 'normal' | 'stale' | 'rotten';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
embedded?: boolean;
|
||||
dealId?: string;
|
||||
initialPipelineId?: string;
|
||||
initialStageId?: string;
|
||||
}>(),
|
||||
{ embedded: false },
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
saved: [];
|
||||
cancel: [];
|
||||
}>();
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const auth = useAuthStore();
|
||||
const id = computed(() => (typeof route.params.id === 'string' ? route.params.id : ''));
|
||||
const isNew = computed(() => route.name === 'deal-new');
|
||||
const id = computed(() => {
|
||||
if (props.embedded) return props.dealId ?? '';
|
||||
return typeof route.params.id === 'string' ? route.params.id : '';
|
||||
});
|
||||
const isNew = computed(() => (props.embedded ? !props.dealId : route.name === 'deal-new'));
|
||||
const pageTitle = computed(() => (isNew.value ? 'ایجاد معامله جدید' : 'ویرایش معامله'));
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -188,7 +206,8 @@ async function load() {
|
||||
error.value = '';
|
||||
try {
|
||||
const queryPipelineId =
|
||||
typeof route.query.pipelineId === 'string' ? route.query.pipelineId : undefined;
|
||||
props.initialPipelineId ||
|
||||
(typeof route.query.pipelineId === 'string' ? route.query.pipelineId : undefined);
|
||||
const [peopleRes, companiesRes, pipelinesRes, productsRes, orgRes, usersRes] =
|
||||
await Promise.all([
|
||||
api.get<{ items: PersonDto[] }>('/people', { params: { limit: 100 } }),
|
||||
@@ -254,12 +273,10 @@ async function load() {
|
||||
companyId.value = route.query.companyId;
|
||||
if (!personId.value) contactKind.value = 'company';
|
||||
}
|
||||
if (
|
||||
typeof route.query.stageId === 'string' &&
|
||||
route.query.stageId &&
|
||||
stages.value.some((s) => s.id === route.query.stageId)
|
||||
) {
|
||||
stageId.value = route.query.stageId;
|
||||
const incomingStage = props.initialStageId ||
|
||||
(typeof route.query.stageId === 'string' ? route.query.stageId : '');
|
||||
if (incomingStage && stages.value.some((s) => s.id === incomingStage)) {
|
||||
stageId.value = incomingStage;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -318,6 +335,10 @@ function validate() {
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
if (props.embedded) {
|
||||
emit('cancel');
|
||||
return;
|
||||
}
|
||||
void router.push({ name: 'pipeline' });
|
||||
}
|
||||
|
||||
@@ -338,6 +359,10 @@ async function submit() {
|
||||
} catch {
|
||||
/* deal saved; follow-up is optional */
|
||||
}
|
||||
if (props.embedded) {
|
||||
emit('saved');
|
||||
return;
|
||||
}
|
||||
await router.replace({ name: 'deal-detail', params: { id: data.id } });
|
||||
} else {
|
||||
await api.patch(`/deals/${id.value}`, {
|
||||
@@ -347,6 +372,10 @@ async function submit() {
|
||||
if (stageId.value) {
|
||||
await api.patch(`/deals/${id.value}/move`, { stageId: stageId.value });
|
||||
}
|
||||
if (props.embedded) {
|
||||
emit('saved');
|
||||
return;
|
||||
}
|
||||
await router.push({ name: 'pipeline' });
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -374,8 +403,17 @@ onMounted(load);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="page" @click="closeMenus">
|
||||
<header class="desktop-head">
|
||||
<section class="page" :class="{ embedded }" @click="closeMenus">
|
||||
<div v-if="embedded" class="embed-actions">
|
||||
<button class="btn primary" type="button" :disabled="saving" @click="submit">
|
||||
{{ saving ? 'در حال ذخیره…' : 'ذخیره معامله' }}
|
||||
</button>
|
||||
<button class="btn cancel" type="button" :disabled="saving" @click="cancel">
|
||||
انصراف
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<header v-if="!embedded" class="desktop-head">
|
||||
<div class="title-block">
|
||||
<span class="doc-ico">
|
||||
<UiIcon name="document" :size="22" />
|
||||
@@ -414,7 +452,7 @@ onMounted(load);
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<header class="mobile-head">
|
||||
<header v-if="!embedded" class="mobile-head">
|
||||
<button class="icon-btn" type="button" aria-label="ذخیره" :disabled="saving" @click="submit">
|
||||
<UiIcon name="check-plain" :size="22" />
|
||||
</button>
|
||||
@@ -748,7 +786,7 @@ onMounted(load);
|
||||
</p>
|
||||
</form>
|
||||
|
||||
<footer class="mobile-save">
|
||||
<footer v-if="!embedded" class="mobile-save">
|
||||
<button class="btn outline" type="button" :disabled="saving" @click="cancel">انصراف</button>
|
||||
<button class="btn primary" type="button" :disabled="saving || loading" @click="submit">
|
||||
{{ saving ? 'در حال ذخیره…' : 'ذخیره معامله' }}
|
||||
@@ -765,6 +803,16 @@ onMounted(load);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.page.embedded {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.embed-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.desktop-head {
|
||||
display: none;
|
||||
justify-content: space-between;
|
||||
|
||||
Reference in New Issue
Block a user