@coopenomics/desktop 2025.5.14 → 2025.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env-example +4 -0
- package/extensions/participant/install.ts +37 -25
- package/extensions/soviet/install.ts +23 -20
- package/package.json +7 -5
- package/quasar.config.cjs +1 -1
- package/src/app/App.vue +1 -0
- package/src/app/providers/routes/index.ts +12 -0
- package/src/app/styles/app.scss +1 -1
- package/src/app/styles/style.css +11 -0
- package/src/css/quasar.variables.scss +1 -1
- package/src/entities/Desktop/model/store.ts +68 -0
- package/src/entities/Desktop/model/types.ts +7 -0
- package/src/entities/Document/model/types.ts +1 -1
- package/src/entities/Meet/api/index.ts +2 -28
- package/src/entities/Meet/model/store.ts +7 -22
- package/src/entities/Wallet/api/index.ts +3 -3
- package/src/env.d.ts +1 -0
- package/src/features/Meet/CloseMeetWithDecision/model/index.ts +119 -17
- package/src/features/Meet/CreateMeet/model/index.ts +51 -9
- package/src/features/Meet/CreateMeet/ui/CreateMeet.vue +37 -6
- package/src/features/Meet/CreateMeet/ui/CreateMeetForm.vue +87 -65
- package/src/features/Meet/GenerateSovietDecision/model/index.ts +14 -4
- package/src/features/Meet/RestartMeet/model/index.ts +121 -3
- package/src/features/Meet/RestartMeet/ui/RestartMeet.vue +4 -6
- package/src/features/Meet/RestartMeet/ui/RestartMeetForm.vue +64 -28
- package/src/features/Meet/SignNotification/index.ts +2 -0
- package/src/features/Meet/SignNotification/model/index.ts +137 -0
- package/src/features/Meet/SignNotification/ui/SignNotificationButton.vue +61 -0
- package/src/features/Meet/SignNotification/ui/index.ts +1 -0
- package/src/features/Meet/VoteOnMeet/model/composable.ts +180 -0
- package/src/features/Meet/VoteOnMeet/model/index.ts +2 -17
- package/src/features/Meet/VoteOnMeet/model/types.ts +4 -0
- package/src/features/Meet/index.ts +6 -0
- package/src/features/User/LoginRedirect/ui/LoginRedirectPage.vue +15 -0
- package/src/features/User/LoginRedirect/ui/index.ts +1 -0
- package/src/features/User/LoginUser/ui/LoginForm/LoginForm.vue +38 -1
- package/src/features/User/LoginWithRedirect/ui/LoginRedirectForm/LoginRedirectForm.vue +0 -0
- package/src/pages/Cooperative/ListOfMeets/ui/ListOfMeetsPage.vue +22 -46
- package/src/pages/Cooperative/MeetDetails/ui/MeetDetailsPage.vue +84 -28
- package/src/pages/PermissionDenied/PermissionDenied.vue +1 -1
- package/src/processes/init-app/index.ts +12 -5
- package/src/processes/navigation-guard-setup/index.ts +37 -5
- package/src/processes/process-decisions/index.ts +7 -6
- package/src/shared/config/Environment.ts +35 -27
- package/src/shared/lib/composables/index.ts +1 -0
- package/src/shared/lib/composables/useMeetStatus.ts +96 -0
- package/src/shared/lib/consts/index.ts +1 -0
- package/src/shared/lib/consts/meet-statuses.ts +114 -0
- package/src/shared/lib/document/model/entity.ts +4 -2
- package/src/shared/lib/types/certificate/index.ts +6 -0
- package/src/shared/lib/types/document/index.ts +1 -1
- package/src/shared/lib/types/workspace.ts +24 -0
- package/src/shared/lib/utils/dates/index.ts +5 -0
- package/src/shared/lib/utils/dates/moment.ts +43 -2
- package/src/shared/lib/utils/dates/timezone.ts +75 -0
- package/src/shared/lib/utils/getNameFromCertificate.ts +108 -0
- package/src/shared/lib/utils/index.ts +1 -0
- package/src/shared/lib/utils/parseLinks.ts +10 -0
- package/src/shared/ui/AgendaNumberAvatar/AgendaNumberAvatar.vue +12 -0
- package/src/shared/ui/AgendaNumberAvatar/index.ts +1 -0
- package/src/shared/ui/BaseDocument/BaseDocument.vue +37 -8
- package/src/shared/ui/ExpandableDocument/ExpandableDocument.vue +49 -0
- package/src/shared/ui/ExpandableDocument/index.ts +1 -0
- package/src/shared/ui/MeetInfoCard/index.ts +1 -0
- package/src/shared/ui/MeetInfoCard/ui/MeetInfoCard.vue +62 -0
- package/src/shared/ui/MeetStatusBanner/index.ts +1 -0
- package/src/shared/ui/MeetStatusBanner/ui/MeetStatusBanner.vue +94 -0
- package/src/shared/ui/index.ts +1 -0
- package/src/widgets/Cooperative/Documents/ListOfDocuments/ui/DocumentsTable.vue +3 -3
- package/src/widgets/Cooperative/Orders/ListOfOrders/ui/ListOfOrdersWidget.vue +2 -2
- package/src/widgets/Cooperative/Payments/ListOfPayments/ui/ListOfPaymentsWidget.vue +2 -2
- package/src/widgets/Desktop/WorkspaceMenu/WorkspaceMenu.vue +74 -82
- package/src/widgets/Header/CommonHeader/CooperativeSettingsHeader.vue +1 -1
- package/src/widgets/Header/CommonHeader/ExtstoreHeader.vue +1 -1
- package/src/widgets/Header/CommonHeader/MainHeader.vue +6 -0
- package/src/widgets/Header/CommonHeader/UserSettingsHeader.vue +1 -1
- package/src/widgets/Meets/MeetCardsList/index.ts +1 -0
- package/src/widgets/Meets/MeetCardsList/ui/MeetCardsList.vue +55 -0
- package/src/widgets/Meets/MeetDetailsActions/MeetDetailsActions.vue +33 -15
- package/src/widgets/Meets/MeetDetailsAgenda/MeetDetailsAgenda.vue +34 -15
- package/src/widgets/Meets/MeetDetailsInfo/MeetDetailsInfo.vue +27 -0
- package/src/widgets/Meets/MeetDetailsInfo/index.ts +1 -0
- package/src/widgets/Meets/MeetDetailsResults/MeetDetailsResults.vue +129 -0
- package/src/widgets/Meets/MeetDetailsResults/index.ts +1 -0
- package/src/widgets/Meets/MeetDetailsVoting/MeetDetailsVoting.vue +221 -62
- package/src/widgets/Meets/MeetQuorumIndicator/MeetQuorumIndicator.vue +0 -0
- package/src/widgets/Meets/MeetQuorumIndicator/index.ts +1 -0
- package/src/widgets/Meets/MeetQuorumIndicator/ui/MeetQuorumIndicator.vue +35 -0
- package/src/widgets/Meets/MeetsTable/ui/MeetsTable.vue +56 -65
- package/src/widgets/NotificationCenter/NotificationCenter.vue +90 -0
- package/src/widgets/NotificationCenter/index.ts +1 -0
- package/src/widgets/Participants/ui/ParticipantsTable.vue +1 -1
- package/src/widgets/Questions/ui/QuestionsTable/QuestionsTable.vue +22 -10
- package/src/widgets/Questions/ui/VotingButtons/VotingButtons.vue +59 -14
- package/src/widgets/Registrator/AlreadyRegistered/AlreadyRegistered.vue +1 -1
- package/src/widgets/User/PaymentMethods/ui/PaymentMethods.vue +0 -1
- package/src-ssr/middlewares/injectEnv.ts +18 -12
- package/src/features/Meet/GenerateAgenda/index.ts +0 -1
- package/src/features/Meet/GenerateAgenda/model/index.ts +0 -18
- package/src/features/Meet/GenerateBallot/index.ts +0 -1
- package/src/features/Meet/GenerateBallot/model/index.ts +0 -18
- package/src/features/Meet/GenerateNotification/index.ts +0 -1
- package/src/features/Meet/GenerateNotification/model/index.ts +0 -18
- package/src/features/Meet/MeetDetailsManagement/index.ts +0 -1
- package/src/features/Meet/MeetDetailsManagement/model/index.ts +0 -121
- package/src/pages/Cooperative/ListOfMeets/model/index.ts +0 -1
- package/src/pages/Cooperative/ListOfMeets/model/model.ts +0 -117
- package/src/widgets/Meets/MeetDetailsActions/model.ts +0 -46
- package/src/widgets/Meets/MeetDetailsHeader/MeetDetailsHeader.vue +0 -40
- package/src/widgets/Meets/MeetDetailsHeader/index.ts +0 -1
- package/src/widgets/Meets/MeetDetailsVoting/model.ts +0 -117
- package/src/widgets/Meets/MeetInfoCard/ui/MeetInfoCard.vue +0 -38
- package/src/widgets/Meets/MeetInfoCard/ui/index.ts +0 -1
- /package/src/{widgets/Meets/MeetInfoCard → features/User/LoginRedirect}/index.ts +0 -0
@@ -11,6 +11,8 @@ import { computed } from 'vue'
|
|
11
11
|
import { useAgendaStore } from 'src/entities/Agenda/model'
|
12
12
|
import type { IAgenda } from 'src/entities/Agenda/model'
|
13
13
|
import { DigitalDocument } from 'src/shared/lib/document'
|
14
|
+
import type { IUserCertificateUnion } from 'src/shared/lib/types/certificate'
|
15
|
+
import { getNameFromCertificate } from 'src/shared/lib/utils/getNameFromCertificate'
|
14
16
|
|
15
17
|
/**
|
16
18
|
* Процесс обработки решений
|
@@ -28,14 +30,11 @@ export function useDecisionProcessor() {
|
|
28
30
|
/**
|
29
31
|
* Форматирует заголовок вопроса
|
30
32
|
*/
|
31
|
-
function formatDecisionTitle(title: string,
|
33
|
+
function formatDecisionTitle(title: string, cert: IUserCertificateUnion) {
|
32
34
|
let result = 'Вопрос на голосование'
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
} else {
|
37
|
-
result = `${title} от ${user.short_name}`
|
38
|
-
}
|
36
|
+
const name = getNameFromCertificate(cert)
|
37
|
+
result = `${title} от ${name}`
|
39
38
|
|
40
39
|
return result
|
41
40
|
}
|
@@ -138,6 +137,8 @@ export function useDecisionProcessor() {
|
|
138
137
|
const { generateSovietDecisionOnAnnualMeet } = useGenerateSovietDecisionOnAnnualMeet()
|
139
138
|
document = await generateSovietDecisionOnAnnualMeet({
|
140
139
|
username: username,
|
140
|
+
decision_id: decision_id,
|
141
|
+
meet_hash: row.table.hash as string
|
141
142
|
})
|
142
143
|
}
|
143
144
|
else {
|
@@ -1,19 +1,23 @@
|
|
1
1
|
// Типы для переменных окружения
|
2
2
|
export interface EnvVars {
|
3
|
-
NODE_ENV
|
4
|
-
BACKEND_URL
|
5
|
-
CHAIN_URL
|
6
|
-
CHAIN_ID
|
7
|
-
CURRENCY
|
8
|
-
COOP_SHORT_NAME
|
9
|
-
SITE_DESCRIPTION
|
10
|
-
SITE_IMAGE
|
11
|
-
STORAGE_URL
|
12
|
-
UPLOAD_URL
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
3
|
+
NODE_ENV: string;
|
4
|
+
BACKEND_URL: string;
|
5
|
+
CHAIN_URL: string;
|
6
|
+
CHAIN_ID: string;
|
7
|
+
CURRENCY: string;
|
8
|
+
COOP_SHORT_NAME: string;
|
9
|
+
SITE_DESCRIPTION: string;
|
10
|
+
SITE_IMAGE: string;
|
11
|
+
STORAGE_URL: string;
|
12
|
+
UPLOAD_URL: string;
|
13
|
+
TIMEZONE: string;
|
14
|
+
CLIENT: boolean;
|
15
|
+
SERVER: boolean;
|
16
|
+
VUE_ROUTER_MODE: string;
|
17
|
+
VUE_ROUTER_BASE: string;
|
18
|
+
NOVU_APP_ID: string;
|
19
|
+
NOVU_BACKEND_URL: string;
|
20
|
+
NOVU_SOCKET_URL: string;
|
17
21
|
[key: string]: string | boolean | undefined;
|
18
22
|
}
|
19
23
|
|
@@ -37,20 +41,24 @@ function getEnv(): EnvVars {
|
|
37
41
|
// Для SSR сервера - берем реальные переменные
|
38
42
|
// Для SPA - эти значения заменятся при сборке
|
39
43
|
return {
|
40
|
-
NODE_ENV: process.env.NODE_ENV,
|
41
|
-
BACKEND_URL: process.env.BACKEND_URL,
|
42
|
-
CHAIN_URL: process.env.CHAIN_URL,
|
43
|
-
CHAIN_ID: process.env.CHAIN_ID,
|
44
|
-
CURRENCY: process.env.CURRENCY,
|
45
|
-
COOP_SHORT_NAME: process.env.COOP_SHORT_NAME,
|
46
|
-
SITE_DESCRIPTION: process.env.SITE_DESCRIPTION,
|
47
|
-
SITE_IMAGE: process.env.SITE_IMAGE,
|
48
|
-
STORAGE_URL: process.env.STORAGE_URL,
|
49
|
-
UPLOAD_URL: process.env.UPLOAD_URL,
|
44
|
+
NODE_ENV: process.env.NODE_ENV as string,
|
45
|
+
BACKEND_URL: process.env.BACKEND_URL as string,
|
46
|
+
CHAIN_URL: process.env.CHAIN_URL as string,
|
47
|
+
CHAIN_ID: process.env.CHAIN_ID as string,
|
48
|
+
CURRENCY: process.env.CURRENCY as string,
|
49
|
+
COOP_SHORT_NAME: process.env.COOP_SHORT_NAME as string,
|
50
|
+
SITE_DESCRIPTION: process.env.SITE_DESCRIPTION as string,
|
51
|
+
SITE_IMAGE: process.env.SITE_IMAGE as string,
|
52
|
+
STORAGE_URL: process.env.STORAGE_URL as string,
|
53
|
+
UPLOAD_URL: process.env.UPLOAD_URL as string,
|
54
|
+
TIMEZONE: process.env.TIMEZONE || 'Europe/Moscow',
|
50
55
|
CLIENT: process.env.CLIENT as unknown as boolean,
|
51
56
|
SERVER: process.env.SERVER as unknown as boolean,
|
52
|
-
VUE_ROUTER_MODE: process.env.VUE_ROUTER_MODE,
|
53
|
-
VUE_ROUTER_BASE: process.env.VUE_ROUTER_BASE
|
57
|
+
VUE_ROUTER_MODE: process.env.VUE_ROUTER_MODE as string,
|
58
|
+
VUE_ROUTER_BASE: process.env.VUE_ROUTER_BASE as string,
|
59
|
+
NOVU_APP_ID: process.env.NOVU_APP_ID as string,
|
60
|
+
NOVU_BACKEND_URL: process.env.NOVU_BACKEND_URL as string,
|
61
|
+
NOVU_SOCKET_URL: process.env.NOVU_SOCKET_URL as string,
|
54
62
|
};
|
55
63
|
}
|
56
64
|
|
@@ -61,7 +69,7 @@ function getEnv(): EnvVars {
|
|
61
69
|
|
62
70
|
// Запасной вариант, если ничего не сработало
|
63
71
|
console.warn('Не удалось получить переменные окружения!');
|
64
|
-
return {};
|
72
|
+
return {} as EnvVars;
|
65
73
|
}
|
66
74
|
|
67
75
|
// Экспортируем переменные окружения
|
@@ -0,0 +1 @@
|
|
1
|
+
export { useMeetStatus } from './useMeetStatus'
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import { computed } from 'vue'
|
2
|
+
import type { IMeet } from 'src/entities/Meet'
|
3
|
+
import moment from 'moment-with-locales-es6'
|
4
|
+
import { BASIC_STATUS_MAP, EXTENDED_STATUS_MAP, SPECIAL_STATUSES } from 'src/shared/lib/consts'
|
5
|
+
import { formatDateToLocalTimezone, formatDateFromNow } from 'src/shared/lib/utils/dates/timezone'
|
6
|
+
|
7
|
+
export function useMeetStatus(meet: IMeet | null) {
|
8
|
+
// Базовый статус собрания
|
9
|
+
const basicStatus = computed(() => {
|
10
|
+
if (!meet?.processing?.meet?.status) return 'Неизвестный статус'
|
11
|
+
return BASIC_STATUS_MAP[meet.processing.meet.status] || 'Неизвестный статус'
|
12
|
+
})
|
13
|
+
|
14
|
+
// Расширенный статус собрания
|
15
|
+
const extendedStatus = computed(() => {
|
16
|
+
if (!meet?.processing?.extendedStatus) return 'Неизвестный статус'
|
17
|
+
return EXTENDED_STATUS_MAP[meet.processing.extendedStatus] || 'Неизвестный статус'
|
18
|
+
})
|
19
|
+
|
20
|
+
// Даты собрания в локальном часовом поясе
|
21
|
+
const formattedOpenDate = computed(() => {
|
22
|
+
if (!meet?.processing?.meet?.open_at) return ''
|
23
|
+
return formatDateToLocalTimezone(meet.processing.meet.open_at)
|
24
|
+
})
|
25
|
+
|
26
|
+
const formattedCloseDate = computed(() => {
|
27
|
+
if (!meet?.processing?.meet?.close_at) return ''
|
28
|
+
return formatDateToLocalTimezone(meet.processing.meet.close_at)
|
29
|
+
})
|
30
|
+
|
31
|
+
// Относительное время до/после собрания
|
32
|
+
const isVotingNotStarted = computed(() => {
|
33
|
+
if (!meet?.processing?.meet?.open_at) return false
|
34
|
+
return moment().isBefore(moment(meet.processing.meet.open_at))
|
35
|
+
})
|
36
|
+
|
37
|
+
const isVotingEnded = computed(() => {
|
38
|
+
if (!meet?.processing?.meet?.close_at) return false
|
39
|
+
return moment().isAfter(moment(meet.processing.meet.close_at))
|
40
|
+
})
|
41
|
+
|
42
|
+
const isVotingInProgress = computed(() => {
|
43
|
+
if (!meet?.processing?.meet?.open_at || !meet?.processing?.meet?.close_at) return false
|
44
|
+
const now = moment()
|
45
|
+
return now.isAfter(moment(meet.processing.meet.open_at)) && now.isBefore(moment(meet.processing.meet.close_at))
|
46
|
+
})
|
47
|
+
|
48
|
+
// Проверяем специальные статусы
|
49
|
+
const hasSpecialStatus = computed(() => {
|
50
|
+
if (!meet?.processing?.extendedStatus) return false
|
51
|
+
return SPECIAL_STATUSES.includes(meet.processing.extendedStatus)
|
52
|
+
})
|
53
|
+
|
54
|
+
const relativeOpenTime = computed(() => {
|
55
|
+
if (!meet?.processing?.meet?.open_at || hasSpecialStatus.value) return ''
|
56
|
+
|
57
|
+
const openMoment = moment(meet.processing.meet.open_at)
|
58
|
+
const now = moment()
|
59
|
+
|
60
|
+
if (now.isBefore(openMoment)) {
|
61
|
+
return `Собрание начнется ${formatDateFromNow(meet.processing.meet.open_at)}`
|
62
|
+
} else {
|
63
|
+
// Проверяем, закончилось ли уже собрание
|
64
|
+
if (isVotingEnded.value) {
|
65
|
+
return relativeCloseTime.value
|
66
|
+
}
|
67
|
+
return `Собрание началось ${formatDateFromNow(meet.processing.meet.open_at)}`
|
68
|
+
}
|
69
|
+
})
|
70
|
+
|
71
|
+
const relativeCloseTime = computed(() => {
|
72
|
+
if (!meet?.processing?.meet?.close_at || hasSpecialStatus.value) return ''
|
73
|
+
|
74
|
+
const closeMoment = moment(meet.processing.meet.close_at)
|
75
|
+
const now = moment()
|
76
|
+
|
77
|
+
if (now.isBefore(closeMoment)) {
|
78
|
+
return `Собрание завершится ${formatDateFromNow(meet.processing.meet.close_at)}`
|
79
|
+
} else {
|
80
|
+
return `Собрание завершилось ${formatDateFromNow(meet.processing.meet.close_at)}`
|
81
|
+
}
|
82
|
+
})
|
83
|
+
|
84
|
+
return {
|
85
|
+
basicStatus,
|
86
|
+
extendedStatus,
|
87
|
+
formattedOpenDate,
|
88
|
+
formattedCloseDate,
|
89
|
+
isVotingNotStarted,
|
90
|
+
isVotingEnded,
|
91
|
+
isVotingInProgress,
|
92
|
+
hasSpecialStatus,
|
93
|
+
relativeOpenTime,
|
94
|
+
relativeCloseTime
|
95
|
+
}
|
96
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './meet-statuses'
|
@@ -0,0 +1,114 @@
|
|
1
|
+
import { Zeus } from '@coopenomics/sdk'
|
2
|
+
|
3
|
+
// Описания расширенных статусов собрания
|
4
|
+
export const EXTENDED_STATUS_MAP: Record<Zeus.ExtendedMeetStatus, string> = {
|
5
|
+
'NONE': 'Неопределенное состояние',
|
6
|
+
'CREATED': 'Собрание создано. Ожидаем утверждения даты проведения.',
|
7
|
+
'AUTHORIZED': 'Дата общего собрания утверждена. Ожидаем начала собрания.',
|
8
|
+
'ONRESTART': 'Получено предложение новой даты общего собрания. Ожидаем утверждения.',
|
9
|
+
'PRECLOSED': 'Получена подпись секретаря собрания на протоколе. Ожидаем подписи председателя.',
|
10
|
+
'CLOSED': 'Собрание успешно завершено',
|
11
|
+
'WAITING_FOR_OPENING': 'Собрание открывается',
|
12
|
+
'VOTING_IN_PROGRESS': 'Собрание идет и завершится',
|
13
|
+
'EXPIRED_NO_QUORUM': 'Кворум собрания не собран. Ожидаем предложения даты нового собрания.',
|
14
|
+
'VOTING_COMPLETED': 'Собрание успешно завершено. Ожидаем подписи секретаря на протоколе.'
|
15
|
+
}
|
16
|
+
|
17
|
+
// Описания базовых статусов собрания
|
18
|
+
export const BASIC_STATUS_MAP: Record<string, string> = {
|
19
|
+
'created': 'Ожидание решения совета',
|
20
|
+
'authorized': 'Утверждено',
|
21
|
+
'preclosed': 'На закрытии',
|
22
|
+
'closed': 'Закрыто'
|
23
|
+
}
|
24
|
+
|
25
|
+
// Статусы, требующие специальной обработки времени
|
26
|
+
export const SPECIAL_STATUSES: Zeus.ExtendedMeetStatus[] = [
|
27
|
+
Zeus.ExtendedMeetStatus.ONRESTART,
|
28
|
+
Zeus.ExtendedMeetStatus.EXPIRED_NO_QUORUM,
|
29
|
+
Zeus.ExtendedMeetStatus.CLOSED,
|
30
|
+
Zeus.ExtendedMeetStatus.PRECLOSED,
|
31
|
+
Zeus.ExtendedMeetStatus.CREATED
|
32
|
+
]
|
33
|
+
|
34
|
+
// Конфигурация для баннеров статусов собраний
|
35
|
+
// @property {string} class - CSS-класс для стилизации
|
36
|
+
// @property {boolean} needTime - Нужно ли отображать время
|
37
|
+
// @property {string} color - Цвет баннера
|
38
|
+
// @property {boolean} outline - Использовать ли контурный стиль
|
39
|
+
// @property {string} icon - иконка
|
40
|
+
export const STATUS_BANNER_CONFIG: Record<
|
41
|
+
Zeus.ExtendedMeetStatus,
|
42
|
+
{ class: string; needTime: boolean; color: string; outline: boolean; icon: string }
|
43
|
+
> = {
|
44
|
+
NONE: {
|
45
|
+
class: 'text-grey-8',
|
46
|
+
needTime: false,
|
47
|
+
color: 'grey-4',
|
48
|
+
outline: true,
|
49
|
+
icon: 'help_outline'
|
50
|
+
},
|
51
|
+
WAITING_FOR_OPENING: {
|
52
|
+
class: 'text-grey-8',
|
53
|
+
needTime: true,
|
54
|
+
color: 'grey-4',
|
55
|
+
outline: true,
|
56
|
+
icon: 'hourglass_empty'
|
57
|
+
},
|
58
|
+
VOTING_IN_PROGRESS: {
|
59
|
+
class: 'text-primary',
|
60
|
+
needTime: true,
|
61
|
+
color: 'primary',
|
62
|
+
outline: true,
|
63
|
+
icon: 'how_to_vote'
|
64
|
+
},
|
65
|
+
VOTING_COMPLETED: {
|
66
|
+
class: 'text-primary',
|
67
|
+
needTime: false,
|
68
|
+
color: 'primary',
|
69
|
+
outline: true,
|
70
|
+
icon: 'hourglass_bottom'
|
71
|
+
},
|
72
|
+
CLOSED: {
|
73
|
+
class: 'text-primary',
|
74
|
+
needTime: false,
|
75
|
+
color: 'primary',
|
76
|
+
outline: true,
|
77
|
+
icon: 'check_circle'
|
78
|
+
},
|
79
|
+
CREATED: {
|
80
|
+
class: 'text-primary',
|
81
|
+
needTime: false,
|
82
|
+
color: 'primary',
|
83
|
+
outline: true,
|
84
|
+
icon: 'fiber_new'
|
85
|
+
},
|
86
|
+
AUTHORIZED: {
|
87
|
+
class: 'text-primary',
|
88
|
+
needTime: false,
|
89
|
+
color: 'primary',
|
90
|
+
outline: true,
|
91
|
+
icon: 'verified_user'
|
92
|
+
},
|
93
|
+
ONRESTART: {
|
94
|
+
class: 'text-primary',
|
95
|
+
needTime: false,
|
96
|
+
color: 'primary',
|
97
|
+
outline: true,
|
98
|
+
icon: 'sync_problem'
|
99
|
+
},
|
100
|
+
PRECLOSED: {
|
101
|
+
class: 'text-primary',
|
102
|
+
needTime: false,
|
103
|
+
color: 'primary',
|
104
|
+
outline: true,
|
105
|
+
icon: 'schedule'
|
106
|
+
},
|
107
|
+
EXPIRED_NO_QUORUM: {
|
108
|
+
class: 'text-primary',
|
109
|
+
needTime: false,
|
110
|
+
color: 'primary',
|
111
|
+
outline: true,
|
112
|
+
icon: 'group_off'
|
113
|
+
}
|
114
|
+
}
|
@@ -4,6 +4,7 @@ import { useGlobalStore } from 'src/shared/store';
|
|
4
4
|
import type { IDocument, IMetaDocument } from 'src/shared/lib/types/document';
|
5
5
|
import type { Cooperative } from 'cooptypes';
|
6
6
|
import { Classes } from '@coopenomics/sdk';
|
7
|
+
import type { ISignedDocument2 } from 'src/entities/Document/model';
|
7
8
|
|
8
9
|
export type ZGeneratedDocument = Cooperative.Document.ZGeneratedDocument
|
9
10
|
|
@@ -17,6 +18,7 @@ export const useSignDocument = () => {
|
|
17
18
|
document: ZGeneratedDocument,
|
18
19
|
account: string,
|
19
20
|
signatureId = 1,
|
21
|
+
existingSignedDocuments?: ISignedDocument2[],
|
20
22
|
): Promise<Cooperative.Document.ISignedDocument2> => {
|
21
23
|
if (!document)
|
22
24
|
throw new Error('Документ на подпись не предоставлен')
|
@@ -30,7 +32,7 @@ export const useSignDocument = () => {
|
|
30
32
|
|
31
33
|
|
32
34
|
// Получаем доступ к Document классу из SDK
|
33
|
-
return await docSigner.signDocument(document, account, signatureId);
|
35
|
+
return await docSigner.signDocument(document, account, signatureId, existingSignedDocuments);
|
34
36
|
}
|
35
37
|
|
36
38
|
return {
|
@@ -67,7 +69,7 @@ export class DigitalDocument {
|
|
67
69
|
const docSigner = new Classes.Document(wifKey);
|
68
70
|
|
69
71
|
// Подписываем документ с использованием SDK
|
70
|
-
const signedDoc = await docSigner.signDocument
|
72
|
+
const signedDoc = await docSigner.signDocument(this.data, account, signatureId);
|
71
73
|
|
72
74
|
this.signedDocument = signedDoc;
|
73
75
|
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import type { Zeus } from '@coopenomics/sdk'
|
2
|
+
|
3
|
+
export type IUserCertificateUnion = Zeus.ModelTypes['UserCertificateUnion']
|
4
|
+
export type IIndividualCertificate = Zeus.ModelTypes['IndividualCertificate']
|
5
|
+
export type IEntrepreneurCertificate = Zeus.ModelTypes['EntrepreneurCertificate']
|
6
|
+
export type IOrganizationCertificate = Zeus.ModelTypes['OrganizationCertificate']
|
@@ -0,0 +1,24 @@
|
|
1
|
+
export interface IWorkspaceRouteMeta {
|
2
|
+
title: string
|
3
|
+
icon: string
|
4
|
+
roles: string[]
|
5
|
+
agreements?: string[]
|
6
|
+
conditions?: string
|
7
|
+
[key: string]: any
|
8
|
+
}
|
9
|
+
|
10
|
+
export interface IWorkspaceRoute {
|
11
|
+
path: string
|
12
|
+
name: string
|
13
|
+
component?: any
|
14
|
+
meta?: IWorkspaceRouteMeta
|
15
|
+
children?: IWorkspaceRoute[]
|
16
|
+
[key: string]: any
|
17
|
+
}
|
18
|
+
|
19
|
+
export interface IWorkspaceConfig {
|
20
|
+
workspace: string
|
21
|
+
title?: string
|
22
|
+
defaultRoute?: string // Имя маршрута для перехода по умолчанию
|
23
|
+
routes: IWorkspaceRoute[]
|
24
|
+
}
|
@@ -1,6 +1,47 @@
|
|
1
|
-
import moment from 'moment-
|
1
|
+
import moment from 'moment-timezone';
|
2
2
|
|
3
|
-
//
|
3
|
+
// Добавляем русскую локализацию вручную
|
4
|
+
moment.updateLocale('ru', {
|
5
|
+
months: 'Январь_Февраль_Март_Апрель_Май_Июнь_Июль_Август_Сентябрь_Октябрь_Ноябрь_Декабрь'.split('_'),
|
6
|
+
monthsShort: 'янв_фев_мар_апр_май_июн_июл_авг_сен_окт_ноя_дек'.split('_'),
|
7
|
+
weekdays: 'Воскресенье_Понедельник_Вторник_Среда_Четверг_Пятница_Суббота'.split('_'),
|
8
|
+
weekdaysShort: 'Вс_Пн_Вт_Ср_Чт_Пт_Сб'.split('_'),
|
9
|
+
weekdaysMin: 'Вс_Пн_Вт_Ср_Чт_Пт_Сб'.split('_'),
|
10
|
+
longDateFormat: {
|
11
|
+
LT: 'HH:mm',
|
12
|
+
LTS: 'HH:mm:ss',
|
13
|
+
L: 'DD.MM.YYYY',
|
14
|
+
LL: 'D MMMM YYYY',
|
15
|
+
LLL: 'D MMMM YYYY HH:mm',
|
16
|
+
LLLL: 'dddd, D MMMM YYYY HH:mm'
|
17
|
+
},
|
18
|
+
calendar: {
|
19
|
+
sameDay: '[Сегодня в] LT',
|
20
|
+
nextDay: '[Завтра в] LT',
|
21
|
+
nextWeek: '[В] dddd [в] LT',
|
22
|
+
lastDay: '[Вчера в] LT',
|
23
|
+
lastWeek: '[В прошлую] dddd [в] LT',
|
24
|
+
sameElse: 'L'
|
25
|
+
},
|
26
|
+
relativeTime: {
|
27
|
+
future: 'через %s',
|
28
|
+
past: '%s назад',
|
29
|
+
s: 'несколько секунд',
|
30
|
+
ss: '%d секунд',
|
31
|
+
m: 'минуту',
|
32
|
+
mm: '%d минуты',
|
33
|
+
h: 'час',
|
34
|
+
hh: '%d час',
|
35
|
+
d: 'день',
|
36
|
+
dd: '%d дней',
|
37
|
+
M: 'месяц',
|
38
|
+
MM: '%d месяцев',
|
39
|
+
y: 'год',
|
40
|
+
yy: '%d лет'
|
41
|
+
}
|
42
|
+
});
|
43
|
+
|
44
|
+
// Устанавливаем локаль по умолчанию
|
4
45
|
moment.locale('ru');
|
5
46
|
|
6
47
|
export default moment;
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import moment from './moment'
|
2
|
+
import { env } from 'src/shared/config/Environment'
|
3
|
+
|
4
|
+
// Дефолтный часовой пояс - московский
|
5
|
+
const DEFAULT_TIMEZONE = 'Europe/Moscow'
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Получает часовой пояс из .env или возвращает московский по умолчанию
|
9
|
+
*/
|
10
|
+
export function getTimezone(): string {
|
11
|
+
return env.TIMEZONE || DEFAULT_TIMEZONE
|
12
|
+
}
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Получает читаемое название часового пояса для отображения в UI
|
16
|
+
*/
|
17
|
+
export function getTimezoneLabel(): string {
|
18
|
+
const tz = getTimezone()
|
19
|
+
return tz === 'Europe/Moscow' ? 'МСК' : tz
|
20
|
+
}
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Преобразует дату из UTC в локальный часовой пояс для отображения
|
24
|
+
* @param utcDate - дата в UTC (из блокчейна)
|
25
|
+
* @param format - формат отображения
|
26
|
+
*/
|
27
|
+
export function formatDateToLocalTimezone(utcDate: string | Date | unknown, format = 'DD.MM.YYYY HH:mm'): string {
|
28
|
+
if (!utcDate || typeof utcDate !== 'string' && !(utcDate instanceof Date)) return ''
|
29
|
+
|
30
|
+
const timezone = getTimezone()
|
31
|
+
return moment.utc(utcDate).tz(timezone).format(format)
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Преобразует локальную дату (из формы) в UTC для отправки в блокчейн
|
36
|
+
* @param localDate - дата в локальном часовом поясе
|
37
|
+
* @param format - формат входной даты
|
38
|
+
*/
|
39
|
+
export function convertLocalDateToUTC(localDate: string, format = 'YYYY-MM-DDTHH:mm'): string {
|
40
|
+
if (!localDate) return ''
|
41
|
+
|
42
|
+
const timezone = getTimezone()
|
43
|
+
return moment.tz(localDate, format, timezone).utc().format()
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Получает текущую дату в локальном часовом поясе для форм (формат datetime-local)
|
48
|
+
* @param offsetMinutes - смещение от текущего времени в минутах
|
49
|
+
*/
|
50
|
+
export function getCurrentLocalDateForForm(offsetMinutes = 0): string {
|
51
|
+
const timezone = getTimezone()
|
52
|
+
return moment().tz(timezone).add(offsetMinutes, 'minutes').format('YYYY-MM-DDTHH:mm')
|
53
|
+
}
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Форматирует дату для отображения "от сейчас" с учетом часового пояса
|
57
|
+
* @param utcDate - дата в UTC
|
58
|
+
*/
|
59
|
+
export function formatDateFromNow(utcDate: string | Date | unknown): string {
|
60
|
+
if (!utcDate || typeof utcDate !== 'string' && !(utcDate instanceof Date)) return ''
|
61
|
+
|
62
|
+
const timezone = getTimezone()
|
63
|
+
return moment.utc(utcDate).tz(timezone).fromNow()
|
64
|
+
}
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Получает дату в локальном часовом поясе для форм (формат datetime-local) с заданным смещением в днях и временем
|
68
|
+
* @param offsetDays - смещение от текущей даты в днях
|
69
|
+
* @param hour - час (0-23)
|
70
|
+
* @param minute - минута (0-59)
|
71
|
+
*/
|
72
|
+
export function getFutureDateForForm(offsetDays = 0, hour = 0, minute = 0): string {
|
73
|
+
const timezone = getTimezone()
|
74
|
+
return moment().tz(timezone).add(offsetDays, 'days').set({ hour, minute, second: 0, millisecond: 0 }).format('YYYY-MM-DDTHH:mm')
|
75
|
+
}
|
@@ -0,0 +1,108 @@
|
|
1
|
+
import {
|
2
|
+
IIndividualCertificate,
|
3
|
+
IEntrepreneurCertificate,
|
4
|
+
IOrganizationCertificate
|
5
|
+
} from '../types/certificate'
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Определяет тип сертификата и возвращает строку с именем пользователя
|
9
|
+
*/
|
10
|
+
export function getNameFromCertificate(
|
11
|
+
certificate: IIndividualCertificate | IEntrepreneurCertificate | IOrganizationCertificate | null | undefined
|
12
|
+
): string {
|
13
|
+
if (!certificate) return ''
|
14
|
+
|
15
|
+
// Определение типа сертификата
|
16
|
+
if (isOrganizationCertificate(certificate)) {
|
17
|
+
// Для организаций возвращаем короткое имя
|
18
|
+
return certificate.short_name
|
19
|
+
} else if (isIndividualCertificate(certificate) || isEntrepreneurCertificate(certificate)) {
|
20
|
+
// Для физ. лиц и ИП возвращаем ФИО
|
21
|
+
return formatFullName(certificate)
|
22
|
+
}
|
23
|
+
|
24
|
+
return ''
|
25
|
+
}
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Определяет тип сертификата и возвращает сокращенное имя (Фамилия И.О.)
|
29
|
+
*/
|
30
|
+
export function getShortNameFromCertificate(
|
31
|
+
certificate: IIndividualCertificate | IEntrepreneurCertificate | IOrganizationCertificate | null | undefined
|
32
|
+
): string {
|
33
|
+
if (!certificate) return ''
|
34
|
+
|
35
|
+
// Определение типа сертификата
|
36
|
+
if (isOrganizationCertificate(certificate)) {
|
37
|
+
// Для организаций возвращаем короткое имя
|
38
|
+
return certificate.short_name
|
39
|
+
} else if (isIndividualCertificate(certificate) || isEntrepreneurCertificate(certificate)) {
|
40
|
+
// Для физ. лиц и ИП возвращаем сокращенное ФИО
|
41
|
+
return formatShortName(certificate)
|
42
|
+
}
|
43
|
+
|
44
|
+
return ''
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Проверяет, является ли сертификат сертификатом физического лица
|
49
|
+
*/
|
50
|
+
function isIndividualCertificate(
|
51
|
+
certificate: any
|
52
|
+
): certificate is IIndividualCertificate {
|
53
|
+
return certificate.first_name !== undefined &&
|
54
|
+
certificate.last_name !== undefined &&
|
55
|
+
!('inn' in certificate) &&
|
56
|
+
!('ogrn' in certificate)
|
57
|
+
}
|
58
|
+
|
59
|
+
/**
|
60
|
+
* Проверяет, является ли сертификат сертификатом ИП
|
61
|
+
*/
|
62
|
+
function isEntrepreneurCertificate(
|
63
|
+
certificate: any
|
64
|
+
): certificate is IEntrepreneurCertificate {
|
65
|
+
return certificate.first_name !== undefined &&
|
66
|
+
certificate.last_name !== undefined &&
|
67
|
+
'inn' in certificate &&
|
68
|
+
!('ogrn' in certificate)
|
69
|
+
}
|
70
|
+
|
71
|
+
/**
|
72
|
+
* Проверяет, является ли сертификат сертификатом организации
|
73
|
+
*/
|
74
|
+
function isOrganizationCertificate(
|
75
|
+
certificate: any
|
76
|
+
): certificate is IOrganizationCertificate {
|
77
|
+
return 'short_name' in certificate && 'inn' in certificate && 'ogrn' in certificate
|
78
|
+
}
|
79
|
+
|
80
|
+
/**
|
81
|
+
* Форматирует ФИО из объекта с полями first_name, last_name и middle_name
|
82
|
+
*/
|
83
|
+
function formatFullName(
|
84
|
+
data: { first_name: string; last_name: string; middle_name?: string | null }
|
85
|
+
): string {
|
86
|
+
const { last_name, first_name, middle_name } = data
|
87
|
+
|
88
|
+
if (middle_name) {
|
89
|
+
return `${last_name} ${first_name} ${middle_name}`
|
90
|
+
}
|
91
|
+
|
92
|
+
return `${last_name} ${first_name}`
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
* Форматирует сокращенное ФИО из объекта с полями first_name, last_name и middle_name
|
97
|
+
* Возвращает "Фамилия И.О."
|
98
|
+
*/
|
99
|
+
function formatShortName(
|
100
|
+
data: { first_name: string; last_name: string; middle_name?: string | null }
|
101
|
+
): string {
|
102
|
+
const { last_name, first_name, middle_name } = data
|
103
|
+
|
104
|
+
const firstInitial = first_name.charAt(0).toUpperCase()
|
105
|
+
const middleInitial = middle_name ? middle_name.charAt(0).toUpperCase() + '.' : ''
|
106
|
+
|
107
|
+
return `${last_name} ${firstInitial}.${middleInitial}`
|
108
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
// Преобразует ссылки вида @https://... или https://... или http://... в интерактивные <a>
|
2
|
+
export function parseLinks(text = ''): string {
|
3
|
+
if (!text) return ''
|
4
|
+
return text
|
5
|
+
.replace(/@?(https?:\/\/[^\s]+)/g, (match, url) => {
|
6
|
+
const cleanUrl = url.startsWith('http') ? url : url.slice(1)
|
7
|
+
return `<a href="${cleanUrl}" target="_blank" rel="noopener noreferrer">${cleanUrl}</a>`
|
8
|
+
})
|
9
|
+
.replace(/\n/g, '<br>')
|
10
|
+
}
|