{{
@@ -57,8 +58,12 @@
- У этой задачи пока нет активностей
- Для этого документа еще нет активности
+ У этой задачи пока нет активностей
+ Для этого документа еще нет активности
@@ -76,7 +81,10 @@ import { useWorkspaceStore } from 'stores/workspace-store';
import aiplan from 'src/utils/aiplan';
import { formatDateTime } from 'src/utils/time';
import { setIntervalFunction } from 'src/utils/helpers';
-import { getIcon, getHistoryText } from 'src/utils/strings';
+import {
+ getIcon,
+ entityActivityRenderer,
+} from 'src/components/activity/entityActivity';
//components - icon
import AvatarImage from './AvatarImage.vue';
@@ -86,10 +94,10 @@ defineProps({
type: Object,
required: true,
},
- type: {
- type: String,
- required: true,
- }
+ type: {
+ type: String,
+ required: true,
+ },
});
const emits = defineEmits<{
diff --git a/src/components/activity/entityActivity/index.ts b/src/components/activity/entityActivity/index.ts
new file mode 100644
index 00000000..eb44c96a
--- /dev/null
+++ b/src/components/activity/entityActivity/index.ts
@@ -0,0 +1,69 @@
+import { HistoryEntry, HistoryRenderer, ProjectBase } from './types';
+
+import { renderBlocks } from './renders/blocks';
+import { renderAssignees, renderWatchers } from './renders/assignees';
+import {
+ renderCompletedAt,
+ renderStartDate,
+ renderTargetDate,
+} from './renders/dates';
+import {
+ renderLinked,
+ renderLink,
+ renderLinkUrl,
+ renderLinkTitle,
+} from './renders/links';
+import { renderAttachment } from './renders/attachments';
+import { renderParent, renderBlocking, renderSubIssue } from './renders/issues';
+import { renderComment } from './renders/comments';
+import { renderLabels, renderLabel } from './renders/labels';
+import { renderSprint } from './renders/sprints';
+import { renderDoc, renderSeqId, renderDescription } from './renders/docs';
+import { renderIssueTransfer, renderProject } from './renders/projects';
+import { renderPriority, renderDefault } from './renders/priority';
+
+const fieldRenderers: Record = {
+ blocks: renderBlocks,
+ assignees: renderAssignees,
+ watchers: renderWatchers,
+ completed_at: renderCompletedAt,
+ start_date: renderStartDate,
+ target_date: renderTargetDate,
+ linked: renderLinked,
+ link: renderLink,
+ link_url: renderLinkUrl,
+ link_title: renderLinkTitle,
+ attachment: renderAttachment,
+ parent: renderParent,
+ blocking: renderBlocking,
+ sub_issue: renderSubIssue,
+ comment: renderComment,
+ labels: renderLabels,
+ label: renderLabel,
+ sprint: renderSprint,
+ doc: renderDoc,
+ seq_id: renderSeqId,
+ description: renderDescription,
+ issue_transfer: renderIssueTransfer,
+ project: renderProject,
+ priority: renderPriority,
+};
+
+export function entityActivityRenderer(
+ m: HistoryEntry,
+ wsProjects: ProjectBase[],
+): string {
+ const renderer = fieldRenderers[m.field];
+
+ if (renderer) {
+ const result = renderer(m, wsProjects);
+ if (result) return result;
+ }
+
+ return renderDefault(m);
+}
+
+export { getIcon } from './utils/getIcon';
+
+export type { HistoryEntry, HistoryRenderer } from './types';
+
diff --git a/src/components/activity/entityActivity/renders/actionTranslations.ts b/src/components/activity/entityActivity/renders/actionTranslations.ts
new file mode 100644
index 00000000..1a3aedd5
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/actionTranslations.ts
@@ -0,0 +1,65 @@
+interface ActionTranslation {
+ add: string;
+ remove: string;
+}
+
+export const HISTORY_ACTIONS: Record = {
+ blocks: {
+ add: 'добавил(-а)',
+ remove: 'удалил(-а)',
+ },
+ assignees: {
+ add: 'добавил(-а)',
+ remove: 'убрал(-а)',
+ },
+ watchers: {
+ add: 'добавил(-а)',
+ remove: 'убрал(-а)',
+ },
+ labels: {
+ add: 'добавил(-а) тег',
+ remove: 'удалил(-а) тег',
+ },
+ label: {
+ add: 'добавил(-а) тег',
+ remove: 'удалил(-а) тег',
+ },
+ linked: {
+ add: 'добавил(-а) связь с задачей',
+ remove: 'убрал(-а) связь с задачей',
+ },
+ sub_issue: {
+ add: 'добавил(-а) подзадачу',
+ remove: 'удалил(-а) подзадачу',
+ },
+ parent: {
+ add: 'заблокировал(-а) задачу',
+ remove: 'снял(-а)',
+ },
+ blocking: {
+ add: 'заблокировал(-а) задачу',
+ remove: 'снял(-а)',
+ },
+ link: {
+ add: 'добавил(-а) ссылку',
+ remove: 'удалил(-а) ссылку',
+ },
+ attachment: {
+ add: 'добавил(-а)',
+ remove: 'удалил(-а)',
+ },
+ doc: {
+ add: 'добавил(-а) вложенный документ',
+ remove: 'убрал(-а) вложенный документ',
+ },
+ sprint: {
+ add: 'добавил(-а) спринт',
+ remove: 'убрал(-а) спринт',
+ },
+};
+
+export function translateHistoryAction(field: string, isAdd: boolean): string {
+ const actions = HISTORY_ACTIONS[field] || HISTORY_ACTIONS.blocks;
+ return isAdd ? actions.add : actions.remove;
+}
+
diff --git a/src/components/activity/entityActivity/renders/assignees.ts b/src/components/activity/entityActivity/renders/assignees.ts
new file mode 100644
index 00000000..a6bfdcf1
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/assignees.ts
@@ -0,0 +1,24 @@
+import { HistoryEntry } from '../types';
+import aiplan from 'src/utils/aiplan';
+import { translateHistoryAction } from './actionTranslations';
+
+export function renderAssignees(m: HistoryEntry): string {
+ if (m.old_value == null || m.old_value === '') {
+ return `${translateHistoryAction('assignees', true)} ${aiplan.ru_field(m)} ${aiplan.UserName(m.new_entity_detail).join(' ')}`;
+ }
+ if (m.new_value === '') {
+ return `${translateHistoryAction('assignees', false)} ${aiplan.ru_field(m)} ${aiplan.UserName(m.old_entity_detail).join(' ')}`;
+ }
+ return `${aiplan.ru_verb(m)} ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
+}
+
+export function renderWatchers(m: HistoryEntry): string {
+ if (m.old_value == null || m.old_value === '') {
+ return `${translateHistoryAction('watchers', true)} ${aiplan.ru_field(m)} ${aiplan.UserName(m.new_entity_detail).join(' ')}`;
+ }
+ if (m.new_value === '') {
+ return `${translateHistoryAction('watchers', false)} ${aiplan.ru_field(m)} ${aiplan.UserName(m.old_entity_detail).join(' ')}`;
+ }
+ return `${aiplan.ru_verb(m)} ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
+}
+
diff --git a/src/components/activity/entityActivity/renders/attachments.ts b/src/components/activity/entityActivity/renders/attachments.ts
new file mode 100644
index 00000000..10416f82
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/attachments.ts
@@ -0,0 +1,11 @@
+import { HistoryEntry } from '../types';
+import aiplan from 'src/utils/aiplan';
+import { translateHistoryAction } from './actionTranslations';
+
+export function renderAttachment(m: HistoryEntry): string {
+ if (m.verb === 'created') {
+ return `${translateHistoryAction('attachment', true)} ${aiplan.ru_field(m)} ${aiplan.ru_value(m)} "${m.new_entity_detail?.asset.name || m.new_value}"`;
+ }
+ return `${aiplan.ru_verb(m)} ${aiplan.ru_field(m)} ${aiplan.ru_value(m)} "${m.old_value}"`;
+}
+
diff --git a/src/components/activity/entityActivity/renders/blocks.ts b/src/components/activity/entityActivity/renders/blocks.ts
new file mode 100644
index 00000000..ebb5e83d
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/blocks.ts
@@ -0,0 +1,14 @@
+import { HistoryEntry } from '../types';
+import aiplan from 'src/utils/aiplan';
+import { translateHistoryAction } from './actionTranslations';
+
+export function renderBlocks(m: HistoryEntry): string {
+ if (m.old_value == null || m.old_value === '') {
+ return `${translateHistoryAction('blocks', true)} ${aiplan.ru_field(m)} ${m.new_value}`;
+ }
+ if (m.new_value === '') {
+ return `${translateHistoryAction('blocks', false)} ${aiplan.ru_field(m)} ${m.old_value}`;
+ }
+ return `${aiplan.ru_verb(m)} ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
+}
+
diff --git a/src/components/activity/entityActivity/renders/comments.ts b/src/components/activity/entityActivity/renders/comments.ts
new file mode 100644
index 00000000..887f3208
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/comments.ts
@@ -0,0 +1,10 @@
+import { HistoryEntry } from '../types';
+import aiplan from 'src/utils/aiplan';
+
+export function renderComment(m: HistoryEntry): string {
+ if (m.verb === 'created') {
+ return `добавил(-а) ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
+ }
+ return `${aiplan.ru_verb(m)} ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
+}
+
diff --git a/src/components/activity/entityActivity/renders/dates.ts b/src/components/activity/entityActivity/renders/dates.ts
new file mode 100644
index 00000000..a18826af
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/dates.ts
@@ -0,0 +1,37 @@
+import { HistoryEntry } from '../types';
+import { formatDate } from 'src/utils/time';
+import { canBeConvertedToDate } from 'src/utils/canBeConvertedToDate';
+
+export function renderCompletedAt(): string {
+ return 'завершил(-а) задачу';
+}
+
+export function renderStartDate(): string {
+ return 'начал(-а) выполнение задачи';
+}
+
+export function renderTargetDate(m: HistoryEntry): string | undefined {
+ if (m.verb !== 'updated') {
+ return undefined;
+ }
+
+ const newDate =
+ m.new_value && canBeConvertedToDate(m.new_value)
+ ? formatDate(m.new_value)
+ : '';
+ const oldDate =
+ m.old_value && canBeConvertedToDate(m.old_value)
+ ? formatDate(m.old_value.replace(/"/g, ''))
+ : '';
+
+ if (m.old_value === '' || !m.old_value) {
+ return `установил(-а) срок исполнения ${newDate}`;
+ } else if (m.new_value === '' && oldDate) {
+ return `убрал(-а) срок исполнения ${oldDate}`;
+ } else {
+ return oldDate && newDate
+ ? `изменил(-а) срок исполнения с ${oldDate} на ${newDate}`
+ : 'изменил(-а) срок исполнения';
+ }
+}
+
diff --git a/src/components/activity/entityActivity/renders/docs.ts b/src/components/activity/entityActivity/renders/docs.ts
new file mode 100644
index 00000000..7463b3d9
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/docs.ts
@@ -0,0 +1,40 @@
+import { HistoryEntry } from '../types';
+import { translateHistoryAction } from './actionTranslations';
+
+export function renderDoc(m: HistoryEntry): string | undefined {
+ if (m.verb === 'remove') {
+ return `${translateHistoryAction('doc', false)} "${m.old_value}"`;
+ }
+ if (m.verb === 'added') {
+ return `${translateHistoryAction('doc', true)} "${m.new_value}"`;
+ }
+ if (m.verb === 'created') {
+ return `создал(-а) вложенный документ "${m.new_value}"`;
+ }
+ return undefined;
+}
+
+export function renderSeqId(m: HistoryEntry): string | undefined {
+ if (m.verb === 'updated') {
+ return `изменил(-а) позицию документа в директории с ${+m.old_value! + 1} на ${+m.new_value! + 1}`;
+ }
+ if (m.verb === 'move') {
+ if (m.new_value === m.workspace_detail?.name) {
+ return 'перенес(-ла) документ в корневую директорию';
+ } else {
+ return `перенес(-ла) документ в директорию "${m.new_value}"`;
+ }
+ }
+ return undefined;
+}
+
+export function renderDescription(m: HistoryEntry): string | undefined {
+ if (m.verb === 'updated') {
+ return 'изменил(-а) описание';
+ }
+ if (m.entity_type === 'doc') {
+ return 'изменил(-а) описание';
+ }
+ return undefined;
+}
+
diff --git a/src/components/activity/entityActivity/renders/issues.ts b/src/components/activity/entityActivity/renders/issues.ts
new file mode 100644
index 00000000..fd57791d
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/issues.ts
@@ -0,0 +1,34 @@
+import { HistoryEntry } from '../types';
+import aiplan from 'src/utils/aiplan';
+import { translateHistoryAction } from './actionTranslations';
+
+export function renderParent(m: HistoryEntry): string {
+ if (m.old_value == null || m.old_value === '') {
+ return `${aiplan.UserName(m.target_user).join(' ')} ${translateHistoryAction('parent', true)} ${m.new_value}`;
+ }
+ if (m.new_value === '') {
+ return `${aiplan.UserName(m.target_user).join(' ')} ${translateHistoryAction('parent', false)} ${aiplan.ru_field(m)} с задачи ${m.old_value}`;
+ }
+ return `${aiplan.ru_verb(m)} ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
+}
+
+export function renderBlocking(m: HistoryEntry): string {
+ if (m.old_value == null || m.old_value === '') {
+ return `${aiplan.UserName(m.target_user).join(' ')} ${translateHistoryAction('blocking', true)} ${m.new_value}`;
+ }
+ if (m.new_value === '') {
+ return `${aiplan.UserName(m.target_user).join(' ')} ${translateHistoryAction('blocking', false)} ${aiplan.ru_field(m)} с задачи ${m.old_value}`;
+ }
+ return `${aiplan.ru_verb(m)} ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
+}
+
+export function renderSubIssue(m: HistoryEntry): string | undefined {
+ if (m.verb === 'added') {
+ return `${translateHistoryAction('sub_issue', true)} ${m.new_value}`;
+ }
+ if (m.verb === 'removed') {
+ return `${translateHistoryAction('sub_issue', false)} ${m.old_value}`;
+ }
+ return undefined;
+}
+
diff --git a/src/components/activity/entityActivity/renders/labels.ts b/src/components/activity/entityActivity/renders/labels.ts
new file mode 100644
index 00000000..7980041f
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/labels.ts
@@ -0,0 +1,23 @@
+import { HistoryEntry } from '../types';
+import { translateHistoryAction } from './actionTranslations';
+
+export function renderLabels(m: HistoryEntry): string | undefined {
+ if (m.verb === 'added') {
+ return `${translateHistoryAction('labels', true)} ${m.new_value}`;
+ }
+ if (m.verb === 'removed') {
+ return `${translateHistoryAction('labels', false)} ${m.old_value}`;
+ }
+ return undefined;
+}
+
+export function renderLabel(m: HistoryEntry): string | undefined {
+ if (m.verb === 'added') {
+ return `${translateHistoryAction('label', true)} ${m.new_value}`;
+ }
+ if (m.verb === 'removed') {
+ return `${translateHistoryAction('label', false)} ${m.old_value}`;
+ }
+ return undefined;
+}
+
diff --git a/src/components/activity/entityActivity/renders/links.ts b/src/components/activity/entityActivity/renders/links.ts
new file mode 100644
index 00000000..1b2b5c0e
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/links.ts
@@ -0,0 +1,34 @@
+import { HistoryEntry } from '../types';
+import { translateHistoryAction } from './actionTranslations';
+
+export function renderLinked(m: HistoryEntry): string | undefined {
+ if (m.verb === 'updated' && !m.new_value && m.old_value) {
+ return `${translateHistoryAction('linked', false)} ${m.old_value}`;
+ }
+ if (m.verb === 'updated' && !m.old_value && m.new_value) {
+ return `${translateHistoryAction('linked', true)} ${m.new_value}`;
+ }
+ return undefined;
+}
+
+export function renderLink(m: HistoryEntry): string | undefined {
+ if (m.verb === 'created' && !m.old_value && m.new_value) {
+ return `${translateHistoryAction('link', true)} "${m.new_entity_detail?.title || m.new_value}"`;
+ }
+ if (m.verb === 'deleted' && m.old_value) {
+ return `${translateHistoryAction('link', false)} "${m.old_entity_detail?.title || m.old_value}"`;
+ }
+ return undefined;
+}
+
+export function renderLinkUrl(m: HistoryEntry): string | undefined {
+ if (m.verb === 'updated' && m.new_value) {
+ return `изменил(-а) ссылку c"${m.old_value}" на "${m.new_value}"`;
+ }
+ return undefined;
+}
+
+export function renderLinkTitle(m: HistoryEntry): string {
+ return `изменил(-а) название ссылки с "${m.old_value}" на "${m.new_value}"`;
+}
+
diff --git a/src/components/activity/entityActivity/renders/priority.ts b/src/components/activity/entityActivity/renders/priority.ts
new file mode 100644
index 00000000..f13094c3
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/priority.ts
@@ -0,0 +1,14 @@
+import { HistoryEntry } from '../types';
+import aiplan from 'src/utils/aiplan';
+
+export function renderPriority(m: HistoryEntry): string {
+ if (m.verb === 'updated' && m.old_value === '') {
+ return `установил(-а) ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
+ }
+ return `${aiplan.ru_verb(m)} ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
+}
+
+export function renderDefault(m: HistoryEntry): string {
+ return `${aiplan.ru_verb(m)} ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
+}
+
diff --git a/src/components/activity/entityActivity/renders/projects.ts b/src/components/activity/entityActivity/renders/projects.ts
new file mode 100644
index 00000000..18eee6f1
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/projects.ts
@@ -0,0 +1,40 @@
+import { HistoryEntry, HistoryRenderer } from '../types';
+
+export const renderIssueTransfer: HistoryRenderer = (m, wsProjects) => {
+ const newProject = wsProjects.find(
+ (project) => project.id === (m.new_identifier || m.project_id),
+ );
+ const newVal = newProject
+ ? `в проект "${newProject.name}"`
+ : 'в скрытый/удаленный проект';
+ const oldProject = wsProjects.find(
+ (project) => project.id === m.old_identifier,
+ );
+ const oldVal = oldProject
+ ? `из проекта "${oldProject.name}"`
+ : 'из скрытого/удаленного проекта';
+
+ if (m.verb === 'copied') {
+ return `скопировал(-а) задачу ${oldVal}`;
+ }
+
+ if (m.verb === 'move') {
+ return `перенес(-ла) задачу ${oldVal}`;
+ }
+
+ return `перенес(-ла) задачу ${oldVal} ${newVal}`;
+};
+
+export function renderProject(m: HistoryEntry): string | undefined {
+ if (m.verb === 'move') {
+ const newProjectName = m.project_detail?.name
+ ? m.project_detail.name
+ : 'в скрытый/удаленный проект';
+ const oldProjectName = m.old_entity_detail?.name
+ ? m.old_entity_detail.name
+ : 'из скрытого/удаленного проекта';
+ return `перенес(-ла) задачу из "${oldProjectName}" в "${newProjectName}"`;
+ }
+ return undefined;
+}
+
diff --git a/src/components/activity/entityActivity/renders/sprints.ts b/src/components/activity/entityActivity/renders/sprints.ts
new file mode 100644
index 00000000..bea1ec03
--- /dev/null
+++ b/src/components/activity/entityActivity/renders/sprints.ts
@@ -0,0 +1,15 @@
+import { HistoryEntry } from '../types';
+import { translateHistoryAction } from './actionTranslations';
+
+export function renderSprint(m: HistoryEntry): string | undefined {
+ if (m.verb === 'updated') {
+ return `изменил(-а) спринт с "${m.old_value}" на "${m.new_value}"`;
+ }
+ if (m.verb === 'added') {
+ return `${translateHistoryAction('sprint', true)} "${m.new_value}"`;
+ }
+ if (m.verb === 'removed') {
+ return `${translateHistoryAction('sprint', false)} "${m.old_value}"`;
+ }
+ return undefined;
+}
diff --git a/src/components/activity/entityActivity/types.ts b/src/components/activity/entityActivity/types.ts
new file mode 100644
index 00000000..19e992ad
--- /dev/null
+++ b/src/components/activity/entityActivity/types.ts
@@ -0,0 +1,26 @@
+export interface ProjectBase {
+ id?: string;
+ name?: string;
+}
+
+export interface HistoryEntry {
+ field: string;
+ verb: string;
+ old_value: string | null;
+ new_value: string | null;
+ old_identifier?: string;
+ new_identifier?: string;
+ old_entity_detail?: any;
+ new_entity_detail?: any;
+ target_user?: any;
+ project_id?: string;
+ workspace_detail?: { name: string };
+ project_detail?: { name: string };
+ entity_type?: string;
+}
+
+export type HistoryRenderer = (
+ m: HistoryEntry,
+ wsProjects: ProjectBase[],
+) => string | undefined;
+
diff --git a/src/components/activity/entityActivity/utils/getIcon.ts b/src/components/activity/entityActivity/utils/getIcon.ts
new file mode 100644
index 00000000..089a4b07
--- /dev/null
+++ b/src/components/activity/entityActivity/utils/getIcon.ts
@@ -0,0 +1,13 @@
+export const getIcon = (name: string): string => {
+ switch (name) {
+ case 'deleted':
+ return 'delete';
+ case 'created':
+ return 'add_circle_outline';
+ case 'updated':
+ return 'edit';
+ default:
+ return 'visibility';
+ }
+};
+
diff --git a/src/components/activity/renders/project-activity.ts b/src/components/activity/renders/project-activity.ts
index 62be1745..7734b78b 100644
--- a/src/components/activity/renders/project-activity.ts
+++ b/src/components/activity/renders/project-activity.ts
@@ -1,6 +1,10 @@
import { computed } from 'vue';
-import { valToNet, valToRole } from 'src/utils/strings';
-import { stateRUS, translateVerb } from 'src/utils/translator';
+import {
+ valToNet,
+ valToRole,
+ stateRUS,
+ translateVerb,
+} from 'src/utils/translator';
import { getFullName } from 'src/utils/helpers';
import aiplan from 'src/utils/aiplan';
import { translateAction } from './actionTranslations';
diff --git a/src/components/activity/renders/workspace-activity.ts b/src/components/activity/renders/workspace-activity.ts
index 89600345..96d87697 100644
--- a/src/components/activity/renders/workspace-activity.ts
+++ b/src/components/activity/renders/workspace-activity.ts
@@ -1,4 +1,4 @@
-import { valToRole } from 'src/utils/strings';
+import { valToRole } from 'src/utils/translator';
import { translateVerb } from 'src/utils/translator';
import aiplan from 'src/utils/aiplan';
import { getURLDoc } from './doc-activity';
diff --git a/src/modules/project-settings/members/ui/MembersProjectSettings.vue b/src/modules/project-settings/members/ui/MembersProjectSettings.vue
index 381e4c6f..f3fab2ac 100644
--- a/src/modules/project-settings/members/ui/MembersProjectSettings.vue
+++ b/src/modules/project-settings/members/ui/MembersProjectSettings.vue
@@ -177,7 +177,7 @@ import SearchIcon from 'src/components/icons/SearchIcon.vue';
import AvatarImage from 'src/components/AvatarImage.vue';
// utils
-import { valToRole } from 'src/utils/strings';
+import { valToRole } from 'src/utils/translator';
// constants
import {
diff --git a/src/modules/project-settings/members/ui/dialogs/EditProjectMemberDialog.vue b/src/modules/project-settings/members/ui/dialogs/EditProjectMemberDialog.vue
index 288fa066..7a3a7391 100644
--- a/src/modules/project-settings/members/ui/dialogs/EditProjectMemberDialog.vue
+++ b/src/modules/project-settings/members/ui/dialogs/EditProjectMemberDialog.vue
@@ -34,7 +34,7 @@ import { onUpdated, ref, watch } from 'vue';
import { useProjectStore } from 'src/stores/project-store';
import { useWorkspaceStore } from 'src/stores/workspace-store';
// utils
-import { valToRole } from 'src/utils/strings';
+import { valToRole } from 'src/utils/translator';
// constants
import { ROLES } from 'src/constants/constants';
diff --git a/src/modules/workspace-notifications/utils/project-notification.ts b/src/modules/workspace-notifications/utils/project-notification.ts
index dbd7ca55..de56da5d 100644
--- a/src/modules/workspace-notifications/utils/project-notification.ts
+++ b/src/modules/workspace-notifications/utils/project-notification.ts
@@ -1,5 +1,10 @@
-import { capitalizeFirstLetter, valToNet, valToRole } from 'src/utils/strings';
-import { stateRUS, translatePrioritets, translateVerb } from 'src/utils/translator';
+import { capitalizeFirstLetter } from 'src/utils/strings';
+import { valToNet, valToRole } from 'src/utils/translator';
+import {
+ stateRUS,
+ translatePrioritets,
+ translateVerb,
+} from 'src/utils/translator';
import { getFullName } from 'src/utils/helpers';
import { NotificationsNotificationDetailResponse } from '@aisa-it/aiplan-api-ts/src/data-contracts';
import { getProjectLink } from 'src/utils/links';
diff --git a/src/modules/workspace-notifications/utils/workspace-notification.ts b/src/modules/workspace-notifications/utils/workspace-notification.ts
index 92b416d6..64142cca 100644
--- a/src/modules/workspace-notifications/utils/workspace-notification.ts
+++ b/src/modules/workspace-notifications/utils/workspace-notification.ts
@@ -1,4 +1,4 @@
-import { valToRole } from 'src/utils/strings';
+import { valToRole } from 'src/utils/translator';
import { translateVerb } from 'src/utils/translator';
import { getFullName } from 'src/utils/helpers';
import { getDocLink } from '../helpers/getDocLink';
diff --git a/src/modules/workspace-settings/ui/tabs/MembersWorkspaceSettings.vue b/src/modules/workspace-settings/ui/tabs/MembersWorkspaceSettings.vue
index f40eb043..1e633548 100644
--- a/src/modules/workspace-settings/ui/tabs/MembersWorkspaceSettings.vue
+++ b/src/modules/workspace-settings/ui/tabs/MembersWorkspaceSettings.vue
@@ -200,7 +200,7 @@ import SearchIcon from 'src/components/icons/SearchIcon.vue';
import AvatarImage from 'src/components/AvatarImage.vue';
// utils
-import { valToRole } from 'src/utils/strings';
+import { valToRole } from 'src/utils/translator';
// constants
import {
diff --git a/src/stores/project-store.ts b/src/stores/project-store.ts
index b08fa5a7..49aea0f1 100644
--- a/src/stores/project-store.ts
+++ b/src/stores/project-store.ts
@@ -2,7 +2,7 @@ import { defineStore, storeToRefs } from 'pinia';
import { withInterceptors } from 'src/utils/interceptorsWithInstanceClass';
import { Projects } from '@aisa-it/aiplan-api-ts/src/Projects';
import { Users } from '@aisa-it/aiplan-api-ts/src/Users';
-import { valToNet } from 'src/utils/strings';
+import { valToNet } from 'src/utils/translator';
import { getProjectEmojiViaCode } from 'src/utils/helpers';
import { useWorkspaceStore } from './workspace-store';
import { useRolesStore } from './roles-store';
diff --git a/src/utils/colors.ts b/src/utils/colors.ts
new file mode 100644
index 00000000..807d29b1
--- /dev/null
+++ b/src/utils/colors.ts
@@ -0,0 +1,21 @@
+export const generateRandomColor = (string: string): string => {
+ if (!string) return 'rgb(var(--color-accent))';
+
+ string = `${string}`;
+
+ const uniqueId = string.length.toString() + string; // Unique identifier based on string length
+ const combinedString = uniqueId + string;
+
+ const hash = Array.from(combinedString).reduce((acc, char) => {
+ const charCode = char.charCodeAt(0);
+ return (acc << 5) - acc + charCode;
+ }, 0);
+
+ const hue = hash % 360;
+ const saturation = 70; // Higher saturation for pastel colors
+ const lightness = 60; // Mid-range lightness for pastel colors
+
+ const randomColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
+
+ return randomColor;
+};
diff --git a/src/utils/strings.ts b/src/utils/strings.ts
index 039a5593..e00c1060 100644
--- a/src/utils/strings.ts
+++ b/src/utils/strings.ts
@@ -1,8 +1,3 @@
-import aiplan from 'src/utils/aiplan';
-import { IProject } from 'src/interfaces/projects';
-import { formatDate } from './time';
-import { canBeConvertedToDate } from './canBeConvertedToDate';
-
export const addSpaceIfCamelCase = (str: string) =>
str.replace(/([a-z])([A-Z])/g, '$1 $2');
@@ -90,28 +85,6 @@ export const cosineSimilarity = (a: string, b: string) => {
return dotProduct / Math.sqrt(magnitudeA * magnitudeB);
};
-export const generateRandomColor = (string: string): string => {
- if (!string) return 'rgb(var(--color-accent))';
-
- string = `${string}`;
-
- const uniqueId = string.length.toString() + string; // Unique identifier based on string length
- const combinedString = uniqueId + string;
-
- const hash = Array.from(combinedString).reduce((acc, char) => {
- const charCode = char.charCodeAt(0);
- return (acc << 5) - acc + charCode;
- }, 0);
-
- const hue = hash % 360;
- const saturation = 70; // Higher saturation for pastel colors
- const lightness = 60; // Mid-range lightness for pastel colors
-
- const randomColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
-
- return randomColor;
-};
-
export const getFirstCharacters = (str: string) => {
const words = str.trim().split(' ');
if (words.length === 1) {
@@ -120,258 +93,3 @@ export const getFirstCharacters = (str: string) => {
return words[0].charAt(0) + words[1].charAt(0);
}
};
-
-export const valToRole = (val: number) => {
- switch (val) {
- case 5:
- return { value: 5, label: 'Гость' };
- case 10:
- return { value: 10, label: 'Участник' };
- case 15:
- return { value: 15, label: 'Администратор' };
- default:
- return { value: 5, label: 'Гость' };
- }
-};
-
-export const valToNet = (val: boolean) => {
- switch (val) {
- case false:
- return { value: false, label: 'Скрытый' };
- case true:
- return { value: true, label: 'Публичный' };
- default:
- return { value: false, label: 'Скрытый' };
- }
-};
-
-export const getIcon = (name: string): string => {
- switch (name) {
- case 'deleted':
- return 'delete';
- case 'created':
- return 'add_circle_outline';
- case 'updated':
- return 'edit';
- default:
- return 'visibility';
- }
-};
-
-export const getHistoryText = (m: any, wsProjects: IProject[]) => {
- switch (m.field) {
- case 'blocks':
- if (m.old_value == null || m.old_value == '')
- return `добавил(-а) ${aiplan.ru_field(m)} ${m.new_value}`;
- if (m.new_value == '')
- return `удалил(-а) ${aiplan.ru_field(m)} ${m.old_value}`;
- return `${aiplan.ru_verb(m)}
- ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
- case 'assignees':
- if (m.old_value == null || m.old_value == '')
- return `добавил(-а) ${aiplan.ru_field(m)}
- ${aiplan.UserName(m.new_entity_detail).join(' ')}`;
- if (m.new_value == '')
- return `убрал(-а) ${aiplan.ru_field(m)}
- ${aiplan.UserName(m.old_entity_detail).join(' ')}`;
- case 'completed_at':
- return 'завершил(-а) задачу';
- case 'start_date':
- return 'начал(-а) выполнение задачи';
- case 'watchers':
- if (m.old_value == null || m.old_value == '')
- return `добавил(-а) ${aiplan.ru_field(m)}
- ${aiplan.UserName(m.new_entity_detail).join(' ')}`;
- if (m.new_value == '')
- return `убрал(-а) ${aiplan.ru_field(m)}
- ${aiplan.UserName(m.old_entity_detail).join(' ')}`;
- case 'parent':
- case 'blocking':
- if (m.old_value == null || m.old_value == '')
- return `${aiplan
- .UserName(m.target_user)
- .join(' ')} заблокировал(-а) задачу ${m.new_value}`;
- if (m.new_value == '')
- return `${aiplan
- .UserName(m.target_user)
- .join(' ')} снял(-а) ${aiplan.ru_field(m)} с задачи ${m.old_value}`;
-
- return `${aiplan.ru_verb(m)}
- ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
- case 'comment':
- if (m.verb === 'created') {
- return `добавил(-а)
- ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
- }
- return `${aiplan.ru_verb(m)}
- ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
- case 'attachment':
- if (m.verb === 'created') {
- return `добавил(-а) ${aiplan.ru_field(m)} ${aiplan.ru_value(m)} "${
- m.new_entity_detail?.asset.name || m.new_value
- }" `;
- }
- return `${aiplan.ru_verb(m)} ${aiplan.ru_field(m)} ${aiplan.ru_value(
- m,
- )} "${m.old_value}"`;
- case 'issue_transfer':
- const newProject = wsProjects.find(
- (project) => project.id === (m.new_identifier || m.project_id),
- );
- const newVal = newProject
- ? `в проект "${newProject.name}"`
- : 'в скрытый/удаленный проект';
- const oldProject = wsProjects.find(
- (project) => project.id === m.old_identifier,
- );
- const oldVal = oldProject
- ? `из проекта "${oldProject.name}"`
- : 'из скрытого/удаленного проекта';
-
- if (m.verb === 'copied') {
- return `скопировал(-а) задачу ${oldVal}`;
- }
-
- if (m.verb === 'move') {
- return `перенес(-ла) задачу ${oldVal}`;
- }
-
- return `перенес(-ла) задачу ${oldVal} ${newVal}`;
-
- case 'linked':
- if (m.verb === 'updated' && !m.new_value && m.old_value) {
- return `убрал(-а) связь с задачей ${m.old_value}`;
- }
- if (m.verb === 'updated' && !m.old_value && m.new_value) {
- return `добавил(-а) связь с задачей ${m.new_value}`;
- }
-
- case 'sub_issue':
- if (m.verb === 'added') {
- return `добавил(-а) подзадачу ${m.new_value}`;
- }
- if (m.verb === 'removed') {
- return `удалил(-а) подзадачу ${m.old_value}`;
- }
- case 'priority':
- if (m.verb === 'updated' && m.old_value === '') {
- return `установил(-а)
- ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
- }
- return `${aiplan.ru_verb(m)}
- ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
-
- case 'labels':
- if (m.verb === 'added') {
- return `добавил(-а) тег ${m.new_value}`;
- }
- if (m.verb === 'removed') {
- return `удалил(-а) тег ${m.old_value}`;
- }
- case 'label':
- if (m.verb === 'added') {
- return `добавил(-а) тег ${m.new_value}`;
- }
- if (m.verb === 'removed') {
- return `удалил(-а) тег ${m.old_value}`;
- }
- case 'link':
- if (m.verb === 'created' && !m.old_value && m.new_value) {
- return `добавил(-а) ссылку "${
- m.new_entity_detail?.title || m.new_value
- }"`;
- }
-
- if (m.verb === 'deleted' && m.old_value) {
- return `удалил(-а) ссылку "${
- m.old_entity_detail?.title || m.old_value
- }"`;
- }
- case 'link_url':
- if (m.verb === 'updated' && m.new_value) {
- return `изменил(-а) ссылку c"${m.old_value}" на "${m.new_value}" `;
- }
-
- case 'link_title':
- return `изменил(-а) название ссылки с "${m.old_value}" на "${m.new_value}"`;
- case 'doc':
- if (m.verb === 'remove') {
- return `убрал(-а) вложенный документ "${m.old_value}"`;
- }
- if (m.verb === 'added') {
- return `добавил(-а) вложенный документ "${m.new_value}"`;
- }
- if (m.verb === 'created') {
- return `создал(-а) вложенный документ "${m.new_value}"`;
- }
-
- case 'seq_id':
- if (m.verb === 'updated') {
- return `изменил(-а) позицию документа в директории с ${
- +m.old_value + 1
- } на ${+m.new_value + 1}`;
- }
- if (m.verb === 'move') {
- if (m.new_value === m.workspace_detail.name) {
- return 'перенес(-ла) документ в корневую директорию';
- } else {
- return `перенес(-ла) документ в директорию "${m.new_value}"`;
- }
- }
- case 'sprint':
- if (m.verb === 'updated') {
- return `изменил(-а) спринт с "${m.old_value}" на "${m.new_value}"`;
- }
- if (m.verb === 'added') {
- return `добавил(-а) спринт "${m.new_value}"`;
- }
- if (m.verb === 'removed') {
- return `убрал(-а) спринт "${m.old_value}"`;
- }
- case 'description':
- if (m.verb === 'updated') {
- return 'изменил(-а) описание';
- }
- if (m.entity_type === 'doc') {
- return 'изменил(-а) описание';
- }
-
- case 'project':
- if (m.verb === 'move') {
- const newProjectName = m.project_detail?.name
- ? m.project_detail.name
- : 'в скрытый/удаленный проект';
- const oldProjectName = m.old_entity_detail.name
- ? m.old_entity_detail.name
- : 'из скрытого/удаленного проекта';
- return `перенес(-ла) задачу из "${oldProjectName}" в "${newProjectName}"`;
- }
-
- case 'target_date':
- if (m.verb === 'updated') {
- const newDate =
- m.new_value instanceof Date || canBeConvertedToDate(m.new_value)
- ? formatDate(m.new_value)
- : '';
- const oldDate =
- m.old_value &&
- (m.old_value instanceof Date || canBeConvertedToDate(m.old_value))
- ? formatDate(m.old_value.replace(/"/g, ''))
- : '';
-
- if (m.old_value === '' || !m.old_value) {
- return `установил(-а) срок исполнения ${newDate}`;
- } else if (m.new_value === '' && oldDate) {
- return `убрал(-а) срок исполнения ${oldDate}`;
- } else {
- return oldDate && newDate
- ? `изменил(-а) срок исполнения с ${oldDate} на ${newDate}`
- : 'изменил(-а) срок исполнения';
- }
- }
-
- default:
- return `${aiplan.ru_verb(m)}
- ${aiplan.ru_field(m)} ${aiplan.ru_value(m)}`;
- }
-};
diff --git a/src/utils/translator.ts b/src/utils/translator.ts
index c8b02718..653337af 100644
--- a/src/utils/translator.ts
+++ b/src/utils/translator.ts
@@ -36,6 +36,30 @@ export const translatePrioritets = (priority: string) => {
}
};
+export const valToRole = (val: number) => {
+ switch (val) {
+ case 5:
+ return { value: 5, label: 'Гость' };
+ case 10:
+ return { value: 10, label: 'Участник' };
+ case 15:
+ return { value: 15, label: 'Администратор' };
+ default:
+ return { value: 5, label: 'Гость' };
+ }
+};
+
+export const valToNet = (val: boolean) => {
+ switch (val) {
+ case false:
+ return { value: false, label: 'Скрытый' };
+ case true:
+ return { value: true, label: 'Публичный' };
+ default:
+ return { value: false, label: 'Скрытый' };
+ }
+};
+
export const translateFirstCharUpperCasePriority = (priority: string) => {
switch (priority) {
case 'urgent':