diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4a260df9b..3d0c275d1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
### Added
- Show locale date as tooltip for relative date time information
- Add popover to counter to show voters by answer
+ - Add forced display mode to poll configuration
## [8.5.0] - 2025-10-03
### Fixed
diff --git a/lib/Db/Poll.php b/lib/Db/Poll.php
index da7df0ae2a..13137f6f32 100644
--- a/lib/Db/Poll.php
+++ b/lib/Db/Poll.php
@@ -243,6 +243,7 @@ public function getStatusArray(): array {
'countParticipants' => $this->getIsAllowed(self::PERMISSION_POLL_RESULTS_VIEW) ? $this->getParticipantsCount() : 0,
'maxVotes' => $this->getVoteLimit(),
'maxOptionVotes' => $this->getOptionLimit(),
+ 'forcedViewMode' => $this->getForcedDisplayMode() === 'user-pref' ? null : $this->getForcedDisplayMode(),
];
}
public function getConfigurationArray(): array {
@@ -257,6 +258,7 @@ public function getConfigurationArray(): array {
'description' => $this->getDescription(),
'expire' => $this->getExpire(),
'forceConfidentialComments' => $this->getForceConfidentialComments(),
+ 'forcedDisplayMode' => $this->getForcedDisplayMode(),
'hideBookedUp' => boolval($this->getHideBookedUp()),
'maxVotesPerOption' => $this->getOptionLimit(),
'maxVotesPerUser' => $this->getVoteLimit(),
@@ -328,6 +330,7 @@ public function deserializeArray(array $pollConfiguration): self {
$this->setHideBookedUp($pollConfiguration['hideBookedUp'] ?? $this->getHideBookedUp());
$this->setProposalsExpire($pollConfiguration['proposalsExpire'] ?? $this->getProposalsExpire());
$this->setShowResults($pollConfiguration['showResults'] ?? $this->getShowResults());
+ $this->setForcedDisplayMode($pollConfiguration['forcedDisplayMode'] ?? $this->getForcedDisplayMode());
$this->setUseNo($pollConfiguration['useNo'] ?? $this->getUseNo());
$this->setOptionLimit($pollConfiguration['maxVotesPerOption'] ?? $this->getOptionLimit());
$this->setVoteLimit($pollConfiguration['maxVotesPerUser'] ?? $this->getVoteLimit());
@@ -421,6 +424,14 @@ public function getForceConfidentialComments(): bool {
return $this->getMiscSettingsArray()['forceConfidentialComments'] ?? false;
}
+ private function setForcedDisplayMode(string $value): void {
+ $this->setMiscSettingsByKey('forcedDisplayMode', $value);
+ }
+
+ public function getForcedDisplayMode(): string {
+ return $this->getMiscSettingsArray()['forcedDisplayMode'] ?? 'user-pref';
+ }
+
private function setCollapseDescription(bool|int $value): void {
$this->setMiscSettingsByKey('collapseDescription', (bool)$value);
}
diff --git a/src/components/Configuration/ConfigForceViewMode.vue b/src/components/Configuration/ConfigForceViewMode.vue
new file mode 100644
index 0000000000..b378cb4b5d
--- /dev/null
+++ b/src/components/Configuration/ConfigForceViewMode.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/SideBar/SideBarTabConfiguration.vue b/src/components/SideBar/SideBarTabConfiguration.vue
index 5fa005110f..fefa4d4cc7 100644
--- a/src/components/SideBar/SideBarTabConfiguration.vue
+++ b/src/components/SideBar/SideBarTabConfiguration.vue
@@ -14,7 +14,10 @@ import LockedIcon from 'vue-material-design-icons/LockOutline.vue'
import UnlockedIcon from 'vue-material-design-icons/LockOpenVariantOutline.vue'
import ShowResultsIcon from 'vue-material-design-icons/Monitor.vue'
import HideResultsUntilClosedIcon from 'vue-material-design-icons/MonitorLock.vue'
+import UserPreferenceIcon from 'vue-material-design-icons/AccountCogOutline.vue'
import ShowResultsNeverIcon from 'vue-material-design-icons/MonitorOff.vue'
+import ListViewIcon from 'vue-material-design-icons/ViewListOutline.vue'
+import TableViewIcon from 'vue-material-design-icons/Table.vue'
import CardDiv from '../Base/modules/CardDiv.vue'
import ConfigBox from '../Base/modules/ConfigBox.vue'
@@ -32,6 +35,7 @@ import ConfigVoteLimit from '../Configuration/ConfigVoteLimit.vue'
import { usePollStore } from '../../stores/poll'
import { useVotesStore } from '../../stores/votes'
import ConfigDangerArea from '../Configuration/ConfigDangerArea.vue'
+import ConfigForceViewMode from '../Configuration/ConfigForceViewMode.vue'
const pollStore = usePollStore()
const votesStore = useVotesStore()
@@ -95,6 +99,18 @@ const votesStore = useVotesStore()
+
+
+
+
+
+
+
+
+
diff --git a/src/components/VoteTable/VoteTable.vue b/src/components/VoteTable/VoteTable.vue
index 93f89300e6..7285ea8b63 100644
--- a/src/components/VoteTable/VoteTable.vue
+++ b/src/components/VoteTable/VoteTable.vue
@@ -192,7 +192,7 @@ function isVotable(participant: User, option: Option) {
grid-auto-flow: column;
overflow: scroll;
margin: auto;
- & > div {
+ &.table-view > div {
border-inline-start: 1px solid var(--color-border);
&.participant {
border-inline-start: none;
diff --git a/src/stores/poll.ts b/src/stores/poll.ts
index a06e01d2bb..585a456fe0 100644
--- a/src/stores/poll.ts
+++ b/src/stores/poll.ts
@@ -49,7 +49,7 @@ export const usePollStore = defineStore('poll', {
state: (): PollStore => ({
id: 0,
type: 'datePoll',
- voteVariant: 'simple',
+ votingVariant: 'simple',
descriptionSafe: '',
configuration: {
title: '',
@@ -63,6 +63,7 @@ export const usePollStore = defineStore('poll', {
collapseDescription: true,
expire: 0,
forceConfidentialComments: false,
+ forcedDisplayMode: 'user-pref',
hideBookedUp: false,
proposalsExpire: 0,
showResults: 'always',
@@ -86,6 +87,7 @@ export const usePollStore = defineStore('poll', {
countParticipants: 0,
maxVotes: 0,
maxOptionVotes: 0,
+ forcedViewMode: null,
},
currentUserStatus: {
groupInvitations: [],
@@ -138,6 +140,10 @@ export const usePollStore = defineStore('poll', {
getters: {
viewMode(state): ViewMode {
const sessionStore = useSessionStore()
+ if (sessionStore.sessionSettings.viewModeForced) {
+ return sessionStore.sessionSettings.viewModeForced
+ }
+
if (state.type === 'textPoll') {
return sessionStore.viewModeTextPoll
}
@@ -242,7 +248,7 @@ export const usePollStore = defineStore('poll', {
setViewMode(viewMode: ViewMode): void {
const sessionStore = useSessionStore()
- sessionStore.setViewMode(this.type, viewMode)
+ sessionStore.setViewMode(viewMode, this.type)
},
async load(isChanging: boolean = false): Promise {
@@ -267,6 +273,10 @@ export const usePollStore = defineStore('poll', {
this.$patch(response.data.poll)
+ sessionStore.setViewMode(
+ response.data.poll.configuration.forcedDisplayMode,
+ )
+
this.meta.status = 'loaded'
} catch (error) {
if ((error as AxiosError)?.code === 'ERR_CANCELED') {
diff --git a/src/stores/poll.types.ts b/src/stores/poll.types.ts
index 520e6d57f9..81f1a1bffe 100644
--- a/src/stores/poll.types.ts
+++ b/src/stores/poll.types.ts
@@ -4,6 +4,7 @@
*/
import { Chunking, StatusResults, User, UserType } from '../Types'
+import { ViewMode } from './preferences.types'
export type PollType = 'textPoll' | 'datePoll'
@@ -33,6 +34,7 @@ export type PollConfiguration = {
description: string
expire: number
forceConfidentialComments: boolean
+ forcedDisplayMode: ViewMode | 'user-pref'
hideBookedUp: boolean
maxVotesPerOption: number
maxVotesPerUser: number
@@ -56,6 +58,7 @@ export type PollStatus = {
countParticipants: number
maxVotes: number
maxOptionVotes: number
+ forcedViewMode: ViewMode | null
}
export type PollPermissions = {
@@ -100,14 +103,14 @@ export type CurrentUserStatus = {
export type Poll = {
id: number
type: PollType
- voteVariant: VoteVariant
- descriptionSafe: string
+ votingVariant: VoteVariant
configuration: PollConfiguration
+ descriptionSafe: string
owner: User
- pollGroups: number[]
status: PollStatus
currentUserStatus: CurrentUserStatus
permissions: PollPermissions
+ pollGroups: number[]
revealParticipants: boolean
sortParticipants: SortParticipants
meta: Meta
diff --git a/src/stores/session.ts b/src/stores/session.ts
index 2599f6cd47..b01d881b53 100644
--- a/src/stores/session.ts
+++ b/src/stores/session.ts
@@ -49,6 +49,7 @@ export const useSessionStore = defineStore('session', {
sessionSettings: {
viewModeDatePoll: '',
viewModeTextPoll: '',
+ viewModeForced: null,
},
appSettings: {
allAccessGroups: [],
@@ -195,11 +196,26 @@ export const useSessionStore = defineStore('session', {
return 'list-view'
},
- setViewMode(pollType: PollType, viewMode: ViewMode): void {
+ setViewMode(
+ viewMode: ViewMode | 'user-pref',
+ pollType: PollType | null = null,
+ ): void {
+ if (viewMode === 'user-pref') {
+ this.sessionSettings.viewModeForced = null
+ return
+ }
+ if (pollType === null) {
+ this.sessionSettings.viewModeForced = viewMode
+ return
+ }
+
if (pollType === 'datePoll') {
+ this.sessionSettings.viewModeForced = null
this.sessionSettings.viewModeDatePoll = viewMode
+ return
}
if (pollType === 'textPoll') {
+ this.sessionSettings.viewModeForced = null
this.sessionSettings.viewModeTextPoll = viewMode
}
},
diff --git a/src/stores/session.types.ts b/src/stores/session.types.ts
index d32df38a84..9928669266 100644
--- a/src/stores/session.types.ts
+++ b/src/stores/session.types.ts
@@ -35,6 +35,7 @@ export type UserStatus = {
export type SessionSettings = {
viewModeDatePoll: '' | ViewMode
viewModeTextPoll: '' | ViewMode
+ viewModeForced: null | ViewMode
}
export type Watcher = {
diff --git a/src/stores/votes.ts b/src/stores/votes.ts
index 4b67fb59d9..6325bf69cf 100644
--- a/src/stores/votes.ts
+++ b/src/stores/votes.ts
@@ -131,7 +131,7 @@ export const useVotesStore = defineStore('votes', {
// sort participants by votes for the selected option if sortByOption is set
// TODO: Future usage: This is only valid for simple votes (not ranked)
- if (state.sortByOption > 0 && pollStore.voteVariant === 'simple') {
+ if (state.sortByOption > 0 && pollStore.votingVariant === 'simple') {
participants.sort((aUser, bUser) => {
// find the votes for the selected option and the users to compare
const aAnswer =