Add desk-style task workflow on Activities and Project boards.

Managers can switch my desk vs team, reassign activities, and filter project cards to their work; assignees can see and move cards.
This commit is contained in:
2026-09-28 12:28:14 +03:30
parent 8effd4cbc6
commit 32e27d45f7
13 changed files with 303 additions and 50 deletions
+75 -5
View File
@@ -28,9 +28,12 @@ import type {
PaginatedResponse,
} from '@mento/shared';
import { brand } from '@mento/shared';
import { useAuthStore } from '@/stores/auth';
import { canViewAllRecords } from '@/lib/roles';
type TypeFilter = 'all' | ActivityType;
type UiStatus = 'done' | 'progress' | 'planned' | 'overdue' | 'canceled';
type DeskScope = 'mine' | 'team';
const TABS: Array<{ key: ActivityFilter; label: string }> = [
{ key: 'today', label: 'امروز' },
@@ -40,6 +43,11 @@ const TABS: Array<{ key: ActivityFilter; label: string }> = [
{ key: 'all', label: 'همه' },
];
const DESK_TABS: Array<{ key: DeskScope; label: string }> = [
{ key: 'mine', label: 'میز من' },
{ key: 'team', label: 'همه تیم' },
];
const TYPE_FILTERS: Array<{ key: TypeFilter; label: string }> = [
{ key: 'all', label: 'همه انواع' },
{ key: 'call', label: 'تماس' },
@@ -52,6 +60,8 @@ const PAGE_SIZE = 6;
const route = useRoute();
const router = useRouter();
const auth = useAuthStore();
const showDeskTabs = computed(() => canViewAllRecords(auth.user?.role));
const filter = computed<ActivityFilter>(() => {
const value = typeof route.query.filter === 'string' ? route.query.filter : 'today';
@@ -61,6 +71,11 @@ const filter = computed<ActivityFilter>(() => {
return 'today';
});
const desk = computed<DeskScope>(() => {
if (!showDeskTabs.value) return 'mine';
return route.query.desk === 'team' ? 'team' : 'mine';
});
const page = computed(() => {
const raw = Number(route.query.page);
return Number.isFinite(raw) && raw > 0 ? raw : 1;
@@ -178,10 +193,11 @@ function subtitle(item: ActivityDto) {
async function loadCounts() {
const keys: ActivityFilter[] = ['today', 'overdue', 'upcoming', 'done', 'all'];
const deskParam = showDeskTabs.value ? desk.value : undefined;
const results = await Promise.all(
keys.map((key) =>
api.get<PaginatedResponse<ActivityDto>>('/activities', {
params: { filter: key, limit: 1 },
params: { filter: key, limit: 1, desk: deskParam },
}),
),
);
@@ -203,6 +219,7 @@ async function load() {
q: q.value || undefined,
page: page.value,
limit: PAGE_SIZE,
desk: showDeskTabs.value ? desk.value : undefined,
},
});
data.value = res;
@@ -217,6 +234,17 @@ async function load() {
}
}
function setDesk(next: DeskScope) {
void router.replace({
name: 'activities',
query: {
...route.query,
desk: next === 'mine' ? undefined : next,
page: undefined,
},
});
}
function setFilter(next: ActivityFilter) {
filterOpen.value = false;
void router.replace({
@@ -318,10 +346,11 @@ function onImportSettled() {
}
watch(
() => [route.query.filter, route.query.q, route.query.page],
() => [route.query.filter, route.query.q, route.query.page, route.query.desk],
() => {
q.value = typeof route.query.q === 'string' ? route.query.q : '';
void load();
void loadCounts();
},
);
@@ -348,12 +377,12 @@ onUnmounted(() => {
<nav class="crumbs" aria-label="مسیر">
<RouterLink :to="{ name: 'home' }">خانه</RouterLink>
<span>‹</span>
<span>فعالیت‌ها</span>
<span>میز کار</span>
</nav>
<UiPageHeader
title="فعالیت‌ها"
lead="تماس، جلسه و وظیفه — امروز و عقب‌افتاده‌ها یکجا."
title="میز کار و فعالیت‌ها"
lead="کارهای امروز، ارجاع به همکار و پیگیری وضعیت."
>
<template #actions>
<UiButton variant="ghost" type="button" :disabled="busy" @click.stop="exportExcel">
@@ -387,6 +416,20 @@ onUnmounted(() => {
</template>
</UiPageHeader>
<div v-if="showDeskTabs" class="desk-tabs" role="tablist" aria-label="محدوده میز کار">
<button
v-for="tab in DESK_TABS"
:key="tab.key"
type="button"
role="tab"
:class="{ on: desk === tab.key }"
:aria-selected="desk === tab.key"
@click="setDesk(tab.key)"
>
{{ tab.label }}
</button>
</div>
<div class="tabs" role="tablist" aria-label="فیلتر فعالیت">
<button
v-for="tab in TABS"
@@ -763,6 +806,33 @@ h1 {
width: min(100%, 320px);
}
.desk-tabs {
display: inline-flex;
gap: 0.25rem;
padding: 0.2rem;
border-radius: 12px;
background: #f1f5f9;
width: fit-content;
}
.desk-tabs button {
border: 0;
background: transparent;
color: #64748b;
font: inherit;
font-size: 0.86rem;
font-weight: 700;
padding: 0.45rem 0.85rem;
border-radius: 10px;
cursor: pointer;
}
.desk-tabs button.on {
background: #fff;
color: #0f766e;
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
}
.tabs {
display: flex;
gap: 0.2rem;