diff --git a/.gitignore b/.gitignore index b8c69ec88..57bb6717a 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,6 @@ node_modules ###< friendsofphp/php-cs-fixer ### /src/Resources/style/output.css -/**/*.d.ts /src/Resources/style/output.css.map test_ci_cypress.sh tests/e2e/cypress/videos/ diff --git a/src/Mealz/MealBundle/Controller/ApiController.php b/src/Mealz/MealBundle/Controller/ApiController.php index 2db121da2..78e0db763 100644 --- a/src/Mealz/MealBundle/Controller/ApiController.php +++ b/src/Mealz/MealBundle/Controller/ApiController.php @@ -200,16 +200,9 @@ public function listParticipantsByDate(DateTime $date): JsonResponse return new JsonResponse(['message' => 'Day not found'], 404); } - $list = []; - $data = $this->participationSrv->getParticipationList($day); + $participants = $this->participationSrv->getParticipationList($day); - foreach ($data as $participant) { - $list[] = $participant->getProfile()->getFirstName() . ' ' . $participant->getProfile()->getName(); - } - - $uniqueArray = array_unique($list); - - return new JsonResponse(array_values($uniqueArray), 200); + return new JsonResponse($participants, 200); } private function addSlots(array &$slotArray, array $slots, Day $day, ?int $activeParticipations): void diff --git a/src/Mealz/MealBundle/Controller/ParticipantController.php b/src/Mealz/MealBundle/Controller/ParticipantController.php index c58397903..c6a1c148e 100644 --- a/src/Mealz/MealBundle/Controller/ParticipantController.php +++ b/src/Mealz/MealBundle/Controller/ParticipantController.php @@ -438,8 +438,8 @@ private function addParticipationInfo(array $response, ArrayCollection $particip /** @var Participant $participant */ foreach ($participants as $participant) { $participationData = $this->participationHelper->getParticipationMealData($participant); - $response[$day->getId()][$participant->getProfile()->getFullName()]['booked'][] = $participationData; - $response[$day->getId()][$participant->getProfile()->getFullName()]['profile'] = $participant->getProfile()->getUsername(); + $response[$day->getId()][$this->participationHelper->getParticipantName($participant)]['booked'][] = $participationData; + $response[$day->getId()][$this->participationHelper->getParticipantName($participant)]['profile'] = $participant->getProfile()->getUsername(); } return $response; diff --git a/src/Mealz/MealBundle/Helper/ParticipationHelper.php b/src/Mealz/MealBundle/Helper/ParticipationHelper.php index 96804efbb..592f53030 100644 --- a/src/Mealz/MealBundle/Helper/ParticipationHelper.php +++ b/src/Mealz/MealBundle/Helper/ParticipationHelper.php @@ -115,6 +115,16 @@ public function getParticipationMealData(Participant $participant): array return $participationData; } + public function getParticipantName(Participant $participant): string + { + $fullname = $participant->getProfile()->getFullName(); + if (true === $participant->getProfile()->isGuest()) { + $fullname .= ' (Guest)'; + } + + return $fullname; + } + protected function compareNameOfParticipants(Participant $participant1, Participant $participant2): int { $result = strcasecmp($participant1->getProfile()->getName(), $participant2->getProfile()->getName()); @@ -144,7 +154,7 @@ private function getParticipationbySlot(Participant $participant, ?Slot $slot, b $combinedDishes = $this->getCombinedDishesFromMeal($meal, $participant); if (true === $meal->isParticipant($participant) && (null === $slot || $slot->isDisabled() || $slot->isDeleted())) { - $slots[''][$participant->getProfile()->getFullName()] = $this->getParticipationData( + $slots[''][$this->getParticipantName($participant)] = $this->getParticipationData( $meal, $profile, $participant, @@ -154,7 +164,7 @@ private function getParticipationbySlot(Participant $participant, ?Slot $slot, b } if (true === $meal->isParticipant($participant)) { - $slots[$slot->getTitle()][$participant->getProfile()->getFullName()] = $this->getParticipationData( + $slots[$slot->getTitle()][$this->getParticipantName($participant)] = $this->getParticipationData( $meal, $profile, $participant, diff --git a/src/Mealz/MealBundle/Service/ParticipationService.php b/src/Mealz/MealBundle/Service/ParticipationService.php index b9b773d9f..f6b0c8291 100644 --- a/src/Mealz/MealBundle/Service/ParticipationService.php +++ b/src/Mealz/MealBundle/Service/ParticipationService.php @@ -248,7 +248,23 @@ public function getCountByMeal(Meal $meal, bool $withoutCombined = false): int public function getParticipationList(Day $day): array { - return $this->participantRepo->getParticipantsByDay($day->getDateTime(), ['load_meal' => false]); + $participants = $this->participantRepo->getParticipantsByDay($day->getDateTime(), ['load_meal' => false]); + + $profiles = array_map( + fn ($participant) => $participant->getProfile(), + $participants + ); + + $profileData = array_map( + fn ($profile) => [ + 'user' => $profile->getUsername(), + 'fullName' => $profile->getFullName(), + 'roles' => $profile->getRoles(), + ], + array_unique($profiles) + ); + + return $profileData; } /** diff --git a/src/Resources/env.d.ts b/src/Resources/env.d.ts new file mode 100644 index 000000000..151aa6856 --- /dev/null +++ b/src/Resources/env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/src/Resources/src/api/getParticipationsByDay.ts b/src/Resources/src/api/getParticipationsByDay.ts index 2e317160c..a59c408c8 100644 --- a/src/Resources/src/api/getParticipationsByDay.ts +++ b/src/Resources/src/api/getParticipationsByDay.ts @@ -1,4 +1,6 @@ import useApi from '@/api/api'; +import type { IProfile } from '@/stores/profilesStore'; +import type { Dictionary } from '@/types/types'; import { onMounted, readonly, ref } from 'vue'; /** @@ -7,7 +9,7 @@ import { onMounted, readonly, ref } from 'vue'; * @returns list of participants */ export function useParticipationsListData(date: string) { - const listDataState = ref([]); + const listDataState = ref([]); const loaded = ref(false); const useParticipationsError = ref(false); @@ -20,16 +22,21 @@ export function useParticipationsListData(date: string) { return; } - const { error, response: listData, request } = useApi('GET', `/api/participations/day/${date}`); + const { + error, + response: listData, + request + } = useApi>('GET', `/api/participations/day/${date}`); useParticipationsError.value = error.value; if (loaded.value === false) { await request(); loaded.value = true; - listDataState.value = listData.value as string[]; + listDataState.value = Object.values(listData.value ?? {}); } } + return { useParticipationsError, listData: readonly(listDataState), diff --git a/src/Resources/src/components/dashboard/MealData.vue b/src/Resources/src/components/dashboard/MealData.vue index e4c111793..2f49f3ed2 100644 --- a/src/Resources/src/components/dashboard/MealData.vue +++ b/src/Resources/src/components/dashboard/MealData.vue @@ -11,6 +11,7 @@ {{ title }} diff --git a/src/Resources/src/components/dashboard/VariationsData.vue b/src/Resources/src/components/dashboard/VariationsData.vue index 2463f90ef..66cc12f3a 100644 --- a/src/Resources/src/components/dashboard/VariationsData.vue +++ b/src/Resources/src/components/dashboard/VariationsData.vue @@ -18,6 +18,7 @@ {{ locale.substring(0, 2) === 'en' ? variation.title.en : variation.title.de }} diff --git a/src/Resources/src/components/dishes/DishTableRow.vue b/src/Resources/src/components/dishes/DishTableRow.vue index cae0d1493..e3c908c9d 100644 --- a/src/Resources/src/components/dishes/DishTableRow.vue +++ b/src/Resources/src/components/dishes/DishTableRow.vue @@ -12,7 +12,7 @@ { if (showParticipations.value === true) { isLoading.value = true; - participations.value = (await getParticipantsForEvent(props.date)) as string[]; + participations.value = ((await getParticipantsForEvent(props.date)) as string[]).sort(); isLoading.value = false; } }); diff --git a/src/Resources/src/components/guest/GuestMeal.vue b/src/Resources/src/components/guest/GuestMeal.vue index f705bc201..5006d92d7 100644 --- a/src/Resources/src/components/guest/GuestMeal.vue +++ b/src/Resources/src/components/guest/GuestMeal.vue @@ -8,6 +8,7 @@ {{ title }} diff --git a/src/Resources/src/components/guest/GuestVariation.vue b/src/Resources/src/components/guest/GuestVariation.vue index b10acd144..78e550a4e 100644 --- a/src/Resources/src/components/guest/GuestVariation.vue +++ b/src/Resources/src/components/guest/GuestVariation.vue @@ -18,6 +18,7 @@ {{ locale.substring(0, 2) === 'en' ? variation.title.en : variation.title.de }} diff --git a/src/Resources/src/components/menuParticipants/AddParticipantsSearchBar.vue b/src/Resources/src/components/menuParticipants/AddParticipantsSearchBar.vue index 86283b818..9760a8d70 100644 --- a/src/Resources/src/components/menuParticipants/AddParticipantsSearchBar.vue +++ b/src/Resources/src/components/menuParticipants/AddParticipantsSearchBar.vue @@ -92,7 +92,7 @@ const props = defineProps<{ weekId: number; }>(); -const { ProfilesState, fetchAbsentingProfiles } = useProfiles(props.weekId); +const { ProfilesState, fetchAbsentingProfiles, getDisplayName } = useProfiles(props.weekId); const { t } = useI18n(); const slot = useSlots(); @@ -130,13 +130,6 @@ const filteredProfiles = computed(() => { }); }); -function getDisplayName(profile: IProfile) { - if (profile.roles.includes('ROLE_GUEST')) { - return `(${t('menu.guest')}) ${profile.fullName}`; - } - return profile.fullName; -} - function handleClick() { openProp.value = true; useDetectClickOutside(combobox, () => (openProp.value = false)); diff --git a/src/Resources/src/components/menuParticipants/MenuTable.vue b/src/Resources/src/components/menuParticipants/MenuTable.vue index 6b3d913e1..9222a41c9 100644 --- a/src/Resources/src/components/menuParticipants/MenuTable.vue +++ b/src/Resources/src/components/menuParticipants/MenuTable.vue @@ -20,7 +20,6 @@ import MenuTableBody from '@/components/menuParticipants/MenuTableBody.vue'; import MenuTableHead from './MenuTableHead.vue'; import { useProgress } from '@marcoschulte/vue3-progress'; import LoadingSpinner from '../misc/LoadingSpinner.vue'; -import process from 'node:process'; const props = defineProps<{ weekId: number; @@ -49,7 +48,7 @@ onMounted(async () => { }); // expose functions for testing -if (process?.env?.NODE_ENV === 'TEST') { +if (import.meta.env.VITE_ENV === 'TEST') { defineExpose({ loaded }); } diff --git a/src/Resources/src/components/menuParticipants/MenuTableBody.vue b/src/Resources/src/components/menuParticipants/MenuTableBody.vue index 346589bcb..117d7f81b 100644 --- a/src/Resources/src/components/menuParticipants/MenuTableBody.vue +++ b/src/Resources/src/components/menuParticipants/MenuTableBody.vue @@ -32,6 +32,7 @@ import { useI18n } from 'vue-i18n'; import MenuTableRow from './MenuTableRow.vue'; import MenuTableDataRows from '@/components/menuParticipants/MenuTableDataRows.vue'; import { computed } from 'vue'; +import getDisplayName from '@/services/useConvertDisplayName'; const props = defineProps<{ weekId: number; @@ -43,7 +44,12 @@ const { t } = useI18n(); const participants = computed(() => getParticipants()); const filteredParticipants = computed(() => { - if (getFilter() === '') return participants.value; - return participants.value.filter((participant) => participant.toLowerCase().includes(getFilter().toLowerCase())); + if (getFilter() === '') { + return participants.value.map((participant) => getDisplayName(participant, t)); + } + + return participants.value + .filter((participant) => participant.toLowerCase().includes(getFilter().toLowerCase())) + .map((participant) => getDisplayName(participant, t)); }); diff --git a/src/Resources/src/components/misc/VeggiIcon.vue b/src/Resources/src/components/misc/VeggiIcon.vue index fb3c9931a..3a82425ca 100644 --- a/src/Resources/src/components/misc/VeggiIcon.vue +++ b/src/Resources/src/components/misc/VeggiIcon.vue @@ -1,5 +1,5 @@ +
+ {{ t('flashMessage.success.participations.no') }} +
diff --git a/src/Resources/src/components/participations/ParticipantsTableRow.vue b/src/Resources/src/components/participations/ParticipantsTableRow.vue index 577a8952e..1e99bd0d0 100644 --- a/src/Resources/src/components/participations/ParticipantsTableRow.vue +++ b/src/Resources/src/components/participations/ParticipantsTableRow.vue @@ -6,7 +6,7 @@ >
- {{ participantName }} + {{ getDisplayName(participantName, t) }} { } }); -if (process?.env?.NODE_ENV === 'TEST') { +if (import.meta.env.VITE_ENV === 'TEST') { defineExpose({ tableHeight }); } diff --git a/src/Resources/src/locales/en.json b/src/Resources/src/locales/en.json index 0b0d7e5c7..9edb6282d 100644 --- a/src/Resources/src/locales/en.json +++ b/src/Resources/src/locales/en.json @@ -286,7 +286,7 @@ "notFound": "No profiles found for this query", "shortQuery": "Min. 3 Buchstaben für Suche eingeben", "search": "Filter participant", - "guest": "Gast", + "guest": "Guest", "noMeals": "No dishes selected yet" }, "printList": { diff --git a/src/Resources/src/main.ts b/src/Resources/src/main.ts index d884d89d3..210da9a74 100644 --- a/src/Resources/src/main.ts +++ b/src/Resources/src/main.ts @@ -27,7 +27,7 @@ const i18n = createI18n({ // fill stores with data Promise.all([userDataStore.fillStore(), environmentStore.fillStore()]).then(() => { const MainApp = createApp(App); - MainApp.config.performance = process.env.NODE_ENV !== 'production'; // enable Vue Devtools + MainApp.config.performance = import.meta.env.VITE_ENV !== 'production'; // enable Vue Devtools MainApp.use(i18n); MainApp.use(router); MainApp.use(VueScreen); diff --git a/src/Resources/src/services/filterParticipantsList.ts b/src/Resources/src/services/filterParticipantsList.ts index ef38011d9..16fd2dfbc 100644 --- a/src/Resources/src/services/filterParticipantsList.ts +++ b/src/Resources/src/services/filterParticipantsList.ts @@ -1,8 +1,9 @@ import { useParticipationsListData } from '@/api/getParticipationsByDay'; +import type { IProfile } from '@/stores/profilesStore'; import { type Ref, computed, reactive } from 'vue'; interface ParticipantState { - participants: Readonly>; + participants: Readonly>; filterValue: string; isLoading: boolean; error: string; @@ -10,8 +11,9 @@ interface ParticipantState { export function filterParticipantsList(date: string) { const { listData } = useParticipationsListData(date); + const participations = reactive({ - participants: listData, + participants: listData as Ref, filterValue: '', isLoading: false, error: '' @@ -27,8 +29,8 @@ export function filterParticipantsList(date: string) { ); }); - function participantsContainString(participant: string, filterInput: string) { - return participant.toLowerCase().includes(filterInput.toLowerCase()); + function participantsContainString(participant: IProfile, filterInput: string) { + return participant.fullName.toLowerCase().includes(filterInput.toLowerCase()); } return { diff --git a/src/Resources/src/services/useConvertDisplayName.ts b/src/Resources/src/services/useConvertDisplayName.ts new file mode 100644 index 000000000..8eb04ec58 --- /dev/null +++ b/src/Resources/src/services/useConvertDisplayName.ts @@ -0,0 +1,6 @@ +export default function getDisplayName(fullname: string, t: (str: string) => string) { + if (fullname.includes('(Guest)')) { + return `${fullname.split(' (Guest)')[0]} (${t('menu.guest')})`; + } + return fullname; +} diff --git a/src/Resources/src/stores/profilesStore.ts b/src/Resources/src/stores/profilesStore.ts index f58220caa..2a19cbe1a 100644 --- a/src/Resources/src/stores/profilesStore.ts +++ b/src/Resources/src/stores/profilesStore.ts @@ -5,6 +5,7 @@ import getProfileWithHash from '@/api/getProfileWithHash'; import { type IMessage, isMessage } from '@/interfaces/IMessage'; import useFlashMessage from '@/services/useFlashMessage'; import { FlashMessageType } from '@/enums/FlashMessage'; +import { useI18n } from 'vue-i18n'; interface IProfilesState { profiles: IProfile[]; @@ -41,6 +42,7 @@ export function useProfiles(weekId: number) { }); const { sendFlashMessage } = useFlashMessage(); + const { t } = useI18n(); watch( () => ProfilesState.error, @@ -86,9 +88,22 @@ export function useProfiles(weekId: number) { return null; } + /** + * Creates a localized string with the name of the profile. + * @param profile The profile + * @returns The localized string + */ + function getDisplayName(profile: IProfile) { + if (profile.roles.includes('ROLE_GUEST')) { + return `(${t('menu.guest')}) ${profile.fullName}`; + } + return profile.fullName; + } + return { ProfilesState: readonly(ProfilesState), fetchAbsentingProfiles, - fetchProfileWithHash + fetchProfileWithHash, + getDisplayName }; } diff --git a/src/Resources/src/views/PrintableList.vue b/src/Resources/src/views/PrintableList.vue index fb9933e5a..aa19efcf5 100644 --- a/src/Resources/src/views/PrintableList.vue +++ b/src/Resources/src/views/PrintableList.vue @@ -77,7 +77,7 @@ :class="[index === 0 ? 'border-gray-300' : 'border-gray-200', 'border-t']" > - {{ String(participantName) }} + {{ getDisplayName(String(participantName), t) }} { .parent() .find('table') .find('td') - .contains('John Doe'); + .contains('(Gast) Doe, John'); }); }); }); \ No newline at end of file