Merge pull request #9 from AlexisNP/bugfix/add-alert-modal-for-event-deletion

Added alert modals for events
This commit is contained in:
AlexisNP
2024-06-05 23:34:07 +02:00
committed by GitHub
17 changed files with 350 additions and 52 deletions

View File

@@ -7,8 +7,6 @@ import CenturyLayout from './state/centennially/Layout.vue'
import DecadeLayout from './state/decennially/Layout.vue'
import YearLayout from './state/yearly/Layout.vue'
const { isAdvancedSearchOpen, isEditEventModalOpen } = storeToRefs(useCalendar())
const route = useRoute()
const worldId = route.params.id
@@ -109,7 +107,8 @@ onMounted(() => {
</div>
</template>
<CalendarSearch v-model:model-value="isAdvancedSearchOpen" />
<CalendarFormUpdateEvent v-model:model-value="isEditEventModalOpen" />
<CalendarSearch />
<CalendarFormUpdateEvent />
<CalendarFormDeleteEvent />
</div>
</template>

View File

@@ -11,8 +11,8 @@ import {
PhDotsThreeOutlineVertical
} from '@phosphor-icons/vue'
const { defaultDate, getFormattedDateTitle, jumpToDate, revealEditEventModal, getRelativeString } = useCalendar()
const { deleteEvent } = useCalendarEvents()
const { defaultDate, getFormattedDateTitle, jumpToDate, getRelativeString } = useCalendar()
const { revealEditEventModal, revealDeleteEventModal } = useCalendarEvents()
const { lastActiveEvent } = storeToRefs(useCalendarEvents())
const props = defineProps<{
@@ -47,6 +47,15 @@ function deployEditModal() {
revealEditEventModal()
commandMenuOpened.value = false
}
/**
* Confirm event deletion
*/
function deployDeleteModal() {
lastActiveEvent.value = { ...props.event }
revealDeleteEventModal()
commandMenuOpened.value = false
}
</script>
<template>
@@ -146,7 +155,7 @@ function deployEditModal() {
<UiCommandList>
<UiCommandGroup>
<UiCommandItem value="edit-event" @select="deployEditModal"> Modifier </UiCommandItem>
<UiCommandItem value="delete-event" @select="deleteEvent(event.id!)"> Supprimer </UiCommandItem>
<UiCommandItem value="delete-event" @select="deployDeleteModal"> Supprimer </UiCommandItem>
</UiCommandGroup>
</UiCommandList>
</UiCommand>

View File

@@ -0,0 +1,69 @@
<script lang="ts" setup>
const { isDeleteEventModalOpen } = storeToRefs(useCalendarEvents())
const { resetSkeleton, deleteEventFromSkeleton } = useCalendarEvents()
const { eventSkeleton, lastActiveEvent } = storeToRefs(useCalendarEvents())
const formErrors = reactive<{ message: string | null }>({
message: null
})
// Watch the popover state
watch(isDeleteEventModalOpen, (hasOpened, _o) => {
if (hasOpened && lastActiveEvent.value) {
eventSkeleton.value = { ...lastActiveEvent.value }
}
})
async function handleAction() {
try {
await deleteEventFromSkeleton()
isDeleteEventModalOpen.value = false
} catch (err) {
if (err instanceof Error) {
formErrors.message = err.message
}
} finally {
resetSkeleton()
}
}
</script>
<template>
<UiAlertDialog v-model:open="isDeleteEventModalOpen">
<UiAlertDialogContent
:align="'center'"
:side="'right'"
:collision-padding="60"
:disable-outside-pointer-events="true"
:trap-focus="true"
class="min-w-96 bg-slate-900 border-slate-800"
>
<UiAlertDialogTitle> Supprimer l'évènement</UiAlertDialogTitle>
<UiAlertDialogDescription>
Les données associés à cet évènement seront supprimées et vous ne pourrez plus les récupérer !
</UiAlertDialogDescription>
<form>
<div class="grid grid-cols-2 gap-y-4">
<div class="text-red-500 ml-8">
<span class="text-sm">
{{ formErrors.message }}
</span>
</div>
</div>
</form>
<UiAlertDialogFooter>
<UiAlertDialogCancel>
Annuler
</UiAlertDialogCancel>
<UiAlertDialogAction class="destructive" @click="handleAction">
Supprimer
</UiAlertDialogAction>
</UiAlertDialogFooter>
</UiAlertDialogContent>
</UiAlertDialog>
</template>

View File

@@ -2,7 +2,8 @@
import { PhAlarm } from '@phosphor-icons/vue'
import { VisuallyHidden } from 'radix-vue'
const isModalOpened = defineModel<boolean>({ default: false })
const { isEditEventModalOpen } = storeToRefs(useCalendarEvents())
const { resetSkeleton, updateEventFromSkeleton } = useCalendarEvents()
const { eventSkeleton, lastActiveEvent } = storeToRefs(useCalendarEvents())
@@ -11,30 +12,30 @@ const formErrors = reactive<{ message: string | null }>({
})
// Watch the popover state
watch(isModalOpened, (hasOpened, _o) => {
watch(isEditEventModalOpen, (hasOpened, _o) => {
if (hasOpened && lastActiveEvent.value) {
eventSkeleton.value = { ...lastActiveEvent.value }
} else {
resetSkeleton()
}
})
async function handleSubmit() {
async function handleAction() {
try {
await updateEventFromSkeleton()
isModalOpened.value = false
isEditEventModalOpen.value = false
} catch (err) {
if (err instanceof Error) {
formErrors.message = err.message
}
} finally {
resetSkeleton()
}
}
</script>
<template>
<UiDialog v-model:open="isModalOpened" :modal="true">
<UiDialogContent
<UiAlertDialog v-model:open="isEditEventModalOpen">
<UiAlertDialogContent
:align="'center'"
:side="'right'"
:collision-padding="60"
@@ -43,14 +44,14 @@ async function handleSubmit() {
class="pl-3 min-w-96 bg-slate-900 border-slate-800"
>
<VisuallyHidden>
<UiDialogTitle> Modifier l'évènement</UiDialogTitle>
<UiAlertDialogTitle> Modifier l'évènement</UiAlertDialogTitle>
<UiDialogDescription>
<UiAlertDialogDescription>
Mettre à jour les données de l'évènement
</UiDialogDescription>
</UiAlertDialogDescription>
</VisuallyHidden>
<form @submit.prevent="handleSubmit">
<form>
<div class="grid grid-cols-2 gap-y-4">
<div class="col-span-2 ml-8">
<input
@@ -99,14 +100,16 @@ async function handleSubmit() {
{{ formErrors.message }}
</span>
</div>
<div class="text-right">
<UiButton size="sm" type="submit">
Sauvegarder
</UiButton>
</div>
</div>
</form>
</UiDialogContent>
</UiDialog>
<UiAlertDialogFooter>
<UiAlertDialogCancel>
Annuler
</UiAlertDialogCancel>
<UiAlertDialogAction @click="handleAction">
Sauvegarder
</UiAlertDialogAction>
</UiAlertDialogFooter>
</UiAlertDialogContent>
</UiAlertDialog>
</template>

View File

@@ -27,10 +27,9 @@ import {
import SearchList from './lists/SearchList.vue'
import type { Category } from '~/models/Category'
const { characters } = storeToRefs(useCharacters())
const { isAdvancedSearchOpen } = storeToRefs(useCalendar())
const { allEvents } = storeToRefs(useCalendarEvents())
const modalOpen = defineModel<boolean>({ default: false })
const { characters } = storeToRefs(useCharacters())
const searchQuery = ref<string>('')
// const searchEnough = computed<boolean>(() => searchQuery.value.length >= 2)
@@ -170,14 +169,14 @@ function resetSearch() {
* Opens the search Uidialog
*/
function openUiDialog() {
modalOpen.value = true
isAdvancedSearchOpen.value = true
}
/**
* Closes the search Uidialog
*/
function closeUiDialog() {
modalOpen.value = false
isAdvancedSearchOpen.value = false
}
/**
@@ -233,7 +232,7 @@ function handleCategorySelect(e: (Category)) {
</script>
<template>
<UiDialog v-model:open="modalOpen" @update:open="resetSearch()">
<UiDialog v-model:open="isAdvancedSearchOpen" @update:open="resetSearch()">
<UiDialogContent
class="flex flex-col flex-nowrap top-16 -translate-y-0 data-[state=closed]:slide-out-to-top-[5%] data-[state=open]:slide-in-from-top-[5%]"
:class="{

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import { type AlertDialogEmits, type AlertDialogProps, AlertDialogRoot, useForwardPropsEmits } from 'radix-vue'
const props = defineProps<AlertDialogProps>()
const emits = defineEmits<AlertDialogEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<AlertDialogRoot v-bind="forwarded">
<slot />
</AlertDialogRoot>
</template>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button/index'
const props = defineProps<AlertDialogActionProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AlertDialogAction v-bind="delegatedProps" :class="cn(buttonVariants(), props.class)">
<slot />
</AlertDialogAction>
</template>

View File

@@ -0,0 +1,20 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button/index'
const props = defineProps<AlertDialogCancelProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AlertDialogCancel v-bind="delegatedProps" :class="cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', props.class)">
<slot />
</AlertDialogCancel>
</template>

View File

@@ -0,0 +1,42 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
AlertDialogContent,
type AlertDialogContentEmits,
type AlertDialogContentProps,
AlertDialogOverlay,
AlertDialogPortal,
useForwardPropsEmits,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AlertDialogContentProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<AlertDialogContentEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<AlertDialogPortal>
<AlertDialogOverlay
class="fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
/>
<AlertDialogContent
v-bind="forwarded"
:class="
cn(
'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
props.class,
)
"
>
<slot />
</AlertDialogContent>
</AlertDialogPortal>
</template>

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import {
AlertDialogDescription,
type AlertDialogDescriptionProps,
} from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AlertDialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AlertDialogDescription
v-bind="delegatedProps"
:class="cn('text-sm text-muted-foreground', props.class)"
>
<slot />
</AlertDialogDescription>
</template>

View File

@@ -0,0 +1,21 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
:class="
cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
props.class,
)
"
>
<slot />
</div>
</template>

View File

@@ -0,0 +1,16 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div
:class="cn('flex flex-col gap-y-2 text-center sm:text-left', props.class)"
>
<slot />
</div>
</template>

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { AlertDialogTitle, type AlertDialogTitleProps } from 'radix-vue'
import { cn } from '@/lib/utils'
const props = defineProps<AlertDialogTitleProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<AlertDialogTitle
v-bind="delegatedProps"
:class="cn('text-lg font-semibold', props.class)"
>
<slot />
</AlertDialogTitle>
</template>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import { AlertDialogTrigger, type AlertDialogTriggerProps } from 'radix-vue'
const props = defineProps<AlertDialogTriggerProps>()
</script>
<template>
<AlertDialogTrigger v-bind="props">
<slot />
</AlertDialogTrigger>
</template>

View File

@@ -0,0 +1,9 @@
export { default as AlertDialog } from './AlertDialog.vue'
export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue'
export { default as AlertDialogContent } from './AlertDialogContent.vue'
export { default as AlertDialogHeader } from './AlertDialogHeader.vue'
export { default as AlertDialogTitle } from './AlertDialogTitle.vue'
export { default as AlertDialogDescription } from './AlertDialogDescription.vue'
export { default as AlertDialogFooter } from './AlertDialogFooter.vue'
export { default as AlertDialogAction } from './AlertDialogAction.vue'
export { default as AlertDialogCancel } from './AlertDialogCancel.vue'

View File

@@ -298,15 +298,6 @@ export const useCalendar = defineStore('calendar', () => {
isAdvancedSearchOpen.value = true
}
/**
* State for advanced search modal
*/
const isEditEventModalOpen: Ref<boolean> = ref<boolean>(false)
function revealEditEventModal() {
isEditEventModalOpen.value = true
}
/**
* Switches the active viewType
*
@@ -649,8 +640,6 @@ export const useCalendar = defineStore('calendar', () => {
isCurrentScreenActive,
isAdvancedSearchOpen,
revealAdvancedSearch,
isEditEventModalOpen,
revealEditEventModal,
convertDateToDays,
getDifferenceInDays,
areDatesIdentical,

View File

@@ -165,6 +165,24 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
}
}
/**
* State for event modal edition
*/
const isEditEventModalOpen: Ref<boolean> = ref<boolean>(false)
function revealEditEventModal() {
isEditEventModalOpen.value = true
}
/**
* State for event modal edition
*/
const isDeleteEventModalOpen: Ref<boolean> = ref<boolean>(false)
function revealDeleteEventModal() {
isDeleteEventModalOpen.value = true
}
/**
* EVENT CREATION FUNCTIONS
*/
@@ -205,19 +223,31 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
}
}
async function deleteEvent(eventId: number) {
if (!eventId) {
throw new Error('ID of the event is required')
}
async function deleteEventFromSkeleton() {
try {
await $fetch(`/api/calendars/events/${eventId}`, { method: 'DELETE' })
const eventIndex = baseEvents.value.findIndex(e => e.id === eventId)
await $fetch(`/api/calendars/events/${eventSkeleton.value.id}`, { method: 'DELETE' })
const eventIndex = baseEvents.value.findIndex(e => e.id === eventSkeleton.value.id)
baseEvents.value.splice(eventIndex, 1)
} catch (err) {
console.log(err)
}
}
return { allEvents, setEvents, currentEvents, getRelativeEventFromDate, getRelativeEventFromEvent, eventSkeleton, resetSkeleton, submitSkeleton, lastActiveEvent, updateEventFromSkeleton, deleteEvent }
return {
allEvents,
setEvents,
currentEvents,
getRelativeEventFromDate,
getRelativeEventFromEvent,
eventSkeleton,
resetSkeleton,
submitSkeleton,
lastActiveEvent,
updateEventFromSkeleton,
deleteEventFromSkeleton,
isEditEventModalOpen,
revealEditEventModal,
isDeleteEventModalOpen,
revealDeleteEventModal
}
})