@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
@@ -0,0 +1,180 @@
|
|
1
|
+
import { ref, computed } from 'vue'
|
2
|
+
import { client } from 'src/shared/api/client'
|
3
|
+
import { Mutations, Zeus } from '@coopenomics/sdk'
|
4
|
+
import moment from 'moment-with-locales-es6'
|
5
|
+
import { useMeetStore } from 'src/entities/Meet'
|
6
|
+
import type { IMeet } from 'src/entities/Meet'
|
7
|
+
import type { IVoteOnMeetInput, IVoteOnMeetResult } from './types'
|
8
|
+
import { formatDateToLocalTimezone } from 'src/shared/lib/utils/dates/timezone'
|
9
|
+
|
10
|
+
moment.locale('ru')
|
11
|
+
|
12
|
+
export type IGenerateBallotInput = Mutations.Meet.GenerateBallotForAnnualGeneralMeetDocument.IInput['data'];
|
13
|
+
export type IGenerateBallotResult = Mutations.Meet.GenerateBallotForAnnualGeneralMeetDocument.IOutput[typeof Mutations.Meet.GenerateBallotForAnnualGeneralMeetDocument.name];
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Генерирует бюллетень для голосования
|
17
|
+
* @private Внутренняя функция, не экспортируется
|
18
|
+
*/
|
19
|
+
async function generateBallot(data: IGenerateBallotInput, options?: any): Promise<IGenerateBallotResult> {
|
20
|
+
const { answers: answersFromInput, ...restData } = data
|
21
|
+
|
22
|
+
// Подготавливаем массив ответов из голосов
|
23
|
+
const meetStore = useMeetStore()
|
24
|
+
const meet = await meetStore.loadMeet({
|
25
|
+
coopname: data.coopname,
|
26
|
+
hash: data.meet_hash
|
27
|
+
})
|
28
|
+
|
29
|
+
if (!meet?.processing?.questions?.length) {
|
30
|
+
throw new Error('Не удалось получить вопросы собрания')
|
31
|
+
}
|
32
|
+
|
33
|
+
// Создаем массив ответов на основе вопросов и голосов
|
34
|
+
const answers = meet.processing.questions.map((question, index) => {
|
35
|
+
const questionId = typeof question.id === 'number' ? question.id.toString() : question.id
|
36
|
+
const questionNumber = typeof question.number === 'number' ? question.number.toString() : question.number
|
37
|
+
|
38
|
+
return {
|
39
|
+
id: questionId,
|
40
|
+
number: questionNumber,
|
41
|
+
vote: answersFromInput[index].vote
|
42
|
+
}
|
43
|
+
})
|
44
|
+
|
45
|
+
const { [Mutations.Meet.GenerateBallotForAnnualGeneralMeetDocument.name]: generatedDocument } = await client.Mutation(
|
46
|
+
Mutations.Meet.GenerateBallotForAnnualGeneralMeetDocument.mutation,
|
47
|
+
{
|
48
|
+
variables: {
|
49
|
+
data: {
|
50
|
+
...restData,
|
51
|
+
answers
|
52
|
+
},
|
53
|
+
options
|
54
|
+
}
|
55
|
+
}
|
56
|
+
);
|
57
|
+
|
58
|
+
return generatedDocument;
|
59
|
+
}
|
60
|
+
|
61
|
+
export function useVoteOnMeet() {
|
62
|
+
// Стор встреч для доступа к данным и методам загрузки
|
63
|
+
const meetStore = useMeetStore()
|
64
|
+
|
65
|
+
// Локальное состояние голосов
|
66
|
+
const votes = ref<Record<number, 'for' | 'against' | 'abstained'>>({})
|
67
|
+
|
68
|
+
// Текущее собрание для голосования (может быть из стора или переданное)
|
69
|
+
const currentVotingMeet = ref<IMeet | null>(null)
|
70
|
+
|
71
|
+
// Получаем активное собрание, приоритет у переданного собрания
|
72
|
+
const activeMeet = computed<IMeet | null>(() => {
|
73
|
+
return currentVotingMeet.value || meetStore.currentMeet
|
74
|
+
})
|
75
|
+
|
76
|
+
// Устанавливаем собрание для голосования
|
77
|
+
const setMeet = (meet: IMeet | null) => {
|
78
|
+
currentVotingMeet.value = meet
|
79
|
+
}
|
80
|
+
|
81
|
+
// Селекторы (computed)
|
82
|
+
const meetAgendaItems = computed(() => {
|
83
|
+
if (!activeMeet.value) return []
|
84
|
+
return activeMeet.value.processing?.questions || []
|
85
|
+
})
|
86
|
+
|
87
|
+
const allVotesSelected = computed(() => {
|
88
|
+
if (!meetAgendaItems.value.length) return false
|
89
|
+
return meetAgendaItems.value.every((_, index) => votes.value[index] !== undefined)
|
90
|
+
})
|
91
|
+
|
92
|
+
const isVotingNow = computed(() => {
|
93
|
+
if (activeMeet.value?.processing?.extendedStatus === Zeus.ExtendedMeetStatus.VOTING_IN_PROGRESS) return true
|
94
|
+
else return false
|
95
|
+
})
|
96
|
+
|
97
|
+
const isVotingNotStarted = computed(() => {
|
98
|
+
if (activeMeet.value?.processing?.extendedStatus === Zeus.ExtendedMeetStatus.WAITING_FOR_OPENING) return true
|
99
|
+
else return false
|
100
|
+
})
|
101
|
+
|
102
|
+
const isVotingEnded = computed(() => {
|
103
|
+
if (activeMeet.value?.processing?.extendedStatus === Zeus.ExtendedMeetStatus.VOTING_COMPLETED) return true
|
104
|
+
else return false
|
105
|
+
})
|
106
|
+
|
107
|
+
const showQuorumIndicator = computed(() => {
|
108
|
+
if (!activeMeet.value) return false
|
109
|
+
const status = activeMeet.value?.processing?.extendedStatus
|
110
|
+
return status === Zeus.ExtendedMeetStatus.VOTING_IN_PROGRESS
|
111
|
+
})
|
112
|
+
|
113
|
+
const formattedOpenDate = computed(() => {
|
114
|
+
if (!activeMeet.value?.processing?.meet?.open_at) return ''
|
115
|
+
return formatDateToLocalTimezone(activeMeet.value.processing.meet.open_at)
|
116
|
+
})
|
117
|
+
|
118
|
+
const formattedCloseDate = computed(() => {
|
119
|
+
if (!activeMeet.value?.processing?.meet?.close_at) return ''
|
120
|
+
return formatDateToLocalTimezone(activeMeet.value.processing.meet.close_at)
|
121
|
+
})
|
122
|
+
|
123
|
+
// Экшены
|
124
|
+
const resetVotes = () => {
|
125
|
+
votes.value = {}
|
126
|
+
}
|
127
|
+
|
128
|
+
const voteOnMeet = async (data: IVoteOnMeetInput): Promise<IVoteOnMeetResult> => {
|
129
|
+
const { [Mutations.Meet.VoteOnAnnualGeneralMeet.name]: result } = await client.Mutation(
|
130
|
+
Mutations.Meet.VoteOnAnnualGeneralMeet.mutation,
|
131
|
+
{
|
132
|
+
variables: {
|
133
|
+
data
|
134
|
+
}
|
135
|
+
}
|
136
|
+
)
|
137
|
+
|
138
|
+
// Обновляем данные собрания после голосования
|
139
|
+
if (data.coopname && data.hash) {
|
140
|
+
// Обновляем данные в сторе собраний
|
141
|
+
const updatedMeet = await meetStore.loadMeet({
|
142
|
+
coopname: data.coopname,
|
143
|
+
hash: data.hash
|
144
|
+
})
|
145
|
+
|
146
|
+
// Обновляем данные в локальном сторе, если используется отдельное собрание
|
147
|
+
if (currentVotingMeet.value) {
|
148
|
+
currentVotingMeet.value = updatedMeet
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
// Сбрасываем локальное состояние голосов
|
153
|
+
resetVotes()
|
154
|
+
|
155
|
+
return result
|
156
|
+
}
|
157
|
+
|
158
|
+
return {
|
159
|
+
// state
|
160
|
+
votes,
|
161
|
+
currentVotingMeet,
|
162
|
+
|
163
|
+
// getters
|
164
|
+
activeMeet,
|
165
|
+
meetAgendaItems,
|
166
|
+
isVotingNow,
|
167
|
+
allVotesSelected,
|
168
|
+
isVotingNotStarted,
|
169
|
+
isVotingEnded,
|
170
|
+
showQuorumIndicator,
|
171
|
+
formattedOpenDate,
|
172
|
+
formattedCloseDate,
|
173
|
+
|
174
|
+
// actions
|
175
|
+
setMeet,
|
176
|
+
voteOnMeet,
|
177
|
+
resetVotes,
|
178
|
+
generateBallot
|
179
|
+
}
|
180
|
+
}
|
@@ -1,18 +1,3 @@
|
|
1
|
-
import { client } from 'src/shared/api/client';
|
2
|
-
import { Mutations } from '@coopenomics/sdk';
|
3
1
|
|
4
|
-
export
|
5
|
-
export
|
6
|
-
|
7
|
-
export async function voteOnMeet(data: IVoteOnMeetInput): Promise<IVoteOnMeetResult> {
|
8
|
-
const { [Mutations.Meet.VoteOnAnnualGeneralMeet.name]: result } = await client.Mutation(
|
9
|
-
Mutations.Meet.VoteOnAnnualGeneralMeet.mutation,
|
10
|
-
{
|
11
|
-
variables: {
|
12
|
-
data
|
13
|
-
}
|
14
|
-
}
|
15
|
-
);
|
16
|
-
|
17
|
-
return result;
|
18
|
-
}
|
2
|
+
export * from './composable'
|
3
|
+
export * from './types'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<template lang="pug">
|
2
|
+
.q-pa-md.flex.flex-center
|
3
|
+
.column.q-gutter-md(style="max-width: 400px")
|
4
|
+
|
5
|
+
q-card(flat).q-pa-md
|
6
|
+
.text-h6.text-center ПОЖАЛУЙСТА, ВОЙДИТЕ, ЧТОБЫ ПРОДОЛЖИТЬ
|
7
|
+
//- .text-body1.text-center Пожалуйста, войдите в свой аккаунт, чтобы продолжить.
|
8
|
+
|
9
|
+
|
10
|
+
login-form
|
11
|
+
</template>
|
12
|
+
|
13
|
+
<script lang="ts" setup>
|
14
|
+
import { LoginForm } from 'src/features/User/LoginUser/ui/LoginForm'
|
15
|
+
</script>
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as LoginRedirectPage } from './LoginRedirectPage.vue'
|
@@ -41,6 +41,8 @@ import { useLoginUser } from 'src/features/User/LoginUser';
|
|
41
41
|
import { FailAlert } from 'src/shared/api';
|
42
42
|
import { ref } from 'vue';
|
43
43
|
import { useRouter } from 'vue-router';
|
44
|
+
import { useDesktopStore } from 'src/entities/Desktop/model';
|
45
|
+
import { LocalStorage } from 'quasar';
|
44
46
|
|
45
47
|
const router = useRouter()
|
46
48
|
|
@@ -49,6 +51,35 @@ const privateKey = ref('')
|
|
49
51
|
const loading = ref(false)
|
50
52
|
const currentUser = useCurrentUserStore()
|
51
53
|
|
54
|
+
/**
|
55
|
+
* Функция для перехода по сохраненному URL после успешного входа
|
56
|
+
*/
|
57
|
+
function navigateToSavedUrl() {
|
58
|
+
if (process.env.CLIENT) {
|
59
|
+
// Проверяем наличие сохраненного URL для редиректа
|
60
|
+
const redirectUrl = LocalStorage.getItem('redirectAfterLogin') as string
|
61
|
+
console.log('login form redirect url', redirectUrl)
|
62
|
+
|
63
|
+
if (redirectUrl) {
|
64
|
+
// Удаляем сохраненный URL
|
65
|
+
LocalStorage.remove('redirectAfterLogin')
|
66
|
+
|
67
|
+
try {
|
68
|
+
// Пытаемся использовать router для навигации
|
69
|
+
const url = new URL(redirectUrl)
|
70
|
+
const path = url.pathname + url.search
|
71
|
+
console.log('Navigating with router to', path)
|
72
|
+
router.push(path)
|
73
|
+
} catch (e) {
|
74
|
+
console.error('Error parsing URL, using direct navigation', e)
|
75
|
+
window.location.href = redirectUrl
|
76
|
+
}
|
77
|
+
|
78
|
+
return true
|
79
|
+
}
|
80
|
+
}
|
81
|
+
return false
|
82
|
+
}
|
52
83
|
|
53
84
|
const submit = async () => {
|
54
85
|
loading.value = true
|
@@ -59,7 +90,13 @@ const submit = async () => {
|
|
59
90
|
if (!currentUser.isRegistrationComplete) {
|
60
91
|
router.push({ name: 'signup' })
|
61
92
|
} else {
|
62
|
-
|
93
|
+
// Пробуем перейти по сохраненному URL
|
94
|
+
if (!navigateToSavedUrl()) {
|
95
|
+
// Если сохраненного URL нет, переходим на страницу по умолчанию
|
96
|
+
const desktops = useDesktopStore()
|
97
|
+
desktops.selectDefaultWorkspace()
|
98
|
+
desktops.goToDefaultPage(router)
|
99
|
+
}
|
63
100
|
}
|
64
101
|
|
65
102
|
loading.value = false
|
File without changes
|
@@ -1,69 +1,44 @@
|
|
1
1
|
<template lang="pug">
|
2
|
-
div
|
2
|
+
div
|
3
3
|
router-view(v-if="$route.name !== 'user-meets' && $route.name !== 'meets'")
|
4
4
|
template(v-else)
|
5
5
|
div.row.justify-center
|
6
6
|
div.col-12
|
7
|
-
div.row.q-
|
8
|
-
CreateMeet
|
9
|
-
|
10
|
-
MeetsTable(
|
7
|
+
div.row.q-pa-md(v-if="canCreateMeet")
|
8
|
+
CreateMeet
|
9
|
+
MeetCardsList(
|
11
10
|
:meets="meets"
|
12
11
|
:loading="loading"
|
13
|
-
@vote="handleVote"
|
14
|
-
@close="handleCloseMeet"
|
15
|
-
@restart="showRestartMeetDialog"
|
16
|
-
@view="navigateToMeetDetails"
|
17
12
|
)
|
18
|
-
|
13
|
+
|
19
14
|
</template>
|
20
15
|
|
21
16
|
<script setup lang="ts">
|
22
|
-
import { onMounted,
|
23
|
-
import { useRoute
|
24
|
-
import {
|
17
|
+
import { onMounted, computed, watch } from 'vue'
|
18
|
+
import { useRoute } from 'vue-router'
|
19
|
+
import { MeetCardsList } from 'src/widgets/Meets/MeetCardsList'
|
25
20
|
import { CreateMeet } from 'src/features/Meet/CreateMeet'
|
26
|
-
import {
|
27
|
-
import type { IMeet } from 'src/entities/Meet'
|
21
|
+
import { useMeetStore } from 'src/entities/Meet'
|
28
22
|
import { useCurrentUserStore } from 'src/entities/User'
|
23
|
+
import { FailAlert } from 'src/shared/api'
|
29
24
|
|
30
25
|
const route = useRoute()
|
31
|
-
const router = useRouter()
|
32
26
|
const coopname = computed(() => route.params.coopname as string)
|
27
|
+
const meetStore = useMeetStore()
|
33
28
|
|
34
29
|
const {isChairman, isMember} = useCurrentUserStore()
|
35
30
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
loadMeets,
|
40
|
-
handleCreateMeet,
|
41
|
-
handleCloseMeet,
|
42
|
-
handleVote
|
43
|
-
} = useMeetManagement(coopname.value)
|
44
|
-
|
45
|
-
// Диалоги
|
46
|
-
const currentMeetToRestart = ref<IMeet | null>(null)
|
47
|
-
|
48
|
-
// Обработчики
|
49
|
-
const handleCreate = async (formData: any) => {
|
50
|
-
const success = await handleCreateMeet(formData)
|
51
|
-
return success
|
52
|
-
}
|
53
|
-
|
54
|
-
const showRestartMeetDialog = (meet: IMeet) => {
|
55
|
-
currentMeetToRestart.value = meet
|
56
|
-
}
|
31
|
+
// Данные напрямую из стора
|
32
|
+
const meets = computed(() => meetStore.meets)
|
33
|
+
const loading = computed(() => meetStore.loading)
|
57
34
|
|
58
|
-
//
|
59
|
-
const
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
}
|
66
|
-
})
|
35
|
+
// Загрузка списка собраний
|
36
|
+
const loadMeets = async () => {
|
37
|
+
try {
|
38
|
+
await meetStore.loadMeets({ coopname: coopname.value })
|
39
|
+
} catch (e: any) {
|
40
|
+
FailAlert(e)
|
41
|
+
}
|
67
42
|
}
|
68
43
|
|
69
44
|
// Проверка разрешений
|
@@ -87,4 +62,5 @@ watch(
|
|
87
62
|
}
|
88
63
|
}
|
89
64
|
)
|
65
|
+
|
90
66
|
</script>
|
@@ -1,47 +1,74 @@
|
|
1
1
|
<template lang="pug">
|
2
|
-
|
2
|
+
q-card(flat).card-container.q-pa-md
|
3
3
|
div(v-if="loading")
|
4
4
|
q-skeleton(type="rect" height="200px" class="q-mb-md")
|
5
5
|
q-skeleton(type="rect" height="100px" v-for="i in 3" :key="i" class="q-mb-md")
|
6
6
|
|
7
|
+
|
7
8
|
div(v-else-if="!meet")
|
8
9
|
div.text-h5.text-center Собрание не найдено
|
9
10
|
|
11
|
+
|
10
12
|
div(v-else)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
13
|
+
div.row.q-col-gutter-md.justify-center
|
14
|
+
div.col-12.col-md-12
|
15
|
+
div.info-card.hover
|
16
|
+
MeetDetailsInfo(:meet="meet")
|
17
|
+
template(#actions)
|
18
|
+
MeetDetailsActions(
|
19
|
+
:meet="meet"
|
20
|
+
:coopname="coopname"
|
21
|
+
:meet-hash="meetHash"
|
22
|
+
)
|
23
|
+
|
24
|
+
// Индикатор явки и статус собрания
|
25
|
+
q-card(v-if="showQuorumIndicator" flat).q-mt-lg
|
26
|
+
MeetQuorumIndicator(:meet="meet")
|
27
|
+
|
28
|
+
// Показываем результаты собрания, если оно завершено
|
29
|
+
template(v-if="isProcessed")
|
30
|
+
q-card(flat).info-card.hover.q-mt-lg
|
31
|
+
MeetDetailsResults(
|
32
|
+
:meet="meet"
|
33
|
+
)
|
34
|
+
|
35
|
+
// Показываем повестку и голосование, если собрание еще не завершено
|
36
|
+
template(v-else)
|
37
|
+
q-card(v-if="showAgenda || isVotingNow" flat).info-card.hover.q-mt-lg
|
38
|
+
// Показываем повестку, если голосование еще не началось
|
39
|
+
template(v-if="showAgenda")
|
40
|
+
MeetDetailsAgenda(
|
41
|
+
:meet="meet"
|
42
|
+
:coopname="coopname"
|
43
|
+
:meet-hash="meetHash"
|
44
|
+
)
|
45
|
+
|
46
|
+
// Показываем голосование, если оно началось
|
47
|
+
template(v-if="isVotingNow")
|
48
|
+
MeetDetailsVoting(
|
49
|
+
:meet="meet"
|
50
|
+
:coopname="coopname"
|
51
|
+
:meet-hash="meetHash"
|
52
|
+
)
|
53
|
+
|
54
|
+
|
31
55
|
</template>
|
32
56
|
|
33
57
|
<script setup lang="ts">
|
34
|
-
import { onMounted, ref, computed } from 'vue'
|
58
|
+
import { onMounted, ref, computed, onUnmounted } from 'vue'
|
35
59
|
import { useRoute } from 'vue-router'
|
36
|
-
import {
|
60
|
+
import { MeetDetailsInfo } from 'src/widgets/Meets/MeetDetailsInfo'
|
37
61
|
import { MeetDetailsActions } from 'src/widgets/Meets/MeetDetailsActions'
|
38
62
|
import { MeetDetailsAgenda } from 'src/widgets/Meets/MeetDetailsAgenda'
|
39
63
|
import { MeetDetailsVoting } from 'src/widgets/Meets/MeetDetailsVoting'
|
64
|
+
import { MeetDetailsResults } from 'src/widgets/Meets/MeetDetailsResults'
|
40
65
|
import { useMeetStore } from 'src/entities/Meet'
|
41
66
|
import { FailAlert } from 'src/shared/api'
|
42
|
-
import type { IMeet } from 'src/entities/Meet'
|
43
67
|
import { useDesktopStore } from 'src/entities/Desktop/model'
|
44
68
|
import { useBackButton } from 'src/shared/lib/navigation'
|
69
|
+
import { useVoteOnMeet } from 'src/features/Meet/VoteOnMeet'
|
70
|
+
import { MeetQuorumIndicator } from 'src/widgets/Meets/MeetQuorumIndicator'
|
71
|
+
import { Zeus } from '@coopenomics/sdk'
|
45
72
|
|
46
73
|
const route = useRoute()
|
47
74
|
const meetStore = useMeetStore()
|
@@ -50,17 +77,35 @@ const desktopStore = useDesktopStore()
|
|
50
77
|
const coopname = computed(() => route.params.coopname as string)
|
51
78
|
const meetHash = computed(() => route.params.hash as string)
|
52
79
|
|
53
|
-
const meet =
|
80
|
+
const meet = computed(() => meetStore.currentMeet)
|
54
81
|
const loading = ref(true)
|
55
82
|
|
83
|
+
// Проверяем, завершено ли собрание
|
84
|
+
const isProcessed = computed(() => {
|
85
|
+
return !!meet.value?.processed
|
86
|
+
})
|
87
|
+
|
88
|
+
const showAgenda = computed(() => {
|
89
|
+
return meet.value?.processing?.extendedStatus === Zeus.ExtendedMeetStatus.WAITING_FOR_OPENING
|
90
|
+
})
|
91
|
+
|
92
|
+
// Используем хук для управления голосованием
|
93
|
+
const { isVotingNow, setMeet, showQuorumIndicator } = useVoteOnMeet()
|
94
|
+
|
95
|
+
let intervalId: ReturnType<typeof setInterval> | null = null
|
96
|
+
|
56
97
|
const loadMeetDetails = async () => {
|
57
|
-
loading.value = true
|
58
98
|
try {
|
59
|
-
|
99
|
+
await meetStore.loadMeet({
|
60
100
|
coopname: coopname.value,
|
61
101
|
hash: meetHash.value
|
62
102
|
})
|
63
|
-
|
103
|
+
|
104
|
+
// Устанавливаем собрание в композабл после загрузки
|
105
|
+
if (meet.value) {
|
106
|
+
setMeet(meet.value)
|
107
|
+
}
|
108
|
+
|
64
109
|
} catch (error: any) {
|
65
110
|
FailAlert(error)
|
66
111
|
} finally {
|
@@ -83,6 +128,17 @@ useBackButton({
|
|
83
128
|
|
84
129
|
// Загрузка деталей собрания
|
85
130
|
onMounted(() => {
|
131
|
+
loading.value = true
|
86
132
|
loadMeetDetails()
|
133
|
+
intervalId = setInterval(() => {
|
134
|
+
loadMeetDetails()
|
135
|
+
}, 15000)
|
136
|
+
})
|
137
|
+
|
138
|
+
onUnmounted(() => {
|
139
|
+
if (intervalId) {
|
140
|
+
clearInterval(intervalId)
|
141
|
+
intervalId = null
|
142
|
+
}
|
87
143
|
})
|
88
144
|
</script>
|
@@ -8,7 +8,7 @@ div.row.justify-center
|
|
8
8
|
div(class="text-h4") Недостаточно прав доступа
|
9
9
|
|
10
10
|
q-card-actions(align="center")
|
11
|
-
q-btn(label="Вернуться" color="
|
11
|
+
q-btn(label="Вернуться" color="primary" icon="fa fa-arrow-left" @click="goBack")
|
12
12
|
</template>
|
13
13
|
<script lang="ts" setup>
|
14
14
|
import { useRouter } from 'vue-router'
|
@@ -6,6 +6,7 @@ import { useBranchOverlayProcess } from '../watch-branch-overlay'
|
|
6
6
|
import { setupNavigationGuard } from '../navigation-guard-setup'
|
7
7
|
import { useInitExtensionsProcess } from 'src/processes/init-installed-extensions'
|
8
8
|
import { applyThemeFromStorage } from 'src/shared/lib/utils'
|
9
|
+
import { useSessionStore } from 'src/entities/Session'
|
9
10
|
|
10
11
|
export async function useInitAppProcess(router: Router) {
|
11
12
|
applyThemeFromStorage()
|
@@ -16,14 +17,20 @@ export async function useInitAppProcess(router: Router) {
|
|
16
17
|
await desktops.healthCheck()
|
17
18
|
await desktops.loadDesktop()
|
18
19
|
|
19
|
-
//
|
20
|
-
desktops.
|
20
|
+
// Регистрируем маршруты рабочего стола до выбора активного рабочего стола
|
21
|
+
desktops.registerWorkspaceMenus(router)
|
21
22
|
|
22
|
-
useBranchOverlayProcess()
|
23
23
|
await useInitWalletProcess().run()
|
24
24
|
|
25
|
-
//
|
26
|
-
|
25
|
+
// Выбираем рабочий стол на основе прав пользователя или сохраненного выбора
|
26
|
+
// только если пользователь авторизован
|
27
|
+
const session = useSessionStore()
|
28
|
+
if (session.isAuth) {
|
29
|
+
desktops.selectDefaultWorkspace()
|
30
|
+
}
|
31
|
+
|
32
|
+
useBranchOverlayProcess()
|
33
|
+
|
27
34
|
setupNavigationGuard(router)
|
28
35
|
|
29
36
|
await useInitExtensionsProcess(router)
|
@@ -3,12 +3,21 @@ import { useSessionStore } from 'src/entities/Session'
|
|
3
3
|
import { useCurrentUserStore } from 'src/entities/User'
|
4
4
|
import { useDesktopStore } from 'src/entities/Desktop/model'
|
5
5
|
import { useSystemStore } from 'src/entities/System/model'
|
6
|
+
import { LocalStorage } from 'quasar'
|
6
7
|
|
7
8
|
function hasAccess(to, userAccount) {
|
8
9
|
if (!to.meta?.roles || to.meta?.roles.length === 0) return true
|
9
10
|
return userAccount && to.meta?.roles.includes(userAccount.role)
|
10
11
|
}
|
11
12
|
|
13
|
+
// Функция для получения URL для редиректа
|
14
|
+
function getRedirectUrl(router: Router, to: any): string {
|
15
|
+
if (process.env.CLIENT) {
|
16
|
+
return router.resolve(to).href
|
17
|
+
}
|
18
|
+
return ''
|
19
|
+
}
|
20
|
+
|
12
21
|
export function setupNavigationGuard(router: Router) {
|
13
22
|
const desktops = useDesktopStore()
|
14
23
|
const session = useSessionStore()
|
@@ -26,12 +35,35 @@ export function setupNavigationGuard(router: Router) {
|
|
26
35
|
|
27
36
|
// редирект с index
|
28
37
|
if (to.name === 'index') {
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
38
|
+
// Убеждаемся, что правильный рабочий стол выбран
|
39
|
+
if (session.isAuth && currentUser.isRegistrationComplete) {
|
40
|
+
// Если рабочий стол не выбран - выбираем по правам пользователя
|
41
|
+
if (!desktops.activeWorkspaceName) {
|
42
|
+
desktops.selectDefaultWorkspace()
|
43
|
+
}
|
44
|
+
|
45
|
+
// Переходим на маршрут по умолчанию для выбранного рабочего стола
|
46
|
+
desktops.goToDefaultPage(router)
|
47
|
+
// next(false)
|
48
|
+
return
|
49
|
+
} else {
|
50
|
+
// Если пользователь не авторизован, используем nonAuthorizedHome
|
51
|
+
const homePage = desktops.currentDesktop?.nonAuthorizedHome
|
52
|
+
next({ name: homePage, params: { coopname: info.coopname } })
|
53
|
+
return
|
54
|
+
}
|
55
|
+
}
|
33
56
|
|
34
|
-
|
57
|
+
// Проверка авторизации для маршрутов, требующих входа
|
58
|
+
if (to.meta?.requiresAuth && !session.isAuth) {
|
59
|
+
// Сохраняем целевой URL для редиректа после входа
|
60
|
+
if (process.env.CLIENT) {
|
61
|
+
// Получаем URL для редиректа
|
62
|
+
const redirectUrl = getRedirectUrl(router, to)
|
63
|
+
LocalStorage.set('redirectAfterLogin', redirectUrl)
|
64
|
+
}
|
65
|
+
// Перенаправляем на страницу входа
|
66
|
+
next({ name: 'login-redirect', params: { coopname: info.coopname } })
|
35
67
|
return
|
36
68
|
}
|
37
69
|
|