@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.
Files changed (114) hide show
  1. package/.env-example +4 -0
  2. package/extensions/participant/install.ts +37 -25
  3. package/extensions/soviet/install.ts +23 -20
  4. package/package.json +7 -5
  5. package/quasar.config.cjs +1 -1
  6. package/src/app/App.vue +1 -0
  7. package/src/app/providers/routes/index.ts +12 -0
  8. package/src/app/styles/app.scss +1 -1
  9. package/src/app/styles/style.css +11 -0
  10. package/src/css/quasar.variables.scss +1 -1
  11. package/src/entities/Desktop/model/store.ts +68 -0
  12. package/src/entities/Desktop/model/types.ts +7 -0
  13. package/src/entities/Document/model/types.ts +1 -1
  14. package/src/entities/Meet/api/index.ts +2 -28
  15. package/src/entities/Meet/model/store.ts +7 -22
  16. package/src/entities/Wallet/api/index.ts +3 -3
  17. package/src/env.d.ts +1 -0
  18. package/src/features/Meet/CloseMeetWithDecision/model/index.ts +119 -17
  19. package/src/features/Meet/CreateMeet/model/index.ts +51 -9
  20. package/src/features/Meet/CreateMeet/ui/CreateMeet.vue +37 -6
  21. package/src/features/Meet/CreateMeet/ui/CreateMeetForm.vue +87 -65
  22. package/src/features/Meet/GenerateSovietDecision/model/index.ts +14 -4
  23. package/src/features/Meet/RestartMeet/model/index.ts +121 -3
  24. package/src/features/Meet/RestartMeet/ui/RestartMeet.vue +4 -6
  25. package/src/features/Meet/RestartMeet/ui/RestartMeetForm.vue +64 -28
  26. package/src/features/Meet/SignNotification/index.ts +2 -0
  27. package/src/features/Meet/SignNotification/model/index.ts +137 -0
  28. package/src/features/Meet/SignNotification/ui/SignNotificationButton.vue +61 -0
  29. package/src/features/Meet/SignNotification/ui/index.ts +1 -0
  30. package/src/features/Meet/VoteOnMeet/model/composable.ts +180 -0
  31. package/src/features/Meet/VoteOnMeet/model/index.ts +2 -17
  32. package/src/features/Meet/VoteOnMeet/model/types.ts +4 -0
  33. package/src/features/Meet/index.ts +6 -0
  34. package/src/features/User/LoginRedirect/ui/LoginRedirectPage.vue +15 -0
  35. package/src/features/User/LoginRedirect/ui/index.ts +1 -0
  36. package/src/features/User/LoginUser/ui/LoginForm/LoginForm.vue +38 -1
  37. package/src/features/User/LoginWithRedirect/ui/LoginRedirectForm/LoginRedirectForm.vue +0 -0
  38. package/src/pages/Cooperative/ListOfMeets/ui/ListOfMeetsPage.vue +22 -46
  39. package/src/pages/Cooperative/MeetDetails/ui/MeetDetailsPage.vue +84 -28
  40. package/src/pages/PermissionDenied/PermissionDenied.vue +1 -1
  41. package/src/processes/init-app/index.ts +12 -5
  42. package/src/processes/navigation-guard-setup/index.ts +37 -5
  43. package/src/processes/process-decisions/index.ts +7 -6
  44. package/src/shared/config/Environment.ts +35 -27
  45. package/src/shared/lib/composables/index.ts +1 -0
  46. package/src/shared/lib/composables/useMeetStatus.ts +96 -0
  47. package/src/shared/lib/consts/index.ts +1 -0
  48. package/src/shared/lib/consts/meet-statuses.ts +114 -0
  49. package/src/shared/lib/document/model/entity.ts +4 -2
  50. package/src/shared/lib/types/certificate/index.ts +6 -0
  51. package/src/shared/lib/types/document/index.ts +1 -1
  52. package/src/shared/lib/types/workspace.ts +24 -0
  53. package/src/shared/lib/utils/dates/index.ts +5 -0
  54. package/src/shared/lib/utils/dates/moment.ts +43 -2
  55. package/src/shared/lib/utils/dates/timezone.ts +75 -0
  56. package/src/shared/lib/utils/getNameFromCertificate.ts +108 -0
  57. package/src/shared/lib/utils/index.ts +1 -0
  58. package/src/shared/lib/utils/parseLinks.ts +10 -0
  59. package/src/shared/ui/AgendaNumberAvatar/AgendaNumberAvatar.vue +12 -0
  60. package/src/shared/ui/AgendaNumberAvatar/index.ts +1 -0
  61. package/src/shared/ui/BaseDocument/BaseDocument.vue +37 -8
  62. package/src/shared/ui/ExpandableDocument/ExpandableDocument.vue +49 -0
  63. package/src/shared/ui/ExpandableDocument/index.ts +1 -0
  64. package/src/shared/ui/MeetInfoCard/index.ts +1 -0
  65. package/src/shared/ui/MeetInfoCard/ui/MeetInfoCard.vue +62 -0
  66. package/src/shared/ui/MeetStatusBanner/index.ts +1 -0
  67. package/src/shared/ui/MeetStatusBanner/ui/MeetStatusBanner.vue +94 -0
  68. package/src/shared/ui/index.ts +1 -0
  69. package/src/widgets/Cooperative/Documents/ListOfDocuments/ui/DocumentsTable.vue +3 -3
  70. package/src/widgets/Cooperative/Orders/ListOfOrders/ui/ListOfOrdersWidget.vue +2 -2
  71. package/src/widgets/Cooperative/Payments/ListOfPayments/ui/ListOfPaymentsWidget.vue +2 -2
  72. package/src/widgets/Desktop/WorkspaceMenu/WorkspaceMenu.vue +74 -82
  73. package/src/widgets/Header/CommonHeader/CooperativeSettingsHeader.vue +1 -1
  74. package/src/widgets/Header/CommonHeader/ExtstoreHeader.vue +1 -1
  75. package/src/widgets/Header/CommonHeader/MainHeader.vue +6 -0
  76. package/src/widgets/Header/CommonHeader/UserSettingsHeader.vue +1 -1
  77. package/src/widgets/Meets/MeetCardsList/index.ts +1 -0
  78. package/src/widgets/Meets/MeetCardsList/ui/MeetCardsList.vue +55 -0
  79. package/src/widgets/Meets/MeetDetailsActions/MeetDetailsActions.vue +33 -15
  80. package/src/widgets/Meets/MeetDetailsAgenda/MeetDetailsAgenda.vue +34 -15
  81. package/src/widgets/Meets/MeetDetailsInfo/MeetDetailsInfo.vue +27 -0
  82. package/src/widgets/Meets/MeetDetailsInfo/index.ts +1 -0
  83. package/src/widgets/Meets/MeetDetailsResults/MeetDetailsResults.vue +129 -0
  84. package/src/widgets/Meets/MeetDetailsResults/index.ts +1 -0
  85. package/src/widgets/Meets/MeetDetailsVoting/MeetDetailsVoting.vue +221 -62
  86. package/src/widgets/Meets/MeetQuorumIndicator/MeetQuorumIndicator.vue +0 -0
  87. package/src/widgets/Meets/MeetQuorumIndicator/index.ts +1 -0
  88. package/src/widgets/Meets/MeetQuorumIndicator/ui/MeetQuorumIndicator.vue +35 -0
  89. package/src/widgets/Meets/MeetsTable/ui/MeetsTable.vue +56 -65
  90. package/src/widgets/NotificationCenter/NotificationCenter.vue +90 -0
  91. package/src/widgets/NotificationCenter/index.ts +1 -0
  92. package/src/widgets/Participants/ui/ParticipantsTable.vue +1 -1
  93. package/src/widgets/Questions/ui/QuestionsTable/QuestionsTable.vue +22 -10
  94. package/src/widgets/Questions/ui/VotingButtons/VotingButtons.vue +59 -14
  95. package/src/widgets/Registrator/AlreadyRegistered/AlreadyRegistered.vue +1 -1
  96. package/src/widgets/User/PaymentMethods/ui/PaymentMethods.vue +0 -1
  97. package/src-ssr/middlewares/injectEnv.ts +18 -12
  98. package/src/features/Meet/GenerateAgenda/index.ts +0 -1
  99. package/src/features/Meet/GenerateAgenda/model/index.ts +0 -18
  100. package/src/features/Meet/GenerateBallot/index.ts +0 -1
  101. package/src/features/Meet/GenerateBallot/model/index.ts +0 -18
  102. package/src/features/Meet/GenerateNotification/index.ts +0 -1
  103. package/src/features/Meet/GenerateNotification/model/index.ts +0 -18
  104. package/src/features/Meet/MeetDetailsManagement/index.ts +0 -1
  105. package/src/features/Meet/MeetDetailsManagement/model/index.ts +0 -121
  106. package/src/pages/Cooperative/ListOfMeets/model/index.ts +0 -1
  107. package/src/pages/Cooperative/ListOfMeets/model/model.ts +0 -117
  108. package/src/widgets/Meets/MeetDetailsActions/model.ts +0 -46
  109. package/src/widgets/Meets/MeetDetailsHeader/MeetDetailsHeader.vue +0 -40
  110. package/src/widgets/Meets/MeetDetailsHeader/index.ts +0 -1
  111. package/src/widgets/Meets/MeetDetailsVoting/model.ts +0 -117
  112. package/src/widgets/Meets/MeetInfoCard/ui/MeetInfoCard.vue +0 -38
  113. package/src/widgets/Meets/MeetInfoCard/ui/index.ts +0 -1
  114. /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 type IVoteOnMeetInput = Mutations.Meet.VoteOnAnnualGeneralMeet.IInput['data'];
5
- export type IVoteOnMeetResult = Mutations.Meet.VoteOnAnnualGeneralMeet.IOutput[typeof Mutations.Meet.VoteOnAnnualGeneralMeet.name];
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,4 @@
1
+ import { Mutations } from '@coopenomics/sdk';
2
+
3
+ export type IVoteOnMeetInput = Mutations.Meet.VoteOnAnnualGeneralMeet.IInput['data'];
4
+ export type IVoteOnMeetResult = Mutations.Meet.VoteOnAnnualGeneralMeet.IOutput[typeof Mutations.Meet.VoteOnAnnualGeneralMeet.name];
@@ -0,0 +1,6 @@
1
+ export * from './CreateMeet'
2
+ export * from './SignNotification'
3
+ export * from './VoteOnMeet'
4
+ export * from './RestartMeet'
5
+ export * from './CloseMeetWithDecision'
6
+ export * from './GenerateSovietDecision'
@@ -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
- router.push({ name: 'index' })
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
@@ -1,69 +1,44 @@
1
1
  <template lang="pug">
2
- div.q-pa-md
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-mb-md(v-if="canCreateMeet")
8
- CreateMeet(@create="handleCreate")
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, ref, computed, watch } from 'vue'
23
- import { useRoute, useRouter } from 'vue-router'
24
- import { MeetsTable } from 'src/widgets/Meets/MeetsTable'
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 { useMeetManagement } from '../model'
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
- const {
37
- meets,
38
- loading,
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 navigateToMeetDetails = (meet: IMeet) => {
60
- router.push({
61
- name: route.name === 'meets' ? 'meet-details' : 'user-meet-details',
62
- params: {
63
- coopname: coopname.value,
64
- hash: meet.hash
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
- div.q-pa-md
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
- MeetDetailsHeader(:meet="meet")
12
-
13
- MeetDetailsActions(
14
- :meet="meet"
15
- :coopname="coopname"
16
- :meet-hash="meetHash"
17
- class="q-mt-md"
18
- )
19
-
20
- MeetDetailsAgenda(
21
- :meet="meet"
22
- class="q-mt-md"
23
- )
24
-
25
- MeetDetailsVoting(
26
- :meet="meet"
27
- :coopname="coopname"
28
- :meet-hash="meetHash"
29
- class="q-mt-md"
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 { MeetDetailsHeader } from 'src/widgets/Meets/MeetDetailsHeader'
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 = ref<IMeet | null>(null)
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
- const result = await meetStore.loadMeet({
99
+ await meetStore.loadMeet({
60
100
  coopname: coopname.value,
61
101
  hash: meetHash.value
62
102
  })
63
- meet.value = result
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="secondary" icon="fa fa-arrow-left" @click="goBack")
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
- // Выбираем активный рабочий стол (например, participant)
20
- desktops.selectWorkspace('participant')
20
+ // Регистрируем маршруты рабочего стола до выбора активного рабочего стола
21
+ desktops.registerWorkspaceMenus(router)
21
22
 
22
- useBranchOverlayProcess()
23
23
  await useInitWalletProcess().run()
24
24
 
25
- // Регистрируем маршруты рабочего стола
26
- desktops.registerWorkspaceMenus(router)
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
- const homePage =
30
- session.isAuth && currentUser.isRegistrationComplete
31
- ? desktops.currentDesktop?.authorizedHome
32
- : desktops.currentDesktop?.nonAuthorizedHome
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
- next({ name: homePage, params: { coopname: info.coopname } })
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