Merge pull request #24 from AlexisNP/features/add-categories-management-to-event-cr

Add categories to event
This commit is contained in:
AlexisNP
2024-06-11 22:36:13 +02:00
committed by GitHub
20 changed files with 498 additions and 199 deletions

View File

@@ -77,6 +77,134 @@
}
}
/* Colors */
.event-red {
@apply text-slate-900 bg-red-500 hover:bg-red-600
}
.event-orange {
@apply text-slate-900 bg-orange-500 hover:bg-orange-600
}
.event-amber {
@apply text-slate-900 bg-amber-500 hover:bg-amber-600
}
.event-yellow {
@apply text-slate-900 bg-yellow-600 hover:bg-yellow-700
}
.event-lime {
@apply text-slate-900 bg-lime-500 hover:bg-lime-600
}
.event-green {
@apply text-white bg-green-600 hover:bg-green-700
}
.event-emerald {
@apply text-white bg-emerald-600 hover:bg-emerald-700
}
.event-teal {
@apply text-white bg-teal-600 hover:bg-teal-700
}
.event-cyan {
@apply text-white bg-cyan-600 hover:bg-cyan-700
}
.event-sky {
@apply text-white bg-sky-600 hover:bg-sky-700
}
.event-blue {
@apply text-white bg-blue-600 hover:bg-blue-700
}
.event-indigo {
@apply text-white bg-indigo-600 hover:bg-indigo-700
}
.event-violet {
@apply text-white bg-violet-600 hover:bg-violet-700
}
.event-purple {
@apply text-white bg-purple-600 hover:bg-purple-700
}
.event-fuchsia {
@apply text-white bg-fuchsia-600 hover:bg-fuchsia-700
}
.event-pink {
@apply text-white bg-pink-700 hover:bg-pink-800
}
.event-rose {
@apply text-white bg-rose-700 hover:bg-rose-800
}
.event-black {
@apply text-white bg-stone-500 hover:bg-stone-700
}
.event-white {
@apply text-slate-900 bg-white hover:bg-yellow-200
}
.event-details-red {
--base-color: var(--color-red-800);
}
.event-details-orange {
--base-color: var(--color-orange-800);
}
.event-details-amber {
--base-color: var(--color-amber-800);
}
.event-details-yellow {
--base-color: var(--color-yellow-800);
}
.event-details-lime {
--base-color: var(--color-lime-800);
}
.event-details-green {
--base-color: var(--color-green-800);
}
.event-details-emerald {
--base-color: var(--color-emerald-800);
}
.event-details-teal {
--base-color: var(--color-teal-800);
}
.event-details-cyan {
--base-color: var(--color-cyan-800);
}
.event-details-sky {
--base-color: var(--color-sky-800);
}
.event-details-blue {
--base-color: var(--color-blue-800);
}
.event-details-indigo {
--base-color: var(--color-indigo-800);
}
.event-details-violet {
--base-color: var(--color-violet-800);
}
.event-details-purple {
--base-color: var(--color-purple-800);
}
.event-details-fuchsia {
--base-color: var(--color-fuchsia-800);
}
.event-details-pink {
--base-color: var(--color-pink-800);
}
.event-details-rose {
--base-color: var(--color-rose-800);
}
.event-details-black {
--base-color: var(--color-stone-800);
}
.event-details-white {
--base-color: var(--color-white);
}
.event-details {
--bg-color: color-mix(in srgb, var(--base-color), var(--color-slate-950) 80%);
--border-color: color-mix(in srgb, var(--base-color), var(--color-slate-800) 50%);
background-color: var(--bg-color);
border-color: var(--border-color);
hr {
border-color: var(--border-color);
}
}
.fade-enter-active,
.fade-leave-active {
transition: all .5s ease;

View File

@@ -12,11 +12,12 @@ const route = useRoute()
const worldId = route.params.id
const { setCalendarId, setMonths, setDefaultDate, currentConfig, selectedDate, jumpToDate } = useCalendar()
const { setEvents } = useCalendarEvents()
const { setEvents, setCategories } = useCalendarEvents()
const { setCharacters } = useCharacters()
const { data: calendar, pending: calPending, refresh: calRefresh } = await useLazyFetch(`/api/calendars/query?world_id=${worldId}`)
const { data: characters, pending: charPending, refresh: charRefresh } = await useLazyFetch(`/api/characters/query?world_id=${worldId}`)
const { data: categories, pending: categoryPending, refresh: categoryRefresh } = await useLazyFetch(`/api/calendars/categories/query`)
if (!calendar.value) {
await calRefresh()
@@ -37,6 +38,15 @@ if (!calendar.value) {
setEvents(calendar.value?.data?.events)
}
}
if (!categories.value) {
await categoryRefresh()
} else {
if (categories.value?.data) {
setCategories(categories.value?.data)
}
}
if (!characters.value) {
await charRefresh()
} else {
@@ -61,6 +71,13 @@ watch(calPending, (newStatus) => {
}
}
})
watch(categoryPending, (newStatus) => {
if (!newStatus) {
if (categories.value?.data) {
setCategories(categories.value?.data)
}
}
})
watch(charPending, (newStatus) => {
if (!newStatus) {
if (characters.value?.data) {
@@ -103,7 +120,7 @@ onMounted(() => {
<template>
<div class="h-full">
<template v-if="calPending || charPending">
<template v-if="calPending || charPending || categoryPending">
<div class="h-full grid place-items-center">
Loading notamment
</div>

View File

@@ -1,5 +1,4 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { cn } from '@/lib/utils'
import type { RPGDate } from '@/models/Date'
import type { CalendarEvent } from '~/models/CalendarEvent'
@@ -13,9 +12,9 @@ const { areDatesIdentical } = useCalendar()
const { revealEditEventModal, revealDeleteEventModal } = useCalendarEvents()
const { lastActiveEvent } = storeToRefs(useCalendarEvents())
const spansMultipleDays = Boolean(props.event.startDate && props.event.endDate)
const isStartEvent = spansMultipleDays && areDatesIdentical(props.tileDate, props.event.startDate)
const isEndEvent = spansMultipleDays && props.event.endDate && areDatesIdentical(props.tileDate, props.event.endDate)
const spansMultipleDays = computed(() => Boolean(props.event.startDate && props.event.endDate))
const isStartEvent = computed(() => spansMultipleDays.value && areDatesIdentical(props.tileDate, props.event.startDate))
const isEndEvent = computed(() => spansMultipleDays.value && props.event.endDate && areDatesIdentical(props.tileDate, props.event.endDate))
const titleCharLimit = 50;
const eventTitle = computed<string>(() => props.event.title.length <= titleCharLimit ? props.event.title : `${props.event.title.slice(0, titleCharLimit)}`)
@@ -59,30 +58,14 @@ onMounted(() => {
<button
class="text-xs px-2 py-1 block w-full text-left rounded-sm focus-visible:bg-red-200"
:class="
cn({
'italic': event.hidden,
'text-white bg-slate-600 hover:bg-slate-700': !event.category,
'text-white bg-lime-600 hover:bg-lime-700': event.category?.name === 'naissance',
'text-white bg-stone-500 hover:bg-stone-700': event.category?.name === 'mort',
'text-white bg-orange-600 hover:bg-orange-700': event.category?.name === 'catastrophe',
'text-white bg-pink-600 hover:bg-pink-700': event.category?.name === 'catastrophe naturelle',
'text-white bg-sky-600 hover:bg-sky-700': event.category?.name === 'législation',
'text-white bg-purple-600 hover:bg-purple-700': event.category?.name === 'religion',
'text-white bg-emerald-600 hover:bg-emerald-700': event.category?.name === 'joueurs',
'text-slate-900 bg-amber-300 hover:bg-amber-400': event.category?.name === 'inauguration',
'text-slate-900 bg-emerald-200 hover:bg-emerald-300': event.category?.name === 'invention',
'text-slate-900 bg-cyan-300 hover:bg-cyan-400': event.category?.name === 'science',
'text-slate-900 bg-white hover:bg-yellow-200': event.category?.name === 'bénédiction',
'text-slate-900 bg-purple-200 hover:bg-purple-300': event.category?.name === 'découverte',
'text-slate-900 bg-indigo-200 hover:bg-indigo-300': event.category?.name === 'exploration',
'text-white bg-amber-600 hover:bg-amber-700': event.category?.name === 'construction',
'text-slate-900 bg-violet-200 hover:bg-violet-300': event.category?.name === 'arcanologie',
'text-white bg-rose-600 hover:bg-rose-700': event.category?.name === 'criminalité',
'text-white bg-stone-600 hover:bg-stone-700': event.category?.name === 'scandale',
'text-slate-900 bg-yellow-500 hover:bg-yellow-600': event.category?.name === 'commerce',
'rounded-r-none': isStartEvent,
'rounded-l-none': isEndEvent
})
cn(
event.category ? `event-${event.category.color}` : '',
{
'italic': event.hidden,
'rounded-r-none': isStartEvent,
'rounded-l-none': isEndEvent,
}
)
"
@dblclick="handleDoubleClick"
@keydown.delete="handleDelete"

View File

@@ -1,4 +1,5 @@
<script lang="ts" setup>
import { cn } from '@/lib/utils'
import type { RPGDate } from '@/models/Date'
import type { CalendarEvent } from '@/models/CalendarEvent'
import { useCalendar } from '@/stores/CalendarStore'
@@ -62,33 +63,18 @@ function deployDeleteModal() {
<template>
<UiPopoverContent
class="event-details w-96 bg-slate-900"
class=" w-96 bg-slate-900 border-slate-800"
:align="'center'"
:align-offset="50"
:side="'left'"
:collision-padding="60"
:hide-when-detached="true"
:class="{
'border-slate-800': !event.category,
'border-lime-800': event.category?.name === 'naissance',
'border-stone-600': event.category?.name === 'mort',
'border-orange-800': event.category?.name === 'catastrophe',
'border-pink-800': event.category?.name === 'catastrophe naturelle',
'border-sky-800': event.category?.name === 'législation',
'border-purple-800': event.category?.name === 'religion',
'border-emerald-800': event.category?.name === 'joueurs',
'border-amber-800': event.category?.name === 'inauguration',
'border-green-800': event.category?.name === 'invention',
'border-cyan-800': event.category?.name === 'science',
'border-slate-600': event.category?.name === 'bénédiction',
'border-purple-700': event.category?.name === 'découverte',
'border-indigo-700': event.category?.name === 'exploration',
'border-amber-700': event.category?.name === 'construction',
'border-violet-700': event.category?.name === 'arcanologie',
'border-rose-800': event.category?.name === 'criminalité',
'border-stone-700': event.category?.name === 'scandale',
'border-yellow-600': event.category?.name === 'commerce'
}"
:class="cn(
event.category ? `event-details-${event.category.color}` : '',
{
'event-details': event.category
}
)"
>
<div ref="eventDetails" class="grid gap-1">
<header class="pr-12">
@@ -128,13 +114,13 @@ function deployDeleteModal() {
<template v-if="event.category || event.secondaryCategories">
<ul class="flex gap-1">
<li v-if="event.category">
<UiBadge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
<UiBadge class="mix-blend-luminosity font-bold bg-gray-600 lowercase" variant="secondary">
{{ event.category?.name }}
</UiBadge>
</li>
<li v-for="cat in event.secondaryCategories" :key="cat.id">
<UiBadge class="mix-blend-luminosity bg-gray-600" variant="secondary">
<UiBadge class="mix-blend-luminosity bg-gray-600 lowercase" variant="secondary">
{{ cat.name }}
</UiBadge>
</li>
@@ -205,72 +191,3 @@ function deployDeleteModal() {
</UiTooltipProvider>
</UiPopoverContent>
</template>
<style lang="scss">
.border-slate-800 {
--base-color: var(--color-slate-800);
}
.border-lime-800 {
--base-color: var(--color-lime-800);
}
.border-stone-600 {
--base-color: var(--color-stone-600);
}
.border-orange-800 {
--base-color: var(--color-orange-800);
}
.border-pink-800 {
--base-color: var(--color-pink-800);
}
.border-sky-800 {
--base-color: var(--color-sky-800);
}
.border-purple-800 {
--base-color: var(--color-purple-800);
}
.border-emerald-800 {
--base-color: var(--color-emerald-800);
}
.border-amber-800 {
--base-color: var(--color-amber-800);
}
.border-green-800 {
--base-color: var(--color-green-800);
}
.border-cyan-800 {
--base-color: var(--color-cyan-800);
}
.border-slate-600 {
--base-color: var(--color-slate-600);
}
.border-purple-700 {
--base-color: var(--color-purple-700);
}
.border-indigo-700 {
--base-color: var(--color-indigo-700);
}
.border-amber-700 {
--base-color: var(--color-amber-700);
}
.border-violet-700 {
--base-color: var(--color-violet-700);
}
.border-rose-800 {
--base-color: var(--color-rose-800);
}
.border-stone-700 {
--base-color: var(--color-stone-700);
}
.border-yellow-600 {
--base-color: var(--color-yellow-600);
}
.event-details {
--bg-color: color-mix(in srgb, var(--base-color), var(--color-slate-950) 85%);
background-color: var(--bg-color);
hr {
border-color: var(--base-color);
}
}
</style>

View File

@@ -1,7 +1,6 @@
<script lang="ts" setup>
import type { RPGDate } from '~/models/Date';
import { PopoverAnchor } from 'radix-vue';
import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea } from '@phosphor-icons/vue'
import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea, PhTag } from '@phosphor-icons/vue'
const { eventSkeleton, operationInProgress } = storeToRefs(useCalendarEvents())
const { resetSkeleton, submitSkeleton, cancelLatestRequest } = useCalendarEvents()
@@ -80,9 +79,9 @@ function handleCancel() {
<template>
<UiPopover v-model:open="popoverOpen">
<PopoverAnchor as-child>
<button :class="btnClass" @dblclick="openEventCreatePopover()" />
</PopoverAnchor>
<UiPopoverTrigger as-child>
<button :class="btnClass" @click="openEventCreatePopover()" />
</UiPopoverTrigger>
<UiPopoverContent
:align="'center'"
:side="'right'"
@@ -96,7 +95,7 @@ function handleCancel() {
@pointer-down-outside="handleClosing"
>
<form @submit.prevent="handleSubmit">
<div class="grid grid-cols-2 gap-y-6">
<div class="grid grid-cols-2 gap-y-3">
<div class="col-span-2 pl-8">
<input
id="new-event-title"
@@ -108,7 +107,7 @@ function handleCancel() {
class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600">
</div>
<div class="col-span-2 pl-8">
<div class="col-span-2 my-2 pl-8">
<textarea
id="new-event-description"
v-model="eventSkeleton.description"
@@ -140,6 +139,22 @@ function handleCancel() {
</div>
<div class="col-span-2">
<div class="flex items-center gap-4">
<PhTag size="18" weight="fill" />
<CalendarInputEventCategory v-model="eventSkeleton.category" placeholder="Ajouter une catégorie principale" />
</div>
</div>
<!-- <div class="col-span-2">
<div class="flex items-center gap-4">
<PhTag size="18" weight="fill" />
<CalendarInputEventCategories v-model="eventSkeleton.secondaryCategories" placeholder="Ajouter des catégories secondaires" />
</div>
</div> -->
<div class="col-span-2 mb-2">
<div class="flex items-center gap-4">
<PhMapPinArea size="18" weight="fill" />
@@ -149,7 +164,7 @@ function handleCancel() {
type="text"
name="new-event-location"
placeholder="Ajouter un endroit"
class="w-full -my-1 py-1 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600">
class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600">
</div>
</div>

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea, PhPencilSimpleLine } from '@phosphor-icons/vue'
import { PhAlarm, PhCircleNotch, PhEye, PhEyeClosed, PhMapPinArea, PhPencilSimpleLine, PhTag } from '@phosphor-icons/vue'
import { VisuallyHidden } from 'radix-vue'
const { isEditEventModalOpen } = storeToRefs(useCalendarEvents())
@@ -81,7 +81,7 @@ function handleCancel() {
</VisuallyHidden>
<form @submit.prevent="handleAction">
<div class="grid grid-cols-2 gap-y-6">
<div class="grid grid-cols-2 gap-y-3">
<div class="col-span-2">
<div class="flex items-center gap-4">
<PhPencilSimpleLine size="20" weight="fill" />
@@ -98,7 +98,7 @@ function handleCancel() {
</div>
</div>
<div class="col-span-2 ml-8">
<div class="col-span-2 my-2 ml-8">
<textarea
id="new-event-description"
v-model="eventSkeleton.description"
@@ -130,6 +130,26 @@ function handleCancel() {
</div>
<div class="col-span-2">
<div class="flex items-center gap-4">
<PhTag size="18" weight="fill" />
<div class="w-1/2">
<CalendarInputEventCategory v-model="eventSkeleton.category" placeholder="Ajouter une catégorie principale" />
</div>
</div>
</div>
<!-- <div class="col-span-2">
<div class="flex items-center gap-4">
<PhTag size="18" weight="fill" />
<div class="w-1/2">
<CalendarInputEventCategories v-model="eventSkeleton.secondaryCategories" placeholder="Ajouter des catégories secondaires" />
</div>
</div>
</div> -->
<div class="col-span-2 mb-2">
<div class="flex items-center gap-4">
<PhMapPinArea size="18" weight="fill" />
@@ -139,7 +159,7 @@ function handleCancel() {
type="text"
name="new-event-location"
placeholder="Ajouter un endroit"
class="w-full -my-1 py-1 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600">
class="w-full -my-1 py-2 px-2 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600">
</div>
</div>

View File

@@ -0,0 +1,88 @@
<script lang="ts" setup>
import { cn } from '@/lib/utils'
import type { Category } from '~/models/Category';
import { PhCaretDown, PhCheck } from '@phosphor-icons/vue';
const isPopoverOpen = ref<boolean>(false)
const props = defineProps<{
placeholder?: string
}>()
const model = defineModel<Category[]>({ default: [] })
const modelBuffer = ref<Category[]>([])
watch(modelBuffer.value, () => {
model.value = [ ...modelBuffer.value ]
})
const { categories: availableCategories } = useCalendarEvents()
const searchTerm = ref<string>('')
const filteredCategories = computed(() =>
searchTerm.value === ''
? availableCategories
: availableCategories.filter((category) => {
return category.name.toLowerCase().includes(searchTerm.value.toLowerCase())
})
)
</script>
<template>
<UiPopover v-model:open="isPopoverOpen">
<UiPopoverTrigger as-child>
<UiButton
variant="outline"
role="combobox"
class="relative w-full max-w-full h-fit justify-between"
>
<template v-if="!model.length">
{{ props.placeholder }}
</template>
<template v-else>
<ul class="flex flex-wrap gap-1">
<li v-for="category in model" :key="`selected-cat-${category.id}`">
<UiBadge class="lowercase" variant="secondary">
{{ category.name }}
</UiBadge>
</li>
</ul>
</template>
<PhCaretDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</UiButton>
</UiPopoverTrigger>
<UiPopoverContent
align="start"
side="bottom"
:collision-padding="50"
class="w-fit h-[33vh] p-0"
>
<UiCommand v-model="modelBuffer" v-model:searchTerm="searchTerm" :multiple="true">
<UiCommandInput placeholder="Rechercher les catégories" />
<UiCommandEmpty>Aucune catégorie trouvée.</UiCommandEmpty>
<UiCommandList>
<UiCommandGroup>
<UiCommandItem
v-for="category in filteredCategories"
:key="category.id"
:value="category"
class="cursor-pointer flex justify-between items-center"
:class="cn({
})"
>
<span>
{{ category.name }}
</span>
<PhCheck v-if="model.find(cat => cat.id === category.id)" />
</UiCommandItem>
</UiCommandGroup>
</UiCommandList>
</UiCommand>
</UiPopoverContent>
</UiPopover>
</template>

View File

@@ -0,0 +1,73 @@
<script lang="ts" setup>
import { PhCaretDown } from '@phosphor-icons/vue';
import type { Category } from '~/models/Category';
const isPopoverOpen = ref<boolean>(false)
const props = defineProps<{
placeholder?: string
}>()
const model = defineModel<Category>()
const { categories: availableCategories } = useCalendarEvents()
const searchTerm = ref<string>('')
function handleCatSelect() {
isPopoverOpen.value = false
}
const filteredCategories = computed(() =>
searchTerm.value === ''
? availableCategories
: availableCategories.filter((category) => {
return category.name.toLowerCase().includes(searchTerm.value.toLowerCase())
})
)
</script>
<template>
<UiPopover v-model:open="isPopoverOpen">
<UiPopoverTrigger as-child>
<UiButton
variant="outline"
role="combobox"
class="w-full max-w-full justify-between"
>
<template v-if="!model">
{{ props.placeholder }}
</template>
<template v-else>
{{ model.name }}
</template>
<PhCaretDown class="ml-2 h-4 w-4 shrink-0 opacity-50" />
</UiButton>
</UiPopoverTrigger>
<UiPopoverContent
align="start"
side="bottom"
:collision-padding="50"
class="w-fit h-[33vh] p-0"
>
<UiCommand v-model="model" v-model:searchTerm="searchTerm">
<UiCommandInput placeholder="Rechercher les catégories" />
<UiCommandEmpty>Aucune catégorie trouvée.</UiCommandEmpty>
<UiCommandList>
<UiCommandGroup>
<UiCommandItem
v-for="category in filteredCategories"
:key="category.id"
:value="category"
class="cursor-pointer"
@select="handleCatSelect"
>
{{ category.name }}
</UiCommandItem>
</UiCommandGroup>
</UiCommandList>
</UiCommand>
</UiPopoverContent>
</UiPopover>
</template>

View File

@@ -1,4 +1,5 @@
<script lang="ts" setup>
import { cn } from '@/lib/utils'
import type { RPGDate } from '@/models/Date'
import type { CalendarEvent } from '@/models/CalendarEvent'
import { useCalendar } from '@/stores/CalendarStore'
@@ -26,28 +27,11 @@ const dateDuration: string | null = props.event.endDate
<template>
<button
class="relative block w-full text-left py-3 px-4 rounded-sm transition-colors"
:class="{
'pt-4': event.hidden,
'text-white bg-slate-600 hover:bg-slate-700': !event.category,
'text-white bg-lime-600 hover:bg-lime-700': event.category?.name === 'naissance',
'text-white bg-stone-500 hover:bg-stone-700': event.category?.name === 'mort',
'text-white bg-orange-600 hover:bg-orange-700': event.category?.name === 'catastrophe',
'text-white bg-pink-600 hover:bg-pink-700': event.category?.name === 'catastrophe naturelle',
'text-white bg-sky-600 hover:bg-sky-700': event.category?.name === 'législation',
'text-white bg-purple-600 hover:bg-purple-700': event.category?.name === 'religion',
'text-white bg-emerald-600 hover:bg-emerald-700': event.category?.name === 'joueurs',
'text-slate-900 bg-amber-300 hover:bg-amber-400': event.category?.name === 'inauguration',
'text-slate-900 bg-emerald-200 hover:bg-emerald-300': event.category?.name === 'invention',
'text-slate-900 bg-cyan-300 hover:bg-cyan-400': event.category?.name === 'science',
'text-slate-900 bg-white hover:bg-yellow-200': event.category?.name === 'bénédiction',
'text-slate-900 bg-purple-200 hover:bg-purple-300': event.category?.name === 'découverte',
'text-slate-900 bg-indigo-200 hover:bg-indigo-300': event.category?.name === 'exploration',
'text-white bg-amber-600 hover:bg-amber-700': event.category?.name === 'construction',
'text-slate-900 bg-violet-200 hover:bg-violet-300': event.category?.name === 'arcanologie',
'text-white bg-rose-600 hover:bg-rose-700': event.category?.name === 'criminalité',
'text-white bg-stone-600 hover:bg-stone-700': event.category?.name === 'scandale',
'text-slate-900 bg-yellow-500 hover:bg-yellow-600': event.category?.name === 'commerce'
}"
:class="cn(
event.category ? `event-${event.category.color}` : '',
{
'pt-4': event.hidden,
})"
@click="$emit('query:date-jump', event.startDate)"
>
<div class="flex gap-2 items-center mb-1">
@@ -97,13 +81,13 @@ const dateDuration: string | null = props.event.endDate
<div v-if="event.category || event.secondaryCategories" class="absolute top-3 right-4">
<ul class="flex gap-1">
<li v-if="event.category">
<UiBadge class="mix-blend-luminosity font-bold bg-gray-600" variant="secondary">
<UiBadge class="mix-blend-luminosity font-bold bg-gray-600 lowercase" variant="secondary">
{{ event.category?.name }}
</UiBadge>
</li>
<li v-for="cat in event.secondaryCategories" :key="cat.id">
<UiBadge class="mix-blend-luminosity bg-gray-600" variant="secondary">
<UiBadge class="mix-blend-luminosity bg-gray-600 lowercase" variant="secondary">
{{ cat.name }}
</UiBadge>
</li>

View File

@@ -0,0 +1,33 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import type { CheckboxRootEmits, CheckboxRootProps } from 'radix-vue'
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from 'radix-vue'
import { Check } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<CheckboxRootEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
<CheckboxRoot
v-bind="forwarded"
:class="
cn('peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
props.class)"
>
<CheckboxIndicator class="flex h-full w-full items-center justify-center text-current">
<slot>
<Check class="h-4 w-4" />
</slot>
</CheckboxIndicator>
</CheckboxRoot>
</template>

View File

@@ -0,0 +1 @@
export { default as Checkbox } from './Checkbox.vue'

View File

@@ -1,5 +1,5 @@
import { z } from 'zod'
import type { Category } from './Category'
import { categorySchema, type Category } from './Category'
import { dateSchema, type RPGDate } from './Date'
export interface CalendarEvent {
@@ -25,7 +25,8 @@ export const postEventBodySchema = z.object({
location: z.string().optional().nullable(),
startDate: dateSchema.required(),
endDate: dateSchema.optional().nullable(),
hidden: z.boolean().optional().nullable()
hidden: z.boolean().optional().nullable(),
category: categorySchema.optional().nullable(),
}),
calendarId: z.number({ coerce: true }).int().positive()
})

View File

@@ -1,4 +1,14 @@
import { z } from 'zod'
import type { RPGColor } from './Color'
export interface Category {
id: number
name: string
color?: RPGColor
}
export const categorySchema = z.object({
id: z.number().int(),
name: z.string(),
color: z.string().default("black")
})

1
models/Color.ts Normal file
View File

@@ -0,0 +1 @@
export type RPGColor = "red" | "orange" | "amber" | "yellow" | "lime" | "green" | "emerald" | "teal" | "cyan" | "sky" | "blue" | "indigo" | "violet" | "purple" | "fuchsia" | "pink" | "rose" | "black" | "white"

View File

@@ -0,0 +1,15 @@
import { serverSupabaseClient } from "#supabase/server";
import type { Category } from "~/models/Category";
export default defineEventHandler(async (event) => {
const client = await serverSupabaseClient(event)
const output = client
.from('calendar_event_categories')
.select(`
id,
name
`)
return output.returns<Category[]>()
})

View File

@@ -22,7 +22,6 @@ export default defineEventHandler(async (event) => {
}
if (bodyError) {
console.log(bodyData)
throw createError({
cause: 'Utilisateur',
fatal: false,
@@ -42,6 +41,7 @@ export default defineEventHandler(async (event) => {
description: bodyData.event.description,
location: bodyData.event.location,
hidden: bodyData.event.hidden,
category: bodyData.event.category?.id,
calendar_id: bodyData?.calendarId
} as never
)

View File

@@ -26,6 +26,7 @@ export default defineEventHandler(async (event) => {
description: bodyData.event.description,
location: bodyData.event.location,
hidden: bodyData.event.hidden,
category: bodyData.event.category?.id,
calendar_id: bodyData?.calendarId
} as never
)

View File

@@ -1,7 +1,8 @@
import type { RPGDate } from '@/models/Date'
import type { CalendarEvent } from '@/models/CalendarEvent'
import type { RPGDate } from '@/models/Date'
import { defineStore } from 'pinia'
import { ref, watch, type Ref } from 'vue'
import type { Category } from '~/models/Category'
import { useCalendar } from './CalendarStore'
export const useCalendarEvents = defineStore('calendar-events', () => {
@@ -14,6 +15,12 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
baseEvents.value = data
}
const categories = ref<Category[]>([])
function setCategories(data: Category[]) {
categories.value = data
}
// Order base events by dates
const allEvents = computed(() => baseEvents.value.sort((a, b) => {
return compareDates(a.startDate, b.startDate, 'desc')
@@ -269,6 +276,8 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
return {
allEvents,
setEvents,
categories,
setCategories,
currentEvents,
getRelativeEventFromDate,
getRelativeEventFromEvent,

View File

@@ -39,7 +39,7 @@ create table public.worlds (
id bigint generated by default as identity primary key,
name text not null,
description text,
color app_colors,
color app_colors default 'black',
gm_id uuid references public.users on delete cascade
);
comment on table public.worlds is 'Worlds belonging to a single user ; a game master.';
@@ -49,6 +49,7 @@ create table public.calendars (
id bigint generated by default as identity primary key,
name text not null,
today json not null,
color app_colors default 'black',
world_id bigint references public.worlds on delete cascade not null
);
comment on table public.calendars is 'Calendar settings and configuration attached to a single world.';
@@ -67,6 +68,7 @@ comment on table public.calendar_months is 'A calendar month.';
create table public.calendar_event_categories (
id bigint generated by default as identity primary key,
name text not null,
color app_colors default 'black',
unique (name)
);
comment on table public.calendar_event_categories is 'Categories describing events.';
@@ -98,6 +100,7 @@ comment on table public.calendar_event_categories_links is 'Link tables for mult
create table public.character_categories (
id bigint generated by default as identity primary key,
name text not null,
color app_colors default 'black',
unique (name)
);
comment on table public.character_categories is 'Categories describing characters';

View File

@@ -2,38 +2,38 @@ insert into public.role_permissions (role, permission) values ('sa', 'events.see
insert into public.role_permissions (role, permission) values ('sa', 'users.ban');
-- Event categories
insert into public.calendar_event_categories (name) values ('naissance');
insert into public.calendar_event_categories (name) values ('mort');
insert into public.calendar_event_categories (name) values ('catastrophe');
insert into public.calendar_event_categories (name) values ('catastrophe naturelle');
insert into public.calendar_event_categories (name) values ('inauguration');
insert into public.calendar_event_categories (name) values ('religion');
insert into public.calendar_event_categories (name) values ('invention');
insert into public.calendar_event_categories (name) values ('science');
insert into public.calendar_event_categories (name) values ('bénédiction');
insert into public.calendar_event_categories (name) values ('joueurs');
insert into public.calendar_event_categories (name) values ('découverte');
insert into public.calendar_event_categories (name) values ('exploration');
insert into public.calendar_event_categories (name) values ('construction');
insert into public.calendar_event_categories (name) values ('arcanologie');
insert into public.calendar_event_categories (name) values ('criminalité');
insert into public.calendar_event_categories (name) values ('scandale');
insert into public.calendar_event_categories (name) values ('commerce');
insert into public.calendar_event_categories (name) values ('législation');
insert into public.calendar_event_categories (name, color) values ('Naissance', 'white');
insert into public.calendar_event_categories (name, color) values ('Mort', 'black');
insert into public.calendar_event_categories (name, color) values ('Catastrophe', 'orange');
insert into public.calendar_event_categories (name, color) values ('Catastrophe naturelle', 'red');
insert into public.calendar_event_categories (name, color) values ('Inauguration', 'green');
insert into public.calendar_event_categories (name, color) values ('Religion', 'violet');
insert into public.calendar_event_categories (name, color) values ('Invention', 'teal');
insert into public.calendar_event_categories (name, color) values ('Science', 'indigo');
insert into public.calendar_event_categories (name, color) values ('Bénédiction', 'white');
insert into public.calendar_event_categories (name, color) values ('Joueurs', 'white');
insert into public.calendar_event_categories (name, color) values ('Découverte', 'purple');
insert into public.calendar_event_categories (name, color) values ('Exploration', 'lime');
insert into public.calendar_event_categories (name, color) values ('Construction', 'blue');
insert into public.calendar_event_categories (name, color) values ('Arcanologie', 'cyan');
insert into public.calendar_event_categories (name, color) values ('Criminalité', 'rose');
insert into public.calendar_event_categories (name, color) values ('Scandale', 'pink');
insert into public.calendar_event_categories (name, color) values ('Commerce', 'amber');
insert into public.calendar_event_categories (name, color) values ('Législation', 'blue');
-- Character categories
insert into public.character_categories (name) values ('joueur');
insert into public.character_categories (name) values ('comte');
insert into public.character_categories (name) values ('scientifique');
insert into public.character_categories (name) values ('mage');
insert into public.character_categories (name) values ('professeur');
insert into public.character_categories (name) values ('criminel');
insert into public.character_categories (name) values ('étincelle');
insert into public.character_categories (name) values ('buse blanche');
insert into public.character_categories (name) values ('ecclésiastique');
insert into public.character_categories (name) values ('militaire');
insert into public.character_categories (name) values ('activiste');
insert into public.character_categories (name) values ('commerçant');
insert into public.character_categories (name, color) values ('Joueur', 'white');
insert into public.character_categories (name, color) values ('Comte', 'emerald');
insert into public.character_categories (name, color) values ('Scientifique', 'indigo');
insert into public.character_categories (name, color) values ('Mage', 'cyan');
insert into public.character_categories (name, color) values ('Professeur', 'teal');
insert into public.character_categories (name, color) values ('Criminel', 'rose');
insert into public.character_categories (name, color) values ('Étincelle', 'lime');
insert into public.character_categories (name, color) values ('Buse blanche', 'yellow');
insert into public.character_categories (name, color) values ('Ecclésiastique', 'violet');
insert into public.character_categories (name, color) values ('Militaire', 'orange');
insert into public.character_categories (name, color) values ('Activiste', 'sky');
insert into public.character_categories (name, color) values ('Commerçant', 'amber');
-- Worlds
insert into public.worlds (name, description, color) values ('Léïm', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis aliquet congue aliquet. Curabitur eu iaculis diam. Nunc iaculis nibh orci, eu semper nunc congue congue. Praesent euismod tortor eget metus tristique lobortis vel in risus. In volutpat ligula orci, id pharetra lectus egestas at.', 'black');
@@ -239,7 +239,7 @@ insert into public.calendar_events (title, description, location, start_date, en
1
);
insert into public.calendar_events (title, description, location, start_date, end_date, category, hidden, wiki, calendar_id) values (
'1ère disparation',
'1ère disparition',
'Taleb Vaht décède dans une grotte à la suite d''une attaque d''ischiels enragées.',
'Cantane',
'{ "day": 28, "month": 7, "year": 3209 }',
@@ -250,7 +250,7 @@ insert into public.calendar_events (title, description, location, start_date, en
1
);
insert into public.calendar_events (title, description, location, start_date, end_date, category, hidden, wiki, calendar_id) values (
'2ème disparation',
'2ème disparition',
'Donovane le mineur kéturien disparait sans laisser de traces.',
'Cantane',
'{ "day": 32, "month": 7, "year": 3209 }',
@@ -261,7 +261,7 @@ insert into public.calendar_events (title, description, location, start_date, en
1
);
insert into public.calendar_events (title, description, location, start_date, end_date, category, hidden, wiki, calendar_id) values (
'3ème disparation',
'3ème disparition',
'Disparition de Sébastien, gredin sauride.',
'Cantane',
'{ "day": 10, "month": 8, "year": 3209 }',
@@ -272,7 +272,7 @@ insert into public.calendar_events (title, description, location, start_date, en
1
);
insert into public.calendar_events (title, description, location, start_date, end_date, category, hidden, wiki, calendar_id) values (
'4ème disparation',
'4ème disparition',
'Disparition de Thérence, patrouilleur sauride de la Vieille Garde.',
'Cantane',
'{ "day": 19, "month": 8, "year": 3209 }',
@@ -283,7 +283,7 @@ insert into public.calendar_events (title, description, location, start_date, en
1
);
insert into public.calendar_events (title, description, location, start_date, end_date, category, hidden, wiki, calendar_id) values (
'5ème disparation',
'5ème disparition',
'Disparition de Mathilda Boulais, vendeuse de pierres.',
'Cantane',
'{ "day": 22, "month": 8, "year": 3209 }',