Give automations a visual chain with conditions and SMS, and match the pipeline board to the sales mockup.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-09 13:12:13 +03:30
co-authored by Cursor
parent 48d3469eea
commit 3ec87ff912
17 changed files with 2355 additions and 410 deletions
+33 -34
View File
@@ -7,6 +7,7 @@ import { formatJalaliNumeric } from '@/lib/jalali';
import PipelineToolbar from '@/components/pipeline/PipelineToolbar.vue';
import type { PipelineViewMode } from '@/components/pipeline/PipelineToolbar.vue';
import PipelineBoard from '@/components/pipeline/PipelineBoard.vue';
import PipelineList from '@/components/pipeline/PipelineList.vue';
import DealCardOverlay from '@/components/pipeline/DealCardOverlay.vue';
import type { PipelineDeal, PipelineStage } from '@/lib/pipeline-board';
import {
@@ -45,7 +46,7 @@ const moving = ref(false);
const pipelineOptions = computed(() =>
pipelines.value.map((p) => ({
id: p.id,
name: p.isDefault ? `${p.name} (پیش‌فرض)` : p.name,
name: p.name,
})),
);
@@ -264,25 +265,14 @@ defineExpose({ updateDealStage: applyUpdateDealStage });
:selected-pipeline-id="selectedPipelineId"
:view-mode="viewMode"
:search="search"
:can-create="canCreatePipeline"
@update:view-mode="viewMode = $event"
@update:search="search = $event"
@update:selected-pipeline-id="selectedPipelineId = $event"
@filter="onFilter"
@create="showCreate = !showCreate"
/>
<div v-if="canCreatePipeline" class="create-row">
<button
class="btn-ghost-sm"
type="button"
@click="showCreate = !showCreate"
>
کاریز جدید
</button>
<button class="btn-ghost-sm" type="button" :disabled="loading" @click="load">
بروزرسانی
</button>
</div>
<form
v-if="showCreate && canCreatePipeline"
class="create-box"
@@ -305,12 +295,15 @@ defineExpose({ updateDealStage: applyUpdateDealStage });
<p v-if="error" class="error">{{ error }}</p>
<p v-else-if="loading && !stages.length" class="muted">در حال بارگذاری کاریز…</p>
<div v-if="viewMode === 'list'" class="list-placeholder">
نمای لیست معاملات
</div>
<PipelineList
v-if="viewMode === 'list' && (!loading || stages.length)"
:stages="visibleStages"
@open-deal="onOpenDeal"
@menu-deal="onMenuDeal"
/>
<PipelineBoard
v-else-if="stages.length"
v-else-if="viewMode === 'kanban' && stages.length"
:stages="visibleStages"
:dragging-id="draggingId"
:drop-stage-id="dropStageId"
@@ -345,16 +338,13 @@ defineExpose({ updateDealStage: applyUpdateDealStage });
<style scoped>
.page {
display: grid;
gap: 0.55rem;
gap: 0.15rem;
min-width: 0;
max-width: 100%;
direction: rtl;
text-align: right;
}
.create-row {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
container-type: inline-size;
container-name: pipeline;
}
.btn-ghost-sm {
@@ -373,10 +363,12 @@ defineExpose({ updateDealStage: applyUpdateDealStage });
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
min-width: 0;
}
.create-box input {
flex: 1 1 180px;
min-width: 0;
border: 1px solid var(--line);
border-radius: 12px;
padding: 0.65rem 0.85rem;
@@ -384,21 +376,28 @@ defineExpose({ updateDealStage: applyUpdateDealStage });
font: inherit;
}
.list-placeholder {
margin-top: 0.5rem;
padding: 2.5rem 1rem;
text-align: center;
color: var(--muted);
background: var(--surface);
border: 1px dashed var(--line);
border-radius: var(--radius);
font-weight: 700;
@container pipeline (max-width: 560px) {
.create-box {
flex-direction: column;
align-items: stretch;
}
.create-box input,
.create-box .btn,
.create-box .btn-ghost-sm {
width: 100%;
}
}
.muted {
color: var(--muted);
}
.error {
color: var(--danger);
font-weight: 600;
}
.menu-pop {
position: fixed;
inset-inline-start: 1rem;