@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,55 @@
|
|
1
|
+
<template lang="pug">
|
2
|
+
div.scroll-area(style="height: calc(100% - $toolbar-min-height); overflow-y: auto;")
|
3
|
+
// Заголовок с текстом в зависимости от наличия собраний
|
4
|
+
div.q-pa-md.q-pb-xs(v-if="!loading && meets.length === 0")
|
5
|
+
p.q-my-none.text-center.text-grey-6 У кооператива нет предстоящих общих собраний
|
6
|
+
|
7
|
+
div(v-if="loading" class="q-pa-md")
|
8
|
+
div(v-for="i in 3" :key="i" class="q-mb-lg")
|
9
|
+
q-skeleton(type="rect" height="300px" class="rounded")
|
10
|
+
|
11
|
+
div(v-else-if="!meets.length" class="text-center q-pa-xl")
|
12
|
+
q-icon(name="event_busy" size="64px" color="grey-6")
|
13
|
+
|
14
|
+
div(v-else class="q-pa-md")
|
15
|
+
div.row.q-col-gutter-md
|
16
|
+
div.col-12(v-for="meet in meets" :key="meet.hash")
|
17
|
+
q-card(flat bordered @click="navigateToMeetDetails(meet)").info-card.hover-card.cursor-pointer
|
18
|
+
MeetDetailsInfo(:meet="meet").hover
|
19
|
+
|
20
|
+
</template>
|
21
|
+
|
22
|
+
<script setup lang="ts">
|
23
|
+
import { MeetDetailsInfo } from 'src/widgets/Meets/MeetDetailsInfo'
|
24
|
+
import type { IMeet } from 'src/entities/Meet'
|
25
|
+
import { useRouter } from 'vue-router'
|
26
|
+
import { useDesktopStore } from 'src/entities/Desktop/model'
|
27
|
+
|
28
|
+
defineProps<{
|
29
|
+
meets: IMeet[]
|
30
|
+
loading: boolean
|
31
|
+
}>()
|
32
|
+
|
33
|
+
const router = useRouter()
|
34
|
+
const desktop = useDesktopStore()
|
35
|
+
|
36
|
+
// Навигация к деталям собрания (скопировано из MeetsTable)
|
37
|
+
const navigateToMeetDetails = (meet: IMeet) => {
|
38
|
+
const currentWorkspace = desktop.activeWorkspaceName
|
39
|
+
const isSoviet = currentWorkspace === 'soviet'
|
40
|
+
|
41
|
+
const routeName = isSoviet ? 'meet-details' : 'user-meet-details'
|
42
|
+
|
43
|
+
router.push({
|
44
|
+
name: routeName,
|
45
|
+
params: {
|
46
|
+
hash: meet.hash,
|
47
|
+
coopname: router.currentRoute.value.params.coopname
|
48
|
+
}
|
49
|
+
})
|
50
|
+
}
|
51
|
+
</script>
|
52
|
+
|
53
|
+
<style lang="scss" scoped>
|
54
|
+
@import 'src/shared/ui/CardStyles/index.scss';
|
55
|
+
</style>
|
@@ -1,35 +1,39 @@
|
|
1
1
|
<template lang="pug">
|
2
|
-
|
2
|
+
div
|
3
3
|
div.row.q-col-gutter-md
|
4
4
|
div.col-12.col-md-auto(v-if="canCloseBySecretary")
|
5
5
|
q-btn(
|
6
|
-
color="
|
7
|
-
icon="fa-solid fa-
|
8
|
-
label="
|
6
|
+
color="primary"
|
7
|
+
icon="fa-solid fa-signature"
|
8
|
+
label="Подписать протокол"
|
9
9
|
@click="closeMeetBySecretary"
|
10
10
|
:loading="isProcessing"
|
11
11
|
)
|
12
12
|
div.col-12.col-md-auto(v-if="canCloseByPresider")
|
13
13
|
q-btn(
|
14
|
-
color="
|
15
|
-
icon="fa-solid fa-
|
16
|
-
label="
|
14
|
+
color="primary"
|
15
|
+
icon="fa-solid fa-stamp"
|
16
|
+
label="Утвердить протокол"
|
17
17
|
@click="closeMeetByPresider"
|
18
18
|
:loading="isProcessing"
|
19
19
|
)
|
20
20
|
div.col-12.col-md-auto(v-if="canRestartMeet")
|
21
21
|
RestartMeet(
|
22
|
-
:meet="meet"
|
23
22
|
show-button
|
24
23
|
@restart="handleRestartMeet"
|
24
|
+
:loading="isProcessing"
|
25
25
|
)
|
26
|
+
|
26
27
|
</template>
|
27
28
|
|
28
29
|
<script setup lang="ts">
|
29
|
-
import { ref } from 'vue'
|
30
|
+
import { ref, onMounted, watch } from 'vue'
|
31
|
+
import { useRouter } from 'vue-router'
|
32
|
+
import { useCloseMeet } from 'src/features/Meet/CloseMeetWithDecision/model'
|
33
|
+
import { useRestartMeet } from 'src/features/Meet/RestartMeet/model'
|
30
34
|
import { RestartMeet } from 'src/features/Meet/RestartMeet'
|
35
|
+
import { useMeetStore } from 'src/entities/Meet'
|
31
36
|
import type { IMeet } from 'src/entities/Meet'
|
32
|
-
import { useMeetDetailsManagement } from 'src/features/Meet/MeetDetailsManagement'
|
33
37
|
|
34
38
|
const props = defineProps<{
|
35
39
|
meet: IMeet
|
@@ -38,14 +42,28 @@ const props = defineProps<{
|
|
38
42
|
}>()
|
39
43
|
|
40
44
|
const isProcessing = ref(false)
|
45
|
+
const meetStore = useMeetStore()
|
46
|
+
const router = useRouter()
|
47
|
+
|
48
|
+
// Устанавливаем текущее собрание в store при монтировании и обновлении пропсов
|
49
|
+
onMounted(() => {
|
50
|
+
meetStore.setCurrentMeet(props.meet)
|
51
|
+
})
|
52
|
+
|
53
|
+
watch(() => props.meet, (newMeet) => {
|
54
|
+
meetStore.setCurrentMeet(newMeet)
|
55
|
+
}, { deep: true })
|
41
56
|
|
42
57
|
const {
|
43
|
-
canManageMeet,
|
44
58
|
canCloseBySecretary,
|
45
59
|
canCloseByPresider,
|
46
|
-
canRestartMeet,
|
47
60
|
closeMeetBySecretary,
|
48
|
-
closeMeetByPresider
|
61
|
+
closeMeetByPresider
|
62
|
+
} = useCloseMeet(isProcessing)
|
63
|
+
|
64
|
+
const {
|
65
|
+
canRestartMeet,
|
49
66
|
handleRestartMeet
|
50
|
-
} =
|
51
|
-
|
67
|
+
} = useRestartMeet(router, isProcessing)
|
68
|
+
|
69
|
+
</script>
|
@@ -1,29 +1,48 @@
|
|
1
1
|
<template lang="pug">
|
2
|
-
|
3
|
-
div.
|
2
|
+
div
|
3
|
+
div.row.justify-center
|
4
|
+
div.text-h6.q-mt-md.full-width.text-center Повестка
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
div.col-12.col-md-12(v-for="(item, index) in meetAgendaItems" :key="index")
|
7
|
+
q-card(flat bordered).q-mb-md.q-mt-md.q-pt-md
|
8
|
+
q-card-section.q-pa-xs
|
9
|
+
div.q-mb-xs.flex.items-start.q-mb-lg.q-pa-xs
|
10
|
+
AgendaNumberAvatar(:number="index + 1" class="q-ma-md")
|
11
|
+
div.col
|
12
|
+
div.text-body1.text-weight-medium.q-mb-2 {{ item.title }}
|
13
|
+
div.text-caption.q-mb-1.q-mt-md
|
14
|
+
span.text-weight-bold Проект решения:
|
15
|
+
span.q-ml-xs {{ item.decision }}
|
16
|
+
div.text-caption.q-mt-md
|
17
|
+
span.text-weight-bold Приложения:
|
18
|
+
span.q-ml-xs(v-if="item.context" v-html="parseLinks(item.context)")
|
19
|
+
span.q-ml-xs(v-else) —
|
20
|
+
div.row.justify-center
|
21
|
+
SignNotificationButton(
|
22
|
+
v-if="coopname && meetHash"
|
23
|
+
:coopname="coopname"
|
24
|
+
:meetHash="meetHash"
|
25
|
+
)
|
15
26
|
</template>
|
16
27
|
|
17
28
|
<script setup lang="ts">
|
18
|
-
import type { IMeet } from 'src/entities/Meet'
|
19
29
|
import { computed } from 'vue'
|
30
|
+
import type { IMeet } from 'src/entities/Meet'
|
31
|
+
import { AgendaNumberAvatar } from 'src/shared/ui/AgendaNumberAvatar'
|
32
|
+
import { SignNotificationButton } from 'src/features/Meet/SignNotification/ui'
|
33
|
+
import { parseLinks } from 'src/shared/lib/utils'
|
20
34
|
|
21
35
|
const props = defineProps<{
|
22
|
-
meet: IMeet
|
36
|
+
meet: IMeet,
|
37
|
+
coopname?: string,
|
38
|
+
meetHash?: string
|
23
39
|
}>()
|
24
40
|
|
41
|
+
const coopname = computed(() => props.coopname || '')
|
42
|
+
const meetHash = computed(() => props.meetHash || '')
|
43
|
+
|
25
44
|
const meetAgendaItems = computed(() => {
|
26
45
|
if (!props.meet) return []
|
27
46
|
return props.meet.processing?.questions || []
|
28
47
|
})
|
29
|
-
</script>
|
48
|
+
</script>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<template lang="pug">
|
2
|
+
div
|
3
|
+
q-card(flat bordered).q-pa-md
|
4
|
+
MeetInfoCard(:meet="meet")
|
5
|
+
// Слот для действий (кнопки)
|
6
|
+
div.q-mt-md(v-if="$slots.actions")
|
7
|
+
slot(name="actions")
|
8
|
+
</template>
|
9
|
+
|
10
|
+
<script setup lang="ts">
|
11
|
+
import { MeetInfoCard } from 'src/shared/ui/MeetInfoCard'
|
12
|
+
import type { IMeet } from 'src/entities/Meet'
|
13
|
+
|
14
|
+
defineProps<{
|
15
|
+
meet: IMeet
|
16
|
+
}>()
|
17
|
+
|
18
|
+
</script>
|
19
|
+
|
20
|
+
<style lang="scss" scoped>
|
21
|
+
@import 'src/shared/ui/CardStyles/index.scss';
|
22
|
+
|
23
|
+
.meet-status {
|
24
|
+
font-size: 14px;
|
25
|
+
padding: 4px 8px;
|
26
|
+
}
|
27
|
+
</style>
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as MeetDetailsInfo } from './MeetDetailsInfo.vue'
|
@@ -0,0 +1,129 @@
|
|
1
|
+
<template lang="pug">
|
2
|
+
div(flat)
|
3
|
+
div.text-center.text-h6.q-mb-md РЕЗУЛЬТАТЫ
|
4
|
+
|
5
|
+
div.row.justify-center
|
6
|
+
// Отображаем документ собрания, если он есть
|
7
|
+
ExpandableDocument(
|
8
|
+
v-if="!!meet?.processed?.decisionAggregate"
|
9
|
+
:documentAggregate="meet.processed.decisionAggregate"
|
10
|
+
title="Протокол решения общего собрания пайщиков"
|
11
|
+
)
|
12
|
+
div.col-12.col-md-12(v-for="(item, index) in meetAgendaItems" :key="index")
|
13
|
+
q-card(flat bordered).q-mb-md
|
14
|
+
q-card-section
|
15
|
+
div.row
|
16
|
+
div.col-12.col-md-auto.flex.justify-center.q-pa-md
|
17
|
+
AgendaNumberAvatar(:number="item.number")
|
18
|
+
div.col-12.col-md
|
19
|
+
|
20
|
+
div.row.justify-between.items-center
|
21
|
+
div.text-h6 {{ item.title }}
|
22
|
+
q-badge(
|
23
|
+
:color="getResultBadgeColor(item)"
|
24
|
+
:label="getResultText(item)"
|
25
|
+
:icon="getResultIcon(item)"
|
26
|
+
floating
|
27
|
+
class="text-weight-bold"
|
28
|
+
)
|
29
|
+
|
30
|
+
q-separator.q-my-sm
|
31
|
+
div.text-body1(v-html="parseLinks(item.context)")
|
32
|
+
q-separator.q-my-sm
|
33
|
+
|
34
|
+
//- div.text-subtitle1.text-weight-bold Решение
|
35
|
+
//- div.text-body2 {{ item.decision }}
|
36
|
+
//- q-separator.q-my-sm
|
37
|
+
|
38
|
+
div.row.q-col-gutter-sm.q-mt-sm
|
39
|
+
// Новый push-дизайн карточек голосования
|
40
|
+
div.col-12.col-md-4
|
41
|
+
q-card(flat bordered :class="'q-pa-md q-mb-sm shadow-2 flex flex-center ' + getCardClass('for')")
|
42
|
+
q-card-section(class="text-center")
|
43
|
+
q-icon(name="thumb_up" :color="getCardSemanticColor('for')" size="32px")
|
44
|
+
div(:class="'text-weight-bold q-mt-sm text-' + getCardSemanticColor('for')") ЗА
|
45
|
+
div(:class="'text-h5 q-mt-xs text-' + getCardSemanticColor('for')") {{ item.votes_for }}
|
46
|
+
div.col-12.col-md-4
|
47
|
+
q-card(flat bordered :class="'q-pa-md q-mb-sm shadow-2 flex flex-center ' + getCardClass('against')")
|
48
|
+
q-card-section(class="text-center")
|
49
|
+
q-icon(name="thumb_down" :color="getCardSemanticColor('against')" size="32px")
|
50
|
+
div(:class="'text-weight-bold q-mt-sm text-' + getCardSemanticColor('against')") ПРОТИВ
|
51
|
+
div(:class="'text-h5 q-mt-xs text-' + getCardSemanticColor('against')") {{ item.votes_against }}
|
52
|
+
|
53
|
+
|
54
|
+
div.col-12.col-md-4
|
55
|
+
q-card(flat bordered :class="'q-pa-md q-mb-sm shadow-2 flex flex-center ' + getCardClass('abstained')")
|
56
|
+
q-card-section(class="text-center")
|
57
|
+
q-icon(name="pan_tool" :color="getCardSemanticColor('abstained')" size="32px")
|
58
|
+
div(:class="'text-weight-bold q-mt-sm text-' + getCardSemanticColor('abstained')") ВОЗДЕРЖАЛИСЬ
|
59
|
+
div(:class="'text-h5 q-mt-xs text-' + getCardSemanticColor('abstained')") {{ item.votes_abstained }}
|
60
|
+
</template>
|
61
|
+
|
62
|
+
<script setup lang="ts">
|
63
|
+
import type { IMeet } from 'src/entities/Meet'
|
64
|
+
import { computed } from 'vue'
|
65
|
+
import { ExpandableDocument } from 'src/shared/ui'
|
66
|
+
import { AgendaNumberAvatar } from 'src/shared/ui/AgendaNumberAvatar'
|
67
|
+
import { useQuasar } from 'quasar'
|
68
|
+
import { parseLinks } from 'src/shared/lib/utils'
|
69
|
+
|
70
|
+
const $q = useQuasar()
|
71
|
+
const isDark = computed(() => $q.dark.isActive)
|
72
|
+
|
73
|
+
const props = defineProps<{
|
74
|
+
meet: IMeet
|
75
|
+
}>()
|
76
|
+
|
77
|
+
const meetAgendaItems = computed(() => {
|
78
|
+
if (!props.meet || !props.meet.processed?.results) return []
|
79
|
+
return props.meet.processed.results || []
|
80
|
+
})
|
81
|
+
|
82
|
+
// Классы для карточек голосования с учётом темы
|
83
|
+
const getCardClass = (type: 'for' | 'against' | 'abstained') => {
|
84
|
+
if (type === 'for') return isDark.value ? 'bg-green-10 card-border-light' : 'bg-green-1'
|
85
|
+
if (type === 'against') return isDark.value ? 'bg-red-10 card-border-light' : 'bg-red-1'
|
86
|
+
if (type === 'abstained') return isDark.value ? 'bg-grey-9 card-border-light' : 'bg-grey-2'
|
87
|
+
return ''
|
88
|
+
}
|
89
|
+
|
90
|
+
// Цвет иконки и текста для карточки с учетом темы
|
91
|
+
const getCardSemanticColor = (type: 'for' | 'against' | 'abstained') => {
|
92
|
+
if (isDark.value) {
|
93
|
+
// В темной теме используем светлые цвета для контраста
|
94
|
+
if (type === 'for') return 'green-3'
|
95
|
+
if (type === 'against') return 'red-3'
|
96
|
+
if (type === 'abstained') return 'grey-4'
|
97
|
+
} else {
|
98
|
+
// В светлой теме используем стандартные цвета
|
99
|
+
if (type === 'for') return 'positive'
|
100
|
+
if (type === 'against') return 'negative'
|
101
|
+
if (type === 'abstained') return 'grey'
|
102
|
+
}
|
103
|
+
return ''
|
104
|
+
}
|
105
|
+
|
106
|
+
// Получение текста результата
|
107
|
+
const getResultText = (question: any) => {
|
108
|
+
if (question.accepted === undefined) return 'Нет данных'
|
109
|
+
return question.accepted ? 'ПРИНЯТО' : 'ОТКЛОНЕНО'
|
110
|
+
}
|
111
|
+
|
112
|
+
// Добавляю функции для иконки и цвета результата
|
113
|
+
const getResultIcon = (question: any) => {
|
114
|
+
if (question.accepted === undefined) return 'help_outline'
|
115
|
+
return question.accepted ? 'check_circle' : 'cancel'
|
116
|
+
}
|
117
|
+
|
118
|
+
const getResultBadgeColor = (question: any) => {
|
119
|
+
if (question.accepted === undefined) return 'grey-5'
|
120
|
+
return question.accepted ? 'positive' : 'negative'
|
121
|
+
}
|
122
|
+
</script>
|
123
|
+
|
124
|
+
<style scoped>
|
125
|
+
/* Светлая рамка для карточек в тёмном режиме */
|
126
|
+
.card-border-light {
|
127
|
+
border: 1.5px solid #fff2;
|
128
|
+
}
|
129
|
+
</style>
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as MeetDetailsResults } from './MeetDetailsResults.vue'
|
@@ -1,59 +1,86 @@
|
|
1
1
|
<template lang="pug">
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
q-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
2
|
+
div
|
3
|
+
// Баннер для уже проголосовавших пользователей
|
4
|
+
q-banner.q-mb-md(
|
5
|
+
v-if="meet?.processing?.isVoted"
|
6
|
+
rounded
|
7
|
+
color="positive"
|
8
|
+
text-color="white"
|
9
|
+
).text-center
|
10
|
+
template(#avatar)
|
11
|
+
q-icon(name="how_to_vote" color="primary" size="50px" class="q-mt-md q-mb-md")
|
12
|
+
div.text-body1.text-weight-medium Вы уже приняли участие в голосовании. Ваш голос принят и учтен.
|
13
|
+
|
14
|
+
|
15
|
+
div(v-if="!meet?.processing?.isVoted", flat)
|
16
|
+
q-card-section.text-center
|
17
|
+
.text-h6 Голосование
|
18
|
+
q-card(flat bordered v-for="(item, index) in meetAgendaItems", :key="index").q-mb-md.q-pt-md
|
19
|
+
q-card-section.q-pa-xs
|
20
|
+
div.q-mb-xs.flex.items-start.q-mb-lg.q-pa-xs
|
21
|
+
AgendaNumberAvatar(:number="index + 1" class="q-ma-md")
|
22
|
+
div.col
|
23
|
+
div.text-body1.text-weight-medium.q-mb-2 {{ item.title }}
|
24
|
+
div.text-caption.q-mb-1.q-mt-md
|
25
|
+
span.text-weight-bold Проект решения:
|
26
|
+
span.q-ml-xs {{ item.decision }}
|
27
|
+
div.text-caption.q-mt-md
|
28
|
+
span.text-weight-bold Приложения:
|
29
|
+
span.q-ml-xs(v-if="item.context" v-html="parseLinks(item.context)")
|
30
|
+
span.q-ml-xs(v-else) —
|
31
|
+
q-separator.q-my-md
|
32
|
+
.text-subtitle1.q-mb-sm Ваш голос:
|
33
|
+
.row.q-col-gutter-sm
|
34
|
+
.col-12.col-md-4
|
35
|
+
|
36
|
+
label.vote-radio-wrapper.positive
|
37
|
+
q-radio(
|
38
|
+
v-model="votes[index]",
|
39
|
+
val="for",
|
40
|
+
color="positive",
|
41
|
+
size="lg",
|
42
|
+
label="ЗА"
|
43
|
+
)
|
44
|
+
.col-12.col-md-4
|
45
|
+
label.vote-radio-wrapper.negative
|
46
|
+
q-radio(
|
47
|
+
v-model="votes[index]",
|
48
|
+
val="against",
|
49
|
+
color="negative",
|
50
|
+
size="lg",
|
51
|
+
label="ПРОТИВ"
|
52
|
+
)
|
53
|
+
.col-12.col-md-4
|
54
|
+
label.vote-radio-wrapper.grey
|
55
|
+
q-radio(
|
56
|
+
v-model="votes[index]",
|
57
|
+
val="abstained",
|
58
|
+
color="grey",
|
59
|
+
size="lg",
|
60
|
+
label="ВОЗДЕРЖАЛСЯ"
|
61
|
+
)
|
62
|
+
|
63
|
+
q-separator
|
64
|
+
q-card-actions(align="center").q-pa-md
|
65
|
+
q-btn.q-px-xl(
|
66
|
+
color="primary",
|
67
|
+
label="ГОЛОСОВАТЬ",
|
68
|
+
size="lg",
|
69
|
+
:loading="isVoting",
|
70
|
+
@click="submitVote",
|
71
|
+
:disable="!allVotesSelected"
|
72
|
+
)
|
51
73
|
</template>
|
52
74
|
|
53
75
|
<script setup lang="ts">
|
54
|
-
import { ref } from 'vue'
|
76
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
77
|
+
import { AgendaNumberAvatar } from 'src/shared/ui/AgendaNumberAvatar'
|
55
78
|
import type { IMeet } from 'src/entities/Meet'
|
56
|
-
import {
|
79
|
+
import { useSessionStore } from 'src/entities/Session'
|
80
|
+
import { FailAlert, SuccessAlert } from 'src/shared/api'
|
81
|
+
import { useSignDocument } from 'src/shared/lib/document'
|
82
|
+
import { useVoteOnMeet, type IVoteOnMeetInput } from 'src/features/Meet/VoteOnMeet'
|
83
|
+
import { parseLinks } from 'src/shared/lib/utils'
|
57
84
|
|
58
85
|
const props = defineProps<{
|
59
86
|
meet: IMeet
|
@@ -61,17 +88,149 @@ const props = defineProps<{
|
|
61
88
|
meetHash: string
|
62
89
|
}>()
|
63
90
|
|
64
|
-
const isVoting = ref(false)
|
65
|
-
const votes = ref<Record<number, 'for' | 'against' | 'abstained'>>({})
|
66
|
-
|
67
91
|
const {
|
68
|
-
|
92
|
+
votes,
|
69
93
|
meetAgendaItems,
|
70
94
|
allVotesSelected,
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
95
|
+
setMeet,
|
96
|
+
voteOnMeet,
|
97
|
+
resetVotes,
|
98
|
+
generateBallot
|
99
|
+
} = useVoteOnMeet()
|
100
|
+
|
101
|
+
const sessionStore = useSessionStore()
|
102
|
+
const { signDocument } = useSignDocument()
|
103
|
+
|
104
|
+
const isVoting = ref(false)
|
105
|
+
|
106
|
+
// Устанавливаем собрание из пропсов при монтировании
|
107
|
+
onMounted(() => {
|
108
|
+
// Устанавливаем собрание из пропсов в композабл
|
109
|
+
setMeet(props.meet)
|
110
|
+
|
111
|
+
// Сбрасываем голоса при открытии компонента
|
112
|
+
resetVotes()
|
113
|
+
})
|
114
|
+
|
115
|
+
// Очищаем ссылку на собрание при размонтировании
|
116
|
+
onUnmounted(() => {
|
117
|
+
setMeet(null)
|
118
|
+
resetVotes()
|
119
|
+
})
|
120
|
+
|
121
|
+
const submitVote = async () => {
|
122
|
+
if (!allVotesSelected.value) return
|
123
|
+
|
124
|
+
isVoting.value = true
|
125
|
+
try {
|
126
|
+
const generatedBallot = await generateBallot({
|
127
|
+
coopname: props.coopname,
|
128
|
+
username: sessionStore.username,
|
129
|
+
meet_hash: props.meetHash,
|
130
|
+
answers: meetAgendaItems.value.map((question, index) => ({
|
131
|
+
id: question.id.toString(),
|
132
|
+
number: question.number.toString(),
|
133
|
+
vote: votes.value[index]
|
134
|
+
}))
|
135
|
+
})
|
136
|
+
|
137
|
+
const signedBallot = await signDocument(generatedBallot, sessionStore.username)
|
138
|
+
|
139
|
+
const vote: IVoteOnMeetInput = {
|
140
|
+
coopname: props.coopname,
|
141
|
+
hash: props.meetHash,
|
142
|
+
ballot: signedBallot,
|
143
|
+
username: sessionStore.username,
|
144
|
+
votes: meetAgendaItems.value.map((item, index) => ({
|
145
|
+
question_id: item.id,
|
146
|
+
vote: votes.value[index]
|
147
|
+
}))
|
148
|
+
}
|
149
|
+
|
150
|
+
await voteOnMeet(vote)
|
151
|
+
SuccessAlert('Ваш голос успешно отправлен')
|
152
|
+
} catch (error: any) {
|
153
|
+
console.error(error)
|
154
|
+
FailAlert(error)
|
155
|
+
} finally {
|
156
|
+
isVoting.value = false
|
157
|
+
}
|
158
|
+
}
|
159
|
+
</script>
|
160
|
+
|
161
|
+
<style scoped>
|
162
|
+
.vote-radio-wrapper {
|
163
|
+
display: flex;
|
164
|
+
align-items: center;
|
165
|
+
justify-content: center;
|
166
|
+
background: #f5f7fa;
|
167
|
+
border-radius: 16px;
|
168
|
+
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.07);
|
169
|
+
padding: 18px 8px 12px 8px;
|
170
|
+
margin-bottom: 8px;
|
171
|
+
transition: box-shadow 0.2s, background 0.2s, color 0.2s;
|
172
|
+
color: black;
|
173
|
+
cursor: pointer;
|
174
|
+
}
|
175
|
+
.vote-radio-wrapper.positive {
|
176
|
+
background: #e8f5e9;
|
177
|
+
}
|
178
|
+
.vote-radio-wrapper.negative {
|
179
|
+
background: #ffebee;
|
180
|
+
}
|
181
|
+
.vote-radio-wrapper.grey {
|
182
|
+
background: #eceff1;
|
183
|
+
}
|
184
|
+
.vote-radio-wrapper:hover {
|
185
|
+
box-shadow: 0 4px 24px 0 rgba(0,0,0,0.13);
|
186
|
+
background: #e3eafc;
|
187
|
+
}
|
188
|
+
.vote-icon {
|
189
|
+
margin-left: 10px;
|
190
|
+
}
|
191
|
+
.q-radio__label {
|
192
|
+
font-size: 1.2em;
|
193
|
+
font-weight: bold;
|
194
|
+
}
|
195
|
+
|
196
|
+
/* --- DARK THEME SUPPORT --- */
|
197
|
+
.body--dark .vote-radio-wrapper,
|
198
|
+
.q-dark .vote-radio-wrapper {
|
199
|
+
background: #23272f;
|
200
|
+
color: #fff;
|
201
|
+
}
|
202
|
+
.body--dark .vote-radio-wrapper.positive,
|
203
|
+
.q-dark .vote-radio-wrapper.positive {
|
204
|
+
background: #295b36;
|
205
|
+
}
|
206
|
+
.body--dark .vote-radio-wrapper.negative,
|
207
|
+
.q-dark .vote-radio-wrapper.negative {
|
208
|
+
background: #5b2323;
|
209
|
+
}
|
210
|
+
.body--dark .vote-radio-wrapper.grey,
|
211
|
+
.q-dark .vote-radio-wrapper.grey {
|
212
|
+
background: #2c313a;
|
213
|
+
}
|
214
|
+
.body--dark .q-radio__label,
|
215
|
+
.q-dark .q-radio__label {
|
216
|
+
color: #fff;
|
217
|
+
}
|
218
|
+
|
219
|
+
/* Кастомизация кружка radio для тёмной темы */
|
220
|
+
.body--dark :deep(.vote-radio-wrapper .q-radio__inner),
|
221
|
+
.q-dark :deep(.vote-radio-wrapper .q-radio__inner) {
|
222
|
+
border-color: #fff !important;
|
223
|
+
}
|
224
|
+
.body--dark :deep(.vote-radio-wrapper.positive .q-radio__inner),
|
225
|
+
.q-dark :deep(.vote-radio-wrapper.positive .q-radio__inner) {
|
226
|
+
border-color: #4caf50 !important;
|
227
|
+
}
|
228
|
+
.body--dark :deep(.vote-radio-wrapper.negative .q-radio__inner),
|
229
|
+
.q-dark :deep(.vote-radio-wrapper.negative .q-radio__inner) {
|
230
|
+
border-color: #f44336 !important;
|
231
|
+
}
|
232
|
+
.body--dark :deep(.vote-radio-wrapper.grey .q-radio__inner),
|
233
|
+
.q-dark :deep(.vote-radio-wrapper.grey .q-radio__inner) {
|
234
|
+
border-color: #b0bec5 !important;
|
235
|
+
}
|
236
|
+
</style>
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
export { default as MeetQuorumIndicator } from './ui/MeetQuorumIndicator.vue'
|