fix(web): make kanban column titles readable

Widen board columns and keep counts inside the add button so stage and card titles have room.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-28 17:24:58 +03:30
co-authored by Cursor
parent 463a1652a6
commit 2cc2d358e4
4 changed files with 42 additions and 51 deletions
@@ -8,8 +8,9 @@ withDefaults(
defineProps<{
accent: PipelineAccent;
label?: string;
count?: string;
}>(),
{ label: 'افزودن معامله' },
{ label: 'افزودن معامله', count: '' },
);
const emit = defineEmits<{
@@ -21,11 +22,18 @@ const emit = defineEmits<{
<a-button
type="dashed"
block
:style="{ color: PIPELINE_ACCENT[accent].text }"
:style="{
color: PIPELINE_ACCENT[accent].text,
display: 'flex',
justifyContent: 'space-between',
}"
:aria-label="label"
@click="emit('add')"
>
<template #icon><PlusOutlined /></template>
{{ label }}
<span :style="{ display: 'inline-flex', alignItems: 'center', gap: '8px' }">
<PlusOutlined />
{{ label }}
</span>
<span v-if="count">{{ count }}</span>
</a-button>
</template>
+9 -21
View File
@@ -18,11 +18,7 @@ import {
} from 'ant-design-vue';
import { userAvatarSrc } from '@/lib/userAvatars';
import type { PipelineAccent, PipelineDeal, PipelineUiCopy } from '@/lib/pipeline-board';
import {
DEAL_BOARD_COPY,
formatCurrencyParts,
PIPELINE_ACCENT,
} from '@/lib/pipeline-board';
import { DEAL_BOARD_COPY, formatCurrencyParts } from '@/lib/pipeline-board';
const props = withDefaults(
defineProps<{
@@ -54,7 +50,6 @@ const showAmount = computed(
const money = computed(() =>
formatCurrencyParts(props.deal.amount ?? 0, props.deal.currencyUnit ?? 'ریال'),
);
const colors = computed(() => PIPELINE_ACCENT[props.accent]);
const assigneeTitle = computed(() =>
(props.deal.assigneeNames ?? []).filter(Boolean).join('، ') || 'مسئول',
);
@@ -113,24 +108,17 @@ const cardStyle = computed(() => ({
<CheckOutlined v-if="selected" />
</template>
</a-button>
<a-avatar
v-else
:style="{
background: colors.soft,
color: colors.text,
flexShrink: 0,
fontWeight: 700,
fontSize: `${token.fontSizeSM}px`,
}"
>
{{ deal.initials }}
</a-avatar>
<div :style="{ flex: 1, minWidth: 0 }">
<a-typography-text
strong
ellipsis
:style="{ display: 'block', margin: 0, lineHeight: 1.35, cursor: 'pointer' }"
:style="{
display: 'block',
margin: 0,
lineHeight: 1.35,
cursor: 'pointer',
fontSize: `${token.fontSizeSM}px`,
fontWeight: 600,
}"
:title="deal.title"
@click.stop="selectMode ? emit('toggleSelect', deal.id) : emit('open', deal.id)"
>
@@ -131,8 +131,8 @@ function submitAdd() {
<div
v-if="canManage"
:style="{
flex: '0 0 280px',
width: '280px',
flex: '0 0 310px',
width: '310px',
alignSelf: 'stretch',
display: 'flex',
alignItems: 'flex-start',
@@ -1,10 +1,6 @@
<script setup lang="ts">
import { computed, nextTick, ref } from 'vue';
import {
HolderOutlined,
MoreOutlined,
PlusOutlined,
} from '@ant-design/icons-vue';
import { HolderOutlined, MoreOutlined } from '@ant-design/icons-vue';
import {
Button as AButton,
Dropdown as ADropdown,
@@ -17,7 +13,7 @@ import {
import AddDealButton from './AddDealButton.vue';
import DealCard from './DealCard.vue';
import type { PipelineDeal, PipelineStage, PipelineUiCopy } from '@/lib/pipeline-board';
import { DEAL_BOARD_COPY, PIPELINE_ACCENT, toFaItemCount } from '@/lib/pipeline-board';
import { DEAL_BOARD_COPY, PIPELINE_ACCENT } from '@/lib/pipeline-board';
const props = withDefaults(
defineProps<{
@@ -67,8 +63,8 @@ const columnStyle = computed(() => ({
display: 'flex',
flexDirection: 'column' as const,
gap: '12px',
flex: '0 0 280px',
width: '280px',
flex: '0 0 310px',
width: '310px',
alignSelf: 'stretch' as const,
minHeight: 0,
padding: 0,
@@ -154,15 +150,21 @@ function onColumnDragStart(event: DragEvent) {
}
const menuItems = computed(() => [
{ key: 'add', label: 'افزودن' },
{ key: 'rename', label: 'تغییر نام' },
{ key: 'right', label: 'انتقال به راست', disabled: props.isFirst },
{ key: 'left', label: 'انتقال به چپ', disabled: props.isLast },
{ key: 'delete', label: 'حذف ستون', danger: true },
]);
const itemCountLabel = computed(() =>
new Intl.NumberFormat('fa-IR').format(props.stage.total ?? props.stage.deals.length),
);
function onMenuClick({ key }: { key: string | number }) {
menuOpen.value = false;
if (key === 'rename') startRename();
if (key === 'add') emit('addDeal', props.stage.id);
else if (key === 'rename') startRename();
else if (key === 'right') emit('move', props.stage.id, -1);
else if (key === 'left') emit('move', props.stage.id, 1);
else if (key === 'delete') emit('delete', props.stage.id);
@@ -224,12 +226,13 @@ function onMenuClick({ key }: { key: string | number }) {
/>
<a-typography-text
v-else
strong
ellipsis
:style="{
flex: 1,
minWidth: 0,
margin: 0,
fontSize: `${token.fontSizeSM}px`,
fontWeight: 600,
cursor: canManage ? 'text' : 'default',
}"
:title="canManage ? 'برای تغییر نام کلیک کنید' : stage.title"
@@ -242,10 +245,6 @@ function onMenuClick({ key }: { key: string | number }) {
{{ stage.badge }}
</a-tag>
<a-typography-text type="secondary" :style="{ whiteSpace: 'nowrap', flexShrink: 0 }">
{{ toFaItemCount(stage.total ?? stage.deals.length, copy.itemNoun) }}
</a-typography-text>
<a-dropdown
v-if="canManage"
v-model:open="menuOpen"
@@ -259,15 +258,6 @@ function onMenuClick({ key }: { key: string | number }) {
<a-menu :items="menuItems" @click="onMenuClick" />
</template>
</a-dropdown>
<a-button
type="text"
:aria-label="copy.addItemAria"
:style="{ color: accent.solid }"
@click="emit('addDeal', stage.id)"
>
<template #icon><PlusOutlined /></template>
</a-button>
</div>
<div role="list" :aria-label="stage.title" :style="bodyStyle">
@@ -311,7 +301,12 @@ function onMenuClick({ key }: { key: string | number }) {
</div>
<div :style="{ padding: '0 12px 12px', flexShrink: 0 }">
<AddDealButton :accent="stage.color" :label="copy.addItem" @add="emit('addDeal', stage.id)" />
<AddDealButton
:accent="stage.color"
:label="copy.addItem"
:count="itemCountLabel"
@add="emit('addDeal', stage.id)"
/>
</div>
</section>
</template>