Merge pull request #60 from AlexisNP/bugfix/quick-create-event-modal

Bugfix/quick create event modal
This commit is contained in:
AlexisNP
2024-12-20 11:49:14 +01:00
committed by GitHub
13 changed files with 145 additions and 34 deletions

View File

@@ -36,8 +36,8 @@ function openEventCreatePopover() {
* @param e The closing event (can be keydown or click) * @param e The closing event (can be keydown or click)
*/ */
function handleClosing(e: Event) { function handleClosing(e: Event) {
popoverOpen.value = false
e.preventDefault() e.preventDefault()
popoverOpen.value = false
} }
</script> </script>
@@ -54,8 +54,6 @@ function handleClosing(e: Event) {
:trap-focus="true" :trap-focus="true"
class="pl-3 w-[30rem] max-w-full border-indigo-200 dark:bg-slate-950 dark:border-indigo-950" class="pl-3 w-[30rem] max-w-full border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
@escape-key-down="handleClosing" @escape-key-down="handleClosing"
@focus-outside="handleClosing"
@interact-outside="handleClosing"
@pointer-down-outside="handleClosing" @pointer-down-outside="handleClosing"
> >
<CalendarFormCreateEvent @event-created="handleClosing" /> <CalendarFormCreateEvent @event-created="handleClosing" />

View File

@@ -1,8 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
const { eventSkeleton, lastActiveEvent, isEditEventModalOpen } = storeToRefs(useCalendar()) const { eventSkeleton, lastActiveEvent, isEditEventModalOpen } = storeToRefs(useCalendar())
const isLoading = ref(false)
// Watch the popover state // Watch the popover state
watch(isEditEventModalOpen, (hasOpened, _o) => { watch(isEditEventModalOpen, (hasOpened, _o) => {
if (hasOpened && lastActiveEvent.value) { if (hasOpened && lastActiveEvent.value) {
@@ -15,10 +13,8 @@ watch(isEditEventModalOpen, (hasOpened, _o) => {
* *
* @param e The closing event (can be keydown or click) * @param e The closing event (can be keydown or click)
*/ */
function handleClosing(e: Event) { function handleClosing() {
if (isLoading.value) { isEditEventModalOpen.value = false
e.preventDefault()
}
} }
</script> </script>
@@ -43,7 +39,7 @@ function handleClosing(e: Event) {
</UiDialogDescription> </UiDialogDescription>
</header> </header>
<CalendarFormUpdateEvent /> <CalendarFormUpdateEvent @event-updated="handleClosing" />
</UiDialogContent> </UiDialogContent>
</UiDialog> </UiDialog>
</template> </template>

View File

@@ -3,6 +3,8 @@ import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea, PhPencilSimpl
import { useToast } from "~/components/ui/toast"; import { useToast } from "~/components/ui/toast";
import type { APIError } from "~/models/Errors"; import type { APIError } from "~/models/Errors";
const emit = defineEmits(["event-updated"])
const { resetSkeleton, updateEventFromSkeleton, cancelLatestRequest } = useCalendar() const { resetSkeleton, updateEventFromSkeleton, cancelLatestRequest } = useCalendar()
const { eventSkeleton, lastActiveEvent } = storeToRefs(useCalendar()) const { eventSkeleton, lastActiveEvent } = storeToRefs(useCalendar())
@@ -22,6 +24,14 @@ async function handleAction() {
try { try {
await updateEventFromSkeleton() await updateEventFromSkeleton()
emit("event-updated")
toast({
title: t("entity.calendar.event.updatedToast.title", { event: eventSkeleton.value.title }),
variant: "success",
duration: 3000
})
} catch (err) { } catch (err) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const apiError = (err as any).data as APIError const apiError = (err as any).data as APIError

View File

@@ -1,11 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PhGlobeHemisphereWest, PhList } from "@phosphor-icons/vue" import { PhCompass, PhList } from "@phosphor-icons/vue"
import type { SidebarMenuActionType } from "./SidebarProps"; import type { SidebarMenuActionType } from "./SidebarProps";
const { revealAdvancedSearch } = useCalendar() const { revealAdvancedSearch } = useCalendar()
const { currentMenu } = storeToRefs(useUiStore()) const { currentMenu } = storeToRefs(useUiStore())
const user = useSupabaseUser() // const user = useSupabaseUser()
function handleMenuItemAction(actionType: SidebarMenuActionType) { function handleMenuItemAction(actionType: SidebarMenuActionType) {
if (actionType === "event-search") { if (actionType === "event-search") {
@@ -23,19 +23,19 @@ function handleMenuItemAction(actionType: SidebarMenuActionType) {
</UiButton> </UiButton>
</li> </li>
<li v-if="user"> <li>
<UiTooltipProvider :delay-duration="50"> <UiTooltipProvider :delay-duration="50">
<UiTooltip> <UiTooltip>
<UiTooltipTrigger as-child> <UiTooltipTrigger as-child>
<UiButton variant="ghost" size="icon" class="rounded-full" as-child> <UiButton variant="ghost" size="icon" class="rounded-full" as-child>
<RouterLink to="/my"> <RouterLink to="/explore">
<PhGlobeHemisphereWest size="24" weight="fill" /> <PhCompass size="24" weight="fill" />
</RouterLink> </RouterLink>
</UiButton> </UiButton>
</UiTooltipTrigger> </UiTooltipTrigger>
<UiTooltipContent :side="'right'" :side-offset="6"> <UiTooltipContent :side="'right'" :side-offset="6">
<p> <p>
{{ $t('entity.world.namePlural') }} {{ $t('pages.explore.menuLabel') }}
</p> </p>
</UiTooltipContent> </UiTooltipContent>
</UiTooltip> </UiTooltip>

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue" import { computed } from "vue"
import { PhCheckCircle, PhGear, PhGlobeHemisphereWest, PhLaptop, PhMoon, PhPalette, PhSignIn, PhSignOut, PhSun, PhTranslate, PhUserCircle } from "@phosphor-icons/vue" import { PhCheckCircle, PhGear, PhUser, PhLaptop, PhMoon, PhPalette, PhSignIn, PhSignOut, PhSun, PhTranslate, PhUserCircle } from "@phosphor-icons/vue"
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
const router = useRouter() const router = useRouter()
@@ -82,9 +82,9 @@ function pushRoute(to: AvailableRoutes) {
</p> </p>
<UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="pushRoute('/my')"> <UiDropdownMenuItem class="flex gap-[.5ch] items-center rounded-none" @click="pushRoute('/my')">
<PhGlobeHemisphereWest size="20" weight="fill" /> <PhUser size="20" weight="fill" />
<span> <span>
{{ $t('entity.world.namePlural') }} {{ $t('ui.sidebarMenu.profile') }}
</span> </span>
</UiDropdownMenuItem> </UiDropdownMenuItem>

View File

@@ -41,6 +41,7 @@ export default defineI18nConfig(() => ({
greeting: "Connected as {user}", greeting: "Connected as {user}",
anonymousGreeting: "Preferences", anonymousGreeting: "Preferences",
sidebarMenu: { sidebarMenu: {
profile: "Profile",
appearance: "Appearance", appearance: "Appearance",
language: "Language", language: "Language",
account: "Account", account: "Account",
@@ -94,9 +95,14 @@ export default defineI18nConfig(() => ({
calendar: { calendar: {
nameSingular: "Calendar", nameSingular: "Calendar",
namePlural: "Calendars", namePlural: "Calendars",
namePublicSingular: "Public Calendar",
namePublicPlural: "Public Calendars",
addSingle: "Add a calendar", addSingle: "Add a calendar",
notFound: "Calendar not found",
notFoundDescription: "The link is not valid or the calendar has been deleted / archived.",
notFoundForWorld: "No calendar for this world… yet !", notFoundForWorld: "No calendar for this world… yet !",
isLoading: "Calendar is loading…", isLoading: "Calendar is loading…",
hasXEvents: "This calendar has {count} public events",
date: { date: {
start: "Start date", start: "Start date",
end: "End date", end: "End date",
@@ -149,6 +155,9 @@ export default defineI18nConfig(() => ({
title: "Edit event", title: "Edit event",
subtitle: "Update event data", subtitle: "Update event data",
}, },
updatedToast: {
title: "Event \"{event}\" has been successfuly updated.",
},
editErrors: { editErrors: {
toastTitle: "Event wasn't updated.", toastTitle: "Event wasn't updated.",
title_too_big: "Title should be less than 120 characters long.", title_too_big: "Title should be less than 120 characters long.",
@@ -224,6 +233,12 @@ export default defineI18nConfig(() => ({
nameSingular: "Character", nameSingular: "Character",
namePlural: "Characters", namePlural: "Characters",
} }
},
pages: {
explore: {
menuLabel: "Explore",
title: "Explore worlds",
}
} }
}, },
fr: { fr: {
@@ -264,6 +279,7 @@ export default defineI18nConfig(() => ({
greeting: "Connecté en tant que {user}", greeting: "Connecté en tant que {user}",
anonymousGreeting: "Préférences", anonymousGreeting: "Préférences",
sidebarMenu: { sidebarMenu: {
profile: "Profil",
appearance: "Apparence", appearance: "Apparence",
language: "Langue", language: "Langue",
account: "Compte", account: "Compte",
@@ -317,9 +333,14 @@ export default defineI18nConfig(() => ({
calendar: { calendar: {
nameSingular: "Calendriers", nameSingular: "Calendriers",
namePlural: "Calendrier", namePlural: "Calendrier",
namePublicSingular: "Calendrier Public",
namePublicPlural: "Calendriers Publics",
addSingle: "Ajouter un calendrier", addSingle: "Ajouter un calendrier",
notFound: "Aucun calendrier trouvé",
notFoundDescription: "Le lien n'est pas valide ou le calendrier a été supprimé / archivé.",
notFoundForWorld: "Aucun calendrier pour ce monde… pour l'instant !", notFoundForWorld: "Aucun calendrier pour ce monde… pour l'instant !",
isLoading: "Chargement du calendrier…", isLoading: "Chargement du calendrier…",
hasXEvents: "Ce calendrier contient {count} évènements publiques",
date: { date: {
start: "Date de début", start: "Date de début",
end: "Date de fin", end: "Date de fin",
@@ -372,6 +393,9 @@ export default defineI18nConfig(() => ({
title: "Modifier l'évènement", title: "Modifier l'évènement",
subtitle: "Mettre à jour les données de l'évènement", subtitle: "Mettre à jour les données de l'évènement",
}, },
updatedToast: {
title: "L'évènement \"{event}\" a été modifié avec succès.",
},
editErrors: { editErrors: {
toastTitle: "L'évènement n'a pas été modifié", toastTitle: "L'évènement n'a pas été modifié",
title_too_big: "Le titre doit être inférieur à 120 caractères.", title_too_big: "Le titre doit être inférieur à 120 caractères.",
@@ -448,6 +472,12 @@ export default defineI18nConfig(() => ({
nameSingular: "Personnage", nameSingular: "Personnage",
namePlural: "Personnages", namePlural: "Personnages",
} }
},
pages: {
explore: {
menuLabel: "Explorer",
title: "Explorer les mondes",
}
} }
} }
} }

View File

@@ -16,6 +16,7 @@ export interface Calendar extends CalendarConfig {
shortId?: string shortId?: string
name: string name: string
events: CalendarEvent[] events: CalendarEvent[]
eventNb?: Array<{ count: number }>
state: CalendarState state: CalendarState
color?: string color?: string
world?: World world?: World

View File

@@ -39,7 +39,8 @@ export default defineNuxtConfig({
login: "/", login: "/",
callback: "/my/", callback: "/my/",
exclude: [ exclude: [
"/calendars(/*)?" "/calendars(/*)?",
"/explore(/*)?"
] ]
} }
}, },

View File

@@ -1,16 +1,23 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PhCircleNotch } from "@phosphor-icons/vue"; import { PhCalendarX, PhCircleNotch } from "@phosphor-icons/vue";
import type { Calendar } from "~/models/CalendarConfig"; import type { Calendar } from "~/models/CalendarConfig";
import type { Category } from "~/models/Category"; import type { Category } from "~/models/Category";
const route = useRoute() const route = useRoute()
const shortId = route.params.id const shortId = route.params.id
const { data: calendarData, pending: calPending } = await useLazyFetch("/api/calendars/query", { key: `calendar-${shortId}`, query: { shortId, full: true } }) const user = useSupabaseUser()
const { data: catData, pending: catPending } = await useLazyFetch("/api/calendars/categories/query", { key: `categories-${shortId}` })
const { data: calendarData, pending: calPending, refresh: calRefresh } = await useLazyFetch("/api/calendars/query", { key: `calendar-${shortId}`, query: { shortId, full: true } })
const { data: catData, pending: catPending, refresh: catRefresh } = await useLazyFetch("/api/calendars/categories/query", { key: `categories-${shortId}` })
const cal = computed<Calendar>(() => calendarData?.value?.data as Calendar) const cal = computed<Calendar>(() => calendarData?.value?.data as Calendar)
const categories = computed<Category[]>(() => catData?.value?.data as Category[]) const categories = computed<Category[]>(() => catData?.value?.data as Category[])
watch(user, () => {
calRefresh()
catRefresh()
})
</script> </script>
<template> <template>
@@ -34,4 +41,22 @@ const categories = computed<Category[]>(() => catData?.value?.data as Category[]
<Calendar :calendar-data="cal" :categories /> <Calendar :calendar-data="cal" :categories />
</div> </div>
<div v-else class="h-full w-full grid place-items-center">
<Head>
<Title>{{ $t("entity.calendar.notFound") }}</Title>
</Head>
<div class="grid justify-items-center opacity-80">
<PhCalendarX size="75" class="opacity-60" />
<Heading level="h1">
{{ $t("entity.calendar.notFound") }}
</Heading>
<p>
{{ $t('entity.calendar.notFoundDescription') }}
</p>
</div>
</div>
</template> </template>

11
pages/calendars/index.vue Normal file
View File

@@ -0,0 +1,11 @@
<script lang="ts" setup></script>
<template>
<main class="p-8">
<Head>
<Title>{{ $t("entity.world.namePlural") }}</Title>
</Head>
<Heading level="h1">Calendriers publics</Heading>
</main>
</template>

46
pages/explore.vue Normal file
View File

@@ -0,0 +1,46 @@
<script lang="ts" setup>
import type { Calendar } from "~/models/CalendarConfig";
const { data } = await useFetch("/api/calendars/query")
const availableCalendars = data.value?.data as Calendar[]
</script>
<template>
<main class="p-8">
<Head>
<Title>{{ $t("pages.explore.title") }}</Title>
</Head>
<Spacing size="lg">
<Heading level="h1">
{{ $t("pages.explore.title") }}
</Heading>
<Spacing size="lg">
<Heading level="h2">
{{ $t("entity.calendar.namePublicPlural") }}
</Heading>
<ul v-if="availableCalendars && availableCalendars?.length > 0" class="grid md:grid-cols-3 gap-2">
<li v-for="calendar in availableCalendars" :key="calendar.shortId">
<UiCard
class="w-full transition-all hover:bg-slate-50 dark:bg-gray-950 dark:hover:bg-indigo-950 dark:focus-within:outline-gray-900"
:link="`/calendars/${calendar.shortId}`"
>
<UiCardHeader>
<UiCardTitle class="text-xl pr-12">{{ calendar.name }}</UiCardTitle>
</UiCardHeader>
<UiCardContent>
<p>
{{ $t("entity.calendar.hasXEvents", { count: calendar.eventNb?.[0].count }) }}
</p>
</UiCardContent>
</UiCard>
</li>
</ul>
</Spacing>
</Spacing>
</main>
</template>

View File

@@ -20,7 +20,8 @@ export default defineEventHandler(async (event) => {
name, name,
today, today,
months:calendar_months (*), months:calendar_months (*),
state state,
eventNb:calendar_events(count)
` `
const fullFields = ` const fullFields = `
@@ -42,6 +43,7 @@ export default defineEventHandler(async (event) => {
category:calendar_event_categories!calendar_events_category_fkey (*), category:calendar_event_categories!calendar_events_category_fkey (*),
secondaryCategories:calendar_event_categories!calendar_event_categories_links (*) secondaryCategories:calendar_event_categories!calendar_event_categories_links (*)
), ),
eventNb:calendar_events(count),
world:worlds ( world:worlds (
id, id,
gmId:gm_id gmId:gm_id

View File

@@ -55,13 +55,6 @@ export const useCalendar = defineStore("calendar", () => {
isReadOnly.value = (!user) || (gmId !== user.value?.id) isReadOnly.value = (!user) || (gmId !== user.value?.id)
} }
// Watch for user changes
watch(user, () => {
if (activeCalendar.value) {
setReadStatus(activeCalendar.value.gmId!)
}
})
/** /**
* Month list (queried from API) * Month list (queried from API)
*/ */
@@ -80,9 +73,7 @@ export const useCalendar = defineStore("calendar", () => {
setDefaultDate(activeCalendar.value.today) setDefaultDate(activeCalendar.value.today)
selectDate(activeCalendar.value.today) selectDate(activeCalendar.value.today)
if (calendarData.world) { setReadStatus(activeCalendar.value.gmId!)
setReadStatus(activeCalendar.value.gmId!)
}
if (!params.day) { if (!params.day) {
params.day = defaultDate.value.day.toString() params.day = defaultDate.value.day.toString()