coodle survey edit form

This commit is contained in:
adisposkofh
2026-06-18 18:22:45 +02:00
parent 10b09bce10
commit 1e771694d7
9 changed files with 762 additions and 176 deletions
+12
View File
@@ -33,6 +33,10 @@ html {
background-color: var(--fhc-primary) !important;
}
.fhc-primary-border-color {
border-color: var(--fhc-primary) !important;
}
.fhc-primary-highlight-bg {
background-color: var(--fhc-primary-highlight) !important;
}
@@ -860,4 +864,12 @@ html {
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.5rem;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}
#coodleParticipantsSearchResults {
background-color: var(--fhc-background);
border: 1px solid var(--fhc-border);
}
.coodleParticipantSearchResult:hover {
opacity: 80%;
}
@@ -1,14 +1,20 @@
import CoodleSurveyVotingTable from "./CoodleSurveyVotingTable.js";
import CoodleSurveyVotingTable from "./CoodleSurvey/CoodleSurveyVotingTable.js";
import CoodleSurveyCalendar from "./CoodleSurvey/CoodleSurveyCalendar.js";
import CoodleSurveyParticipants from "./CoodleSurvey/CoodleSurveyParticipants.js";
import CoodleSurveyDurationSelector from "./CoodleSurvey/CoodleSurveyDurationSelector.js";
import CoodleSurveyBasicInfo from "./CoodleSurvey/CoodleSurveyBasicInfo.js";
import {
formatDate,
numberPadding,
addMinutesToDate,
} from "../../../../helpers/DateHelpers.js";
import { formatDate, numberPadding } from "../../../../helpers/DateHelpers.js";
export default {
name: "CoodleSurvey",
components: { CoodleSurveyVotingTable },
components: {
CoodleSurveyVotingTable,
CoodleSurveyCalendar,
CoodleSurveyParticipants,
CoodleSurveyDurationSelector,
CoodleSurveyBasicInfo,
},
props: {
survey: { type: Object | null },
uid: { type: String | null },
@@ -75,57 +81,6 @@ export default {
return "New survey";
}
},
formattedSurveyDescription() {
return this.$props.survey?.description?.replaceAll("\n", "<br>");
},
formattedSurveyTimeslotDuration() {
const minutes = this.$props.survey.timeslotDuration % 60;
const hours = (this.$props.survey.timeslotDuration - minutes) / 60;
let formattedDuration = hours ? hours + " hr " : "";
formattedDuration += minutes + " min";
return formattedDuration;
},
formattedSurveyEndsAt() {
if (this.$props.survey?.endsAt) {
return formatDate(this.$props.survey.endsAt);
} else {
return "";
}
},
formattedSurveyCreatedAt() {
if (this.$props.survey?.createdAt) {
return formatDate(this.$props.survey.createdAt);
} else {
return "";
}
},
formattedSurveyUpdatedAt() {
if (this.$props.survey?.updatedAt) {
return formatDate(this.$props.survey.updatedAt);
} else {
return "";
}
},
formattedSurveyCanceledAt() {
if (this.$props.survey?.canceledAt) {
return formatDate(this.$props.survey.canceledAt);
} else {
return "";
}
},
formattedSurveyCompletedAt() {
if (this.$props.survey?.completedAt) {
return formatDate(this.$props.survey.completedAt);
} else {
return "";
}
},
surveyCreatorProfileHref() {
return this.$router.resolve({
name: "ProfilView",
params: { uid: this.survey?.creator?.uid },
}).href;
},
parsedSelectedTimeslot() {
return this.$props.survey?.selectedTimeslotId
? this.parseTimeslot(
@@ -149,7 +104,7 @@ export default {
},
watch: {
survey: {
handler() {
async handler() {
this.setSurveyFormData();
if (!this.survey?.id) {
this.isEditInProgress = true;
@@ -157,38 +112,52 @@ export default {
},
deep: true,
},
isEditInProgress() {
if (!this.isEditInProgress) {
this.setSurveyFormData();
}
},
},
methods: {
setSurveyFormData() {
if (this.$props.survey) {
this.surveyFormData = { ...this.$props.survey };
return;
} else {
let defaultEndsAtDate = new Date();
defaultEndsAtDate.setDate(defaultEndsAtDate.getDate() + 7);
this.surveyFormData = {
id: null,
creator: null,
title: "",
description: "",
timeslotDuration: 60,
maxSelections: 1,
areParticipantsAnonymized: false,
areSelectionsAnonymized: false,
selectedTimeslot: null,
endsAt: defaultEndsAtDate.toISOString().slice(0, 10),
completedAt: null,
canceledAt: null,
updatedAt: null,
createdAt: null,
timeslots: [],
participants: [],
};
}
this.surveyFormData = {
id: null,
creator: null,
title: "",
description: "",
timeslotDuration: 60,
maxSelections: 1,
areParticipantsAnonymized: false,
areSelectionsAnonymized: false,
selectedTimeslot: null,
endsAt: null, //todo: set to current date + 7
completedAt: null,
canceledAt: null,
updatedAt: null,
createdAt: null,
timeslots: [],
participants: [],
};
this.surveyFormData.participants.forEach(
(participant) => {
participant.isCalendarShown = false;
},
);
},
parseTimeslot(timeslot) {
const timeslotStartsAt = new Date(timeslot.startsAt + " UTC");
const timeslotEndsAt = addMinutesToDate(
timeslotStartsAt,
this.$props.survey?.timeslotDuration ?? 0,
let timeslotEndsAt = new Date(timeslot.startsAt + " UTC");
timeslotEndsAt.setMinutes(
timeslotEndsAt.getMinutes() +
(this.$props.survey?.timeslotDuration ?? 0),
);
return {
@@ -224,6 +193,10 @@ export default {
console.log("canceling...");
// todo
},
submitForm() {
console.log("submitting...");
// todo
},
},
created() {
this.setSurveyFormData();
@@ -269,91 +242,65 @@ export default {
</ul>
</div>
</div>
</div>
</div>
<div class="card-body">
<div v-if="!isEditInProgress" class="d-flex flex-column gap-3">
<span v-if="$props.survey?.completedAt" class="fst-italic">
{{ "This survey was completed on " + formattedSurveyCompletedAt + ". " }}
<span v-if="$props.survey?.selectedTimeslotId">
{{
"The timeslot (((timeslot))) was selected.".replace(
"(((timeslot)))",
parsedSelectedTimeslot.weekday +
" " +
parsedSelectedTimeslot.fullDate +
" " +
parsedSelectedTimeslot.startTime +
"-" +
parsedSelectedTimeslot.endTime
)
}}
</span>
<span v-else>{{ "No timeslot was selected." }}</span>
</span>
<span v-else-if="$props.survey?.canceledAt" class="fst-italic">
{{ "This survey was canceled on " + formattedSurveyCanceledAt + "." }}
</span>
<span
v-if="$props.survey?.description?.length"
class="text-wrap"
v-html="formattedSurveyDescription"
></span>
<div class="d-flex flex-column gap-1">
<div>
{{ "Created by " }}
<a :href="surveyCreatorProfileHref" :target="'_blank'">{{ this.$props.survey?.creator?.name }}</a>
{{ " on " + formattedSurveyCreatedAt}}
</div>
<div>{{ "Last edited on " + formattedSurveyUpdatedAt }}</div>
<div>{{ "Planned to end on " + formattedSurveyEndsAt }}</div>
</div>
<div class="d-flex flex-column gap-1">
<div class="d-flex flex-row">
<div class="d-flex flex-row justify-content-center align-items-center" style="width: 30px;">
<i class="fa-solid fa-clock fa-lg"></i>
</div>
<span>
<span class="fw-bold">{{ "Proposed appointment duration: " }}</span>
{{ formattedSurveyTimeslotDuration }}
</span>
</div>
<div class="d-flex flex-row">
<div class="d-flex flex-row justify-content-center align-items-center" style="width: 30px;">
<i class="fa-solid fa-calendar-check fa-lg"></i>
</div>
<span>
<span class="fw-bold">{{ "Maximum selectable timeslots: " }}</span>
{{ $props.survey.maxSelections}}
</span>
</div>
<div class="d-flex flex-row">
<div class="d-flex flex-row justify-content-center align-items-center" style="width: 30px;">
<i class="fa-solid fa-user-slash fa-lg"></i>
</div>
<span>
<span class="fw-bold">{{ "Are participants anonymized: " }}</span>
{{ $props.survey.areParticipantsAnonymized ? "yes" : "no" }}
</span>
</div>
<div class="d-flex flex-row">
<div class="d-flex flex-row justify-content-center align-items-center" style="width: 30px;">
<i class="fa-solid fa-person-booth fa-lg"></i>
</div>
<span>
<span class="fw-bold">{{ "Are votes anonymized: " }}</span>
{{ $props.survey.areSelectionsAnonymized ? "yes" : "no" }}
</span>
</div>
</div>
<hr class="my-2">
<div class="d-flex flex-column gap-2">
<div class="d-flex flex-column gap-3">
<coodle-survey-basic-info
v-if="$props.survey"
v-model:surveyFormDataModelValue="surveyFormData"
:survey="$props.survey"
:parsedSelectedTimeslot="parsedSelectedTimeslot"
:isEditInProgress="isEditInProgress"
/>
<hr>
<div v-if="!isEditInProgress" class="d-flex flex-column gap-2">
<coodle-survey-voting-table
v-if="$props.survey"
:uid="$props.uid"
:survey="$props.survey"
:timeslots="parsedTimeslotsForVotingTable"
:survey="$props.survey"
/>
</div>
<div v-else class="d-flex flex-column gap-3">
<div class="row">
<div class="col-12 col-lg-4">
<coodle-survey-participants
v-if="surveyFormData?.participants"
v-model:participantsModelValue="surveyFormData.participants"
/>
</div>
<div class="col-12 col-lg-8">
<div class="d-flex flex-column gap-3">
<coodle-survey-duration-selector
v-if="surveyFormData?.timeslotDuration"
v-model:durationModelValue="surveyFormData.timeslotDuration"
:survey="$props.survey"
/>
<coodle-survey-calendar />
</div>
</div>
</div>
<div class="d-flex flex-row gap-2 justify-content-end align-items-start px-0">
<div>
<div
@click="isEditInProgress = false"
:class="isDarkMode ? 'btn-outline-light' : 'btn-outline-dark'"
class="btn text-nowrap"
>
{{ "Cancel" }}
</div>
</div>
<div>
<div
@click="submitForm()"
:class="isDarkMode ? 'btn-light' : 'btn-dark'"
class="btn text-nowrap"
>
{{ "Save" }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>`,
@@ -0,0 +1,235 @@
import { formatDate } from "../../../../../helpers/DateHelpers.js";
export default {
name: "CoodleSurveyBasicInfo",
props: {
survey: Object | null,
surveyFormDataModelValue: Object | null,
parsedSelectedTimeslot: Object | null,
isEditInProgress: Boolean,
},
emits: ["update:surveyFormDataModelValue"],
computed: {
formattedSurveyDescription() {
return this.$props.survey?.description?.replaceAll("\n", "<br>");
},
formattedSurveyTimeslotDuration() {
const minutes = this.$props.survey.timeslotDuration % 60;
const hours = (this.$props.survey.timeslotDuration - minutes) / 60;
let formattedDuration = hours ? hours + " hr " : "";
formattedDuration += minutes + " min";
return formattedDuration;
},
formattedSurveyEndsAt() {
if (this.$props.survey?.endsAt) {
return formatDate(this.$props.survey.endsAt);
} else {
return "";
}
},
formattedSurveyCreatedAt() {
if (this.$props.survey?.createdAt) {
return formatDate(this.$props.survey.createdAt);
} else {
return "";
}
},
formattedSurveyUpdatedAt() {
if (this.$props.survey?.updatedAt) {
return formatDate(this.$props.survey.updatedAt);
} else {
return "";
}
},
formattedSurveyCanceledAt() {
if (this.$props.survey?.canceledAt) {
return formatDate(this.$props.survey.canceledAt);
} else {
return "";
}
},
formattedSurveyCompletedAt() {
if (this.$props.survey?.completedAt) {
return formatDate(this.$props.survey.completedAt);
} else {
return "";
}
},
surveyCreatorProfileHref() {
return this.$router.resolve({
name: "ProfilView",
params: { uid: this.survey?.creator?.uid },
}).href;
},
surveyFormData: {
get() {
return this.$props.surveyFormDataModelValue;
},
set(newValue) {
this.$emit("update:surveyFormDataModelValue", newValue);
},
},
},
template: /*html*/ `
<div class="d-flex flex-column gap-3">
<template v-if="!$props.isEditInProgress">
<span v-if="$props.survey?.completedAt" class="fst-italic">
{{ "This survey was completed on " + formattedSurveyCompletedAt + ". " }}
<span v-if="$props.survey?.selectedTimeslotId">
{{
"The timeslot (((timeslot))) was selected.".replace(
"(((timeslot)))",
parsedSelectedTimeslot?.weekday +
" " +
parsedSelectedTimeslot?.fullDate +
" " +
parsedSelectedTimeslot?.startTime +
"-" +
parsedSelectedTimeslot?.endTime
)
}}
</span>
<span v-else>{{ "No timeslot was selected." }}</span>
</span>
<span v-else-if="$props.survey?.canceledAt" class="fst-italic">
{{ "This survey was canceled on " + formattedSurveyCanceledAt + "." }}
</span>
<span
v-if="$props.survey?.description?.length"
class="text-wrap"
v-html="formattedSurveyDescription"
></span>
<div class="d-flex flex-column gap-1">
<div>
{{ "Created by " }}
<a :href="surveyCreatorProfileHref" :target="'_blank'">{{ this.$props.survey?.creator?.name }}</a>
{{ " on " + formattedSurveyCreatedAt}}
</div>
<div>{{ "Last edited on " + formattedSurveyUpdatedAt }}</div>
<div>{{ "Planned to end on " + formattedSurveyEndsAt }}</div>
</div>
<div class="d-flex flex-column gap-1">
<div class="d-flex flex-row">
<div class="d-flex flex-row justify-content-center align-items-center" style="width: 30px;">
<i class="fa-solid fa-clock fa-lg"></i>
</div>
<span>
<span class="fw-bold">{{ "Proposed appointment duration: " }}</span>
{{ formattedSurveyTimeslotDuration }}
</span>
</div>
<div class="d-flex flex-row">
<div class="d-flex flex-row justify-content-center align-items-center" style="width: 30px;">
<i class="fa-solid fa-calendar-check fa-lg"></i>
</div>
<span>
<span class="fw-bold">{{ "Maximum selectable timeslots: " }}</span>
{{ $props.survey.maxSelections}}
</span>
</div>
<div class="d-flex flex-row">
<div class="d-flex flex-row justify-content-center align-items-center" style="width: 30px;">
<i class="fa-solid fa-user-slash fa-lg"></i>
</div>
<span>
<span class="fw-bold">{{ "Are participants anonymized: " }}</span>
{{ $props.survey.areParticipantsAnonymized ? "yes" : "no" }}
</span>
</div>
<div class="d-flex flex-row">
<div class="d-flex flex-row justify-content-center align-items-center" style="width: 30px;">
<i class="fa-solid fa-person-booth fa-lg"></i>
</div>
<span>
<span class="fw-bold">{{ "Are votes anonymized: " }}</span>
{{ $props.survey.areSelectionsAnonymized ? "yes" : "no" }}
</span>
</div>
</div>
</template>
<template v-else>
<span class="fst-italic">{{ "Required inputs are marked with *." }}</span>
<div class="row">
<div class="d-flex flex-column gap-2 col-12 col-md-6 pb-2">
<div class="d-flex flex-column gap-1">
<label for="surveyTitleInput" class="fw-bold">{{ "* " + "Title" }}</label>
<input v-model="surveyFormData.title" id="surveyTitleInput" type="text" />
</div>
<div class="d-flex flex-column gap-1">
<label for="surveyDescriptionInput" class="fw-bold">{{ "Description" }}</label>
<textarea v-model="surveyFormData.description" rows="4" class="flex-grow-1" />
</div>
</div>
<div class="col-12 col-md-6">
<div class="row">
<div class="col-12 col-xxl-6 pb-2">
<div class="d-flex flex-column gap-1">
<label
for="surveyMaxSelectionsInput"
class="fw-bold text-nowrap text-truncate"
>
{{ "Maximum number of selections" }}
</label>
<input
v-model="surveyFormData.maxSelections"
id="surveyMaxSelectionsInput"
type="number"
class="flex-grow-1"
/>
</div>
</div>
<div class="col-12 col-xxl-6 pb-2">
<div class="d-flex flex-column gap-1">
<label
for="surveyEndsAtInput"
class="fw-bold text-nowrap text-truncate"
>
{{ "* " + "Planned end date" }}
</label>
<input
v-model="surveyFormData.endsAt"
id="surveyEndsAtInput"
type="date"
class="flex-grow-1"
/>
</div>
</div>
<div class="col-12">
<div class="row">
<div class="col-12 col-lg-6 d-flex flex-row align-items-center gap-2 pb-2">
<label
for="surveyAreParticipantsAnonymizedInput"
class="fw-bold text-nowrap text-truncate"
>
{{ "Are participants anonymous?" }}
</label>
<input
v-model="surveyFormData.areParticipantsAnonymized"
id="surveyAreParticipantsAnonymizedInput"
type="checkbox"
style="height:15px;width:15px;"
/>
</div>
<div class="col-12 col-lg-6 d-flex flex-row align-items-center gap-2 pb-2">
<label
for="surveyAreSelectionsAnonymizedInput"
class="fw-bold text-nowrap text-truncate"
>
{{ "Are votes anonymous?" }}
</label>
<input
v-model="surveyFormData.areSelectionsAnonymized"
id="surveyAreSelectionsAnonymizedInput"
type="checkbox"
style="height:15px;width:15px;"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
</div>
`,
};
@@ -0,0 +1,18 @@
export default {
name: "CoodleSurveyCalendar",
components: {},
props: {},
emits: [],
data() {
return {}
},
computed: {},
watch: {},
methods: {},
created() {},
template: /*html*/ `
<div>
calendar placeholder
</div>
`,
}
@@ -0,0 +1,91 @@
export default {
name: "CoodleSurveyDurationSelector",
props: {
durationModelValue: Number | null,
survey: Object,
},
emits: ["update:durationModelValue"],
data() {
return {
appointmentDurationPresets: [15, 30, 45, 60, 90, 120],
selectedDurationPreset: null,
durationCustomInput: null,
};
},
computed: {
duration: {
get() {
return this.$props.durationModelValue;
},
set(newValue) {
this.$emit("update:durationModelValue", newValue);
},
},
},
watch: {
durationCustomInput() {
if (this.durationCustomInput) {
this.duration = this.durationCustomInput;
this.selectedDurationPreset = null;
}
},
survey: {
handler() {
this.initializeInputs();
},
deep: true,
},
},
methods: {
selectAppointmentDurationPreset(preset) {
this.selectedDurationPreset = preset;
this.duration = preset;
this.durationCustomInput = null;
},
initializeInputs() {
this.selectedDurationPreset = null;
this.durationCustomInput = null;
if (this.appointmentDurationPresets.includes(this.duration)) {
this.selectedDurationPreset = this.duration;
} else {
this.durationCustomInput = this.duration;
}
},
},
created() {
this.initializeInputs();
},
template: /*html*/ `
<div class="d-flex flex-column gap-2">
<label class="fw-bold">{{ "* " + "Appointment duration (in minutes)" }}</label>
<div class="overflow-x-auto">
<div class="d-flex flex-row align-items-center gap-2">
<div
v-for="durationPreset in appointmentDurationPresets"
@click="selectAppointmentDurationPreset(durationPreset)"
type="button"
:class="{'fhc-primary-border-color': selectedDurationPreset === durationPreset}"
class="border border-2 d-flex justify-content-center align-items-center"
style="min-width:60px;height:40px;"
>
<span>{{ durationPreset }}</span>
</div>
<span class="text-nowrap ps-2">{{ "Custom duration: " }}</span>
<div
:class="{'fhc-primary-border-color': durationCustomInput}"
class="border border-2 d-flex justify-content-center align-items-center px-1"
style="width:60px;height:40px;"
>
<input
v-model="durationCustomInput"
type="number"
min="1"
class="border-0"
style="min-width:0; outline: none; background: none;"
/>
</div>
</div>
</div>
</div>
`,
};
@@ -0,0 +1,251 @@
export default {
name: "CoodleSurveyParticipants",
components: {},
props: {
participantsModelValue: Array,
},
emits: ["update:participantsModelValue"],
data() {
return {
searchInput: "",
isSearchingForParticipants: false,
searchResults: [],
areSearchResultsShown: false,
// todo: increase depending on feedback
warningGroupSize: 5,
// todo: remove
searchResultsDevExample: [
{
uid: "ma1000",
name: "Test User 10",
type: "user",
},
{
uid: "ma1001",
name: "Test User 11",
type: "user",
},
{
uid: "ma1002",
name: "Test User 12",
type: "user",
},
{
uid: "ma1003",
name: "Test User 13",
type: "user",
},
{
uid: "ABC-DEF",
name: "ABC-DEF",
type: "group",
users: [
{
uid: "ma1003",
name: "Test User 13",
},
{
uid: "ma1004",
name: "Test User 14",
},
],
},
{
uid: "GHI-JKL",
name: "GHI-JKL",
type: "group",
users: [
{
uid: "ma1004",
name: "Test User 14",
},
{
uid: "ma1005",
name: "Test User 15",
},
{
uid: "ma1006",
name: "Test User 16",
},
{
uid: "ma1007",
name: "Test User 17",
},
{
uid: "ma1008",
name: "Test User 18",
},
{
uid: "ma1009",
name: "Test User 19",
},
],
},
],
};
},
computed: {
participants: {
get() {
return this.$props.participantsModelValue;
},
set(newValue) {
this.$emit("update:participantsModelValue", newValue);
},
},
},
watch: {
searchInput() {
if (this.searchInput.length >= 2) {
this.areSearchResultsShown = true;
this.searchParticipants();
} else {
this.areSearchResultsShown = false;
}
},
},
methods: {
async hideSearchResults() {
// todo: clean up, don't use timeout
setTimeout(() => {
this.areSearchResultsShown = false;
}, 100);
},
searchParticipants() {
this.isSearchingForParticipants = true;
// todo
this.searchResults = this.searchResultsDevExample;
this.isSearchingForParticipants = false;
},
selectSearchResult(searchResult) {
if (
searchResult.type === "group" &&
searchResult.users.length > this.warningGroupSize
) {
let warningMessage =
"Group (((groupName))) contains (((n))) users. Are you sure you want to add all (((n))) users to your survey?";
warningMessage = warningMessage.replace(
"(((groupName)))",
searchResult.name,
);
warningMessage = warningMessage.replaceAll(
"(((n)))",
searchResult.users.length,
);
if (!window.confirm(warningMessage)) {
return;
}
}
const addedParticipants =
searchResult.type === "group"
? searchResult.users
: [searchResult];
this.addParticipants(addedParticipants);
},
addParticipants(newParticipants) {
newParticipants = newParticipants.filter((newParticipant) => {
return !this.participants.some(
(existingParticipant) =>
existingParticipant.uid === newParticipant.uid,
);
}).map((newParticipant) => {
return {
uid: newParticipant.uid,
name: newParticipant.name,
isCalendarShown: false,
}
});
this.participants = this.participants.concat(newParticipants);
},
removeParticipant(participantToBeRemoved) {
this.participants = this.participants.filter(
(participant) => participant.uid !== participantToBeRemoved.uid,
);
},
showCalendar(participantForCalendarToBeDisplayed) {
this.participants = this.participants.map((participant) => {
let updatedParticipant = { ...participant };
if (
participant.uid === participantForCalendarToBeDisplayed.uid
) {
updatedParticipant.isCalendarShown = true;
}
return updatedParticipant;
});
},
},
template: /*html*/ `
<div class="d-flex flex-column gap-2 mb-3">
<label for="searchInput" class="fw-bold">{{ "Participants" }}</label>
<div class="position-relative" style="max-width:300px;">
<input
v-model="searchInput"
@blur="hideSearchResults()"
id="searchInput"
class="w-100"
/>
<div
v-if="areSearchResultsShown"
id="coodleParticipantsSearchResults"
class="position-absolute w-100 d-flex flex-column overflow-y-auto"
style="height:200px;"
>
<div
v-if="isSearchingForParticipants"
class="flex-grow-1 d-flex flex-row justify-content-center align-items-center"
>
<i class="fa-solid fa-spinner fa-spin fa-xl"></i>
</div>
<div
v-else-if="!searchResults.length"
class="d-flex flex-row justify-content-center align-items-center py-3"
>
<span>{{ "No results found!" }}</span>
</div>
<div v-else>
<div
v-for="searchResult in searchResults"
@click="selectSearchResult(searchResult)"
type="button"
class="coodleParticipantSearchResult d-flex flex-row gap-2 justify-content-start align-items-center py-1 px-2 border-bottom border-1"
>
<i v-if="searchResult.type === 'user'" class="fa-solid fa-user"></i>
<i v-else-if="searchResult.type === 'group'" class="fa-solid fa-user-group"></i>
<span>{{ searchResult.name }}</span>
</div>
</div>
</div>
</div>
<span v-if="!participants.length" class="fst-italic">
{{ "No participants added yet!" }}
</span>
<div v-else class="d-flex flex-row gap-2 flex-wrap">
<div
v-for="participant in participants"
class="d-flex flex-row align-items-center gap-4 py-1 px-3 border border-1 rounded-pill"
>
<span>{{ participant.name }}</span>
<div class="d-flex flex-row align-items-center gap-3">
<i
v-if="participant.isCalendarShown"
@click="participant.isCalendarShown = false"
type="button"
class="fa-solid fa-calendar-check fhc-primary-color"
></i>
<i
v-else
@click="showCalendar(participant)"
type="button"
class="fa-regular fa-calendar"
></i>
<i @click="removeParticipant(participant)" type="button" class="fa-solid fa-xmark"></i>
</div>
</div>
</div>
</div>
`,
};
@@ -143,6 +143,7 @@ export default {
},
submitVote() {
// todo
console.log("voting...");
},
submitFinalSelection() {
if (!this.$props.survey.id) return;
@@ -153,7 +154,7 @@ export default {
window.alert("You haven't made a selection!");
return;
} else if (this.selectedTimeslotId === "none") {
selectedTimeslot = "\"No appointment is possible\"";
selectedTimeslot = '"No appointment is possible"';
} else {
const selectedTimeslotInfo = this.$props.timeslots.find(
(timeslot) => timeslot.id === this.selectedTimeslotId,
@@ -181,6 +182,37 @@ export default {
) {
return;
}
let participantsThatHaveNotVoted =
this.participantsWithoutAuthUser.reduce((sum, participant) => {
return (
sum +
(!Object.values(participant.selection).filter(
(isSelected) => isSelected,
).length
? 1
: 0)
);
}, 0);
if (
!Object.values(this.authUserParticipant.selection).filter(
(isSelected) => isSelected,
).length
) {
participantsThatHaveNotVoted++;
}
if (participantsThatHaveNotVoted) {
if (
!window.confirm(
"Not all participants have voted. Are you sure you want to proceed?",
)
) {
return;
}
}
console.log("finalizing...");
// todo
},
+16 -5
View File
@@ -9,8 +9,9 @@ export default {
data() {
return {
uid: null,
// todo: return view to activeSurveysTable and survey to null
view: "survey",
// survey: null,
// todo: delete comment after dev
survey: {
id: 1,
creator: {
@@ -19,13 +20,13 @@ export default {
},
title: "Test Meeting",
description:
"To discuss many important matters. \n Another line of the description.",
"To discuss many important matters.\nAnother line of the description.",
timeslotDuration: 75,
maxSelections: 2,
areParticipantsAnonymized: false,
areSelectionsAnonymized: false,
selectedTimeslotId: null,
endsAt: "2026-06-23 23:30:30",
endsAt: "2026-06-23",
completedAt: null,
canceledAt: null,
createdAt: "2026-06-14 23:30:30",
@@ -43,6 +44,14 @@ export default {
id: 3,
startsAt: "2026-06-25 07:00:00",
},
{
id: 4,
startsAt: "2026-06-24 09:00:00",
},
{
id: 5,
startsAt: "2026-06-27 12:30:00",
},
],
participants: [
{
@@ -57,12 +66,12 @@ export default {
},
{
uid: "ma1436",
name: "Test User 2",
name: "Test Userrr 2",
selection: null,
},
{
uid: "ma1437",
name: "Test User 3",
name: "Test Userrrrr 3",
selection: [2,3],
},
],
@@ -70,6 +79,8 @@ export default {
1: 0,
2: 2,
3: 1,
4: 0,
5: 0,
"none": 1,
},
},
-11
View File
@@ -61,14 +61,3 @@ export function formatDate(d) {
);
}
}
/**
* adds some number of minutes to a date
*
* @param {Date} d - the date to which the minutes are added
* @param {int} m - number of minutes
* @returns {Date} updated date
*/
export function addMinutesToDate(d, m) {
return new Date(d.getTime() + m * 60000);
}