Add org-scoped activity priority labels/colors and apply them across forms, boards, and routines.
Also fix activity leave-without-save confirm, kanban due-date labels with tone-colored peek chevron, and rename the types manager button. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Organization" ADD COLUMN "activityPriorities" JSONB NOT NULL DEFAULT '{}';
|
||||
@@ -162,6 +162,8 @@ model Organization {
|
||||
enabledCurrencies String[] @default(["IRR", "USD", "EUR"])
|
||||
/// ماژولهای فعال منو برای این محیط
|
||||
enabledModules String[] @default([])
|
||||
/// برچسب/رنگ اولویت فعالیت و روتین (کلیدهای enum ثابت؛ فقط ظاهر)
|
||||
activityPriorities Json @default("{}")
|
||||
/// تاریخ قطع بایگانی اطلاعات قبل (شروع روز انتخابشده)؛ null = بدون فیلتر
|
||||
priorArchiveEnabledAt DateTime?
|
||||
isActive Boolean @default(true)
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
Res,
|
||||
StreamableFile,
|
||||
@@ -40,6 +41,7 @@ import {
|
||||
ReorderActivityTypesDto,
|
||||
UpdateActivityBoardDto,
|
||||
UpdateActivityDto,
|
||||
UpdateActivityPrioritiesDto,
|
||||
UpdateActivityStageDto,
|
||||
UpdateActivityTypeDto,
|
||||
} from './dto';
|
||||
@@ -139,6 +141,18 @@ export class ActivitiesController {
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
@Get('priorities')
|
||||
listPriorities(@CurrentUser() user: Actor) {
|
||||
return this.typesService.listPriorities(user.organizationId);
|
||||
}
|
||||
|
||||
@Put('priorities')
|
||||
@UseGuards(RolesGuard)
|
||||
@Roles('owner', 'admin')
|
||||
updatePriorities(@CurrentUser() user: Actor, @Body() dto: UpdateActivityPrioritiesDto) {
|
||||
return this.typesService.updatePriorities(user.organizationId, dto.items);
|
||||
}
|
||||
|
||||
@Get('types')
|
||||
listTypes(@CurrentUser() user: Actor) {
|
||||
return this.typesService.list(user.organizationId);
|
||||
|
||||
@@ -6,6 +6,8 @@ import type {
|
||||
ActivityFieldValue,
|
||||
ActivityFieldValues,
|
||||
ActivityParticipantsValue,
|
||||
ActivityPriority,
|
||||
ActivityPrioritySetting,
|
||||
ActivityStageColor,
|
||||
ActivityType,
|
||||
ActivityTypeDefDto,
|
||||
@@ -17,8 +19,11 @@ import type {
|
||||
import {
|
||||
ACTIVITY_FIELD_TYPES,
|
||||
ACTIVITY_FIELD_TYPES_WITH_OPTIONS,
|
||||
ACTIVITY_PRIORITY_KEYS,
|
||||
ACTIVITY_STAGE_COLORS,
|
||||
ACTIVITY_TYPE_ICONS,
|
||||
DEFAULT_ACTIVITY_PRIORITY_SETTINGS,
|
||||
parseActivityPrioritySettings,
|
||||
} from '@mento/shared';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
@@ -619,6 +624,43 @@ export class ActivityTypesService {
|
||||
: 'green';
|
||||
}
|
||||
|
||||
async listPriorities(organizationId: string): Promise<ActivityPrioritySetting[]> {
|
||||
const row = await this.prisma.organization.findUnique({
|
||||
where: { id: organizationId },
|
||||
select: { activityPriorities: true },
|
||||
});
|
||||
return parseActivityPrioritySettings(row?.activityPriorities);
|
||||
}
|
||||
|
||||
async updatePriorities(
|
||||
organizationId: string,
|
||||
items: Array<{ key: ActivityPriority; label: string; color: string }>,
|
||||
): Promise<ActivityPrioritySetting[]> {
|
||||
const byKey = new Map(items.map((i) => [i.key, i]));
|
||||
if (ACTIVITY_PRIORITY_KEYS.some((k) => !byKey.has(k))) {
|
||||
throw new BadRequestException('هر پنج سطح اولویت باید ارسال شوند');
|
||||
}
|
||||
const hex = /^#[0-9a-fA-F]{6}$/;
|
||||
const stored: Record<string, { label: string; color: string }> = {};
|
||||
for (const key of ACTIVITY_PRIORITY_KEYS) {
|
||||
const item = byKey.get(key)!;
|
||||
const label = item.label.trim().slice(0, 40);
|
||||
const color = item.color.trim();
|
||||
if (!label) throw new BadRequestException(`عنوان اولویت «${key}» خالی است`);
|
||||
if (!hex.test(color)) throw new BadRequestException(`رنگ اولویت «${key}» نامعتبر است`);
|
||||
const def = DEFAULT_ACTIVITY_PRIORITY_SETTINGS.find((d) => d.key === key)!;
|
||||
// فقط تفاوت با پیشفرض را نگه میداریم تا JSON جمعوجور بماند
|
||||
if (label !== def.label || color.toLowerCase() !== def.color.toLowerCase()) {
|
||||
stored[key] = { label, color };
|
||||
}
|
||||
}
|
||||
await this.prisma.organization.update({
|
||||
where: { id: organizationId },
|
||||
data: { activityPriorities: stored as Prisma.InputJsonValue },
|
||||
});
|
||||
return parseActivityPrioritySettings(stored);
|
||||
}
|
||||
|
||||
private mapError(e: unknown) {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') {
|
||||
return new BadRequestException('نوع فعالیتی با این نام وجود دارد');
|
||||
|
||||
@@ -9,11 +9,13 @@ import {
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Matches,
|
||||
Max,
|
||||
MaxLength,
|
||||
Min,
|
||||
MinLength,
|
||||
ValidateIf,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import type { ActivityFieldDef, ActivityFieldValues, ActivityTypeIcon } from '@mento/shared';
|
||||
import { ACTIVITY_TYPE_ICONS } from '@mento/shared';
|
||||
@@ -501,3 +503,26 @@ export class UpdateActivityDto {
|
||||
@IsIn(['creator', 'assignees'])
|
||||
editPolicy?: 'creator' | 'assignees';
|
||||
}
|
||||
|
||||
const PRIORITY_KEYS = ['very_low', 'low', 'medium', 'high', 'very_high'] as const;
|
||||
|
||||
export class ActivityPriorityItemDto {
|
||||
@IsIn(PRIORITY_KEYS)
|
||||
key!: (typeof PRIORITY_KEYS)[number];
|
||||
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(40)
|
||||
label!: string;
|
||||
|
||||
@IsString()
|
||||
@Matches(/^#[0-9a-fA-F]{6}$/)
|
||||
color!: string;
|
||||
}
|
||||
|
||||
export class UpdateActivityPrioritiesDto {
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ActivityPriorityItemDto)
|
||||
items!: ActivityPriorityItemDto[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user