Migration to nuxt 4
Used codemods CLI and reworked most alias'd path that stopped working
This commit is contained in:
87
app/components/calendar/input/EventCategories.vue
Normal file
87
app/components/calendar/input/EventCategories.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script lang="ts" setup>
|
||||
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 } = useCalendar()
|
||||
|
||||
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"
|
||||
:aria-expanded="isPopoverOpen"
|
||||
aria-controls="event-categories"
|
||||
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 size-4 shrink-0 opacity-50" />
|
||||
</UiButton>
|
||||
</UiPopoverTrigger>
|
||||
<UiPopoverContent
|
||||
id="event-categories"
|
||||
align="start"
|
||||
side="bottom"
|
||||
:collision-padding="50"
|
||||
class="w-fit h-[33vh] p-0"
|
||||
>
|
||||
<UiCommand v-model="modelBuffer" v-model:search-term="searchTerm" :multiple="true">
|
||||
<UiCommandInput :placeholder="$t('entity.category.search')" />
|
||||
<UiCommandEmpty>{{ $t('entity.category.notFoundAny') }}</UiCommandEmpty>
|
||||
<UiCommandList>
|
||||
<UiCommandGroup>
|
||||
<UiCommandItem
|
||||
v-for="category in filteredCategories"
|
||||
:key="category.id"
|
||||
:value="category"
|
||||
class="cursor-pointer flex justify-between items-center"
|
||||
>
|
||||
<span>
|
||||
{{ category.name }}
|
||||
</span>
|
||||
|
||||
<PhCheck v-if="model.find(cat => cat.id === category.id)" />
|
||||
</UiCommandItem>
|
||||
</UiCommandGroup>
|
||||
</UiCommandList>
|
||||
</UiCommand>
|
||||
</UiPopoverContent>
|
||||
</UiPopover>
|
||||
</template>
|
||||
87
app/components/calendar/input/EventCategory.vue
Normal file
87
app/components/calendar/input/EventCategory.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script lang="ts" setup>
|
||||
import { PhCaretDown } from "@phosphor-icons/vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Category } from "@@/models/Category";
|
||||
|
||||
const isPopoverOpen = ref<boolean>(false)
|
||||
|
||||
const props = defineProps<{
|
||||
placeholder?: string
|
||||
}>()
|
||||
|
||||
const model = defineModel<Category>()
|
||||
|
||||
const { categories: availableCategories } = useCalendar()
|
||||
|
||||
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"
|
||||
:aria-expanded="isPopoverOpen"
|
||||
aria-controls="event-category"
|
||||
class="w-full max-w-full justify-between"
|
||||
>
|
||||
<template v-if="!model">
|
||||
{{ props.placeholder }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<span
|
||||
class="bgc"
|
||||
:class="cn(`element-${model.color}`)"
|
||||
>
|
||||
{{ capitalize(model.name) }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<PhCaretDown class="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</UiButton>
|
||||
</UiPopoverTrigger>
|
||||
<UiPopoverContent
|
||||
id="event-category"
|
||||
align="start"
|
||||
side="bottom"
|
||||
:collision-padding="50"
|
||||
class="w-fit h-[33vh] p-0"
|
||||
>
|
||||
<UiCommand v-model="model" v-model:search-term="searchTerm">
|
||||
<UiCommandInput :placeholder="$t('entity.category.search')" />
|
||||
<UiCommandEmpty>{{ $t('entity.category.notFoundAny') }}</UiCommandEmpty>
|
||||
<UiCommandList>
|
||||
<UiCommandGroup>
|
||||
<UiCommandItem
|
||||
v-for="category in filteredCategories"
|
||||
:key="category.id"
|
||||
:value="category"
|
||||
class="cursor-pointer"
|
||||
@select="handleCatSelect"
|
||||
>
|
||||
<span
|
||||
class="bgc"
|
||||
:class="cn(`element-${category.color}`)"
|
||||
>
|
||||
{{ capitalize(category.name) }}
|
||||
</span>
|
||||
</UiCommandItem>
|
||||
</UiCommandGroup>
|
||||
</UiCommandList>
|
||||
</UiCommand>
|
||||
</UiPopoverContent>
|
||||
</UiPopover>
|
||||
</template>
|
||||
172
app/components/calendar/input/MonthList.vue
Normal file
172
app/components/calendar/input/MonthList.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<script lang="ts" setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { useSortable } from "@vueuse/integrations/useSortable";
|
||||
import type { CalendarMonth } from "@@/models/CalendarMonth";
|
||||
|
||||
import { PhList, PhPlus, PhTrash } from "@phosphor-icons/vue";
|
||||
|
||||
const model = defineModel<CalendarMonth[]>({ required: true })
|
||||
|
||||
/**
|
||||
* Input value for new month's name
|
||||
*/
|
||||
const monthName: Ref<string | undefined> = ref<string>()
|
||||
const monthNameRef = ref<HTMLInputElement>()
|
||||
const { focused: monthNameFocused } = useFocus(monthNameRef)
|
||||
const validMonthNameDatatypes = ["string"]
|
||||
const monthNameIsTaken = computed(() => model.value.find(m => m.name === monthName.value))
|
||||
const validMonthName = computed(() => validMonthNameDatatypes.includes(typeof monthName.value) && !monthNameIsTaken.value)
|
||||
|
||||
/**
|
||||
* Input value for new month's number of days
|
||||
*/
|
||||
const monthDays: Ref<number | undefined> = ref<number>()
|
||||
const monthDaysRef = ref<HTMLInputElement>()
|
||||
const validMonthDaysDatatypes = ["number"]
|
||||
const validMonthDays = computed(() => validMonthDaysDatatypes.includes(typeof monthDays.value) && monthDays.value && monthDays.value >= 12)
|
||||
|
||||
const validNewMonth = computed(() => validMonthDays.value && validMonthName.value)
|
||||
|
||||
/**
|
||||
* Add current month input data to the model list
|
||||
*/
|
||||
function addMonthToModel(): void {
|
||||
if (!monthDays.value || !monthName.value) return
|
||||
|
||||
// Create month object to add
|
||||
const monthToInsert: CalendarMonth = {
|
||||
name: monthName.value,
|
||||
days: monthDays.value,
|
||||
position: model.value.length
|
||||
}
|
||||
|
||||
model.value.push(monthToInsert)
|
||||
|
||||
// Reset form state
|
||||
monthName.value = ""
|
||||
monthNameFocused.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a specific month from the model
|
||||
*
|
||||
* @param index Index position of the month in the model
|
||||
*/
|
||||
function removeMonthFromModel(index: number) {
|
||||
if (isNaN(index)) return
|
||||
|
||||
model.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// Sortable setup
|
||||
const monthSortableList = ref<HTMLElement | null>(null)
|
||||
useSortable(monthSortableList, model.value, { animation: 150, handle: ".handle" })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid md:grid-cols-12 gap-4 items-center">
|
||||
<div class="md:col-start-2 md:col-span-5">
|
||||
<UiInput
|
||||
id="new-month-name"
|
||||
ref="monthNameRef"
|
||||
v-model="monthName"
|
||||
type="text"
|
||||
name="newMonthName"
|
||||
:placeholder="$t('entity.calendar.months.inputName')"
|
||||
:class="cn({ 'border-red-600': monthNameIsTaken })"
|
||||
/>
|
||||
</div>
|
||||
<div class="md:col-span-5">
|
||||
<UiInput
|
||||
id="new-month-days"
|
||||
ref="monthDaysRef"
|
||||
v-model="monthDays"
|
||||
type="number"
|
||||
name="newMonthName"
|
||||
:placeholder="$t('entity.calendar.months.daysNb')"
|
||||
min="0"
|
||||
step="1"
|
||||
class="invalid:border-red-600"
|
||||
/>
|
||||
</div>
|
||||
<div class="md:col-span-1">
|
||||
<UiButton size="icon" class="rounded-full size-8" :disabled="!validNewMonth" @click.prevent="addMonthToModel">
|
||||
<PhPlus size="17"/>
|
||||
</UiButton>
|
||||
</div>
|
||||
<div class="md:col-span-full">
|
||||
<div
|
||||
class="border-[1px] border-border p-4 rounded-sm max-h-80 overflow-y-auto"
|
||||
:class="model.length ? 'md:grid md:grid-cols-12 md:gap-4 md:items-center' : ''"
|
||||
>
|
||||
<div v-if="model.length" class="hidden md:block col-span-1">
|
||||
<ul class="grid gap-y-4 justify-center">
|
||||
<li v-for="(m, i) in model" :key="`num-${m.name}`">
|
||||
<UiButton size="icon" variant="secondary" class="size-8 rounded-full">
|
||||
<span class="font-bold text-sm">{{ i + 1 }}</span>
|
||||
</UiButton>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-11">
|
||||
<ul ref="monthSortableList" class="grid gap-y-2" :class="model.length ? 'visible' : 'absolute invisible'">
|
||||
<template v-if="model.length">
|
||||
<li v-for="(m, i) in model" :key="m.name" class="grid md:grid-cols-12 gap-4 md:items-center text-slate-900 bg-slate-200 rounded-md">
|
||||
<div class="md:col-span-1 text-right duration-200 ease-out transition transform origin-top-right">
|
||||
<UiButton type="button" variant="ghost" size="icon" class="handle rounded-full size-8">
|
||||
<PhList size="17" />
|
||||
</UiButton>
|
||||
</div>
|
||||
<div class="md:col-span-3">
|
||||
<div class="font-bold md:pl-2">
|
||||
{{ m.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<UiInput
|
||||
:id="`month-days-n${i}`"
|
||||
v-model="m.days"
|
||||
class="bg-transparent border-none"
|
||||
type="number"
|
||||
:name="`monthDays-n${i}`"
|
||||
:placeholder="$t('entity.calendar.months.daysNb')"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-1">
|
||||
{{ $t('entity.calendar.months.daysMaybePlural') }}
|
||||
</div>
|
||||
|
||||
<div class="md:col-start-12">
|
||||
<UiTooltipProvider>
|
||||
<UiTooltip>
|
||||
<UiTooltipTrigger as-child>
|
||||
<UiButton type="button" variant="ghost" size="icon" class="rounded-full size-8" @click="removeMonthFromModel(i)">
|
||||
<PhTrash size="17" />
|
||||
</UiButton>
|
||||
</UiTooltipTrigger>
|
||||
<UiTooltipContent>
|
||||
<p>
|
||||
{{ $t('entity.calendar.months.deleteOne', { month: m.name }) }}
|
||||
</p>
|
||||
</UiTooltipContent>
|
||||
</UiTooltip>
|
||||
</UiTooltipProvider>
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
<template v-if="!model.length">
|
||||
<p class="col-span-12 text-lg text-center italic opacity-50">
|
||||
{{ $t('entity.calendar.months.none') }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
160
app/components/calendar/input/RPGDate.vue
Normal file
160
app/components/calendar/input/RPGDate.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RPGDate } from "@@/models/Date";
|
||||
|
||||
import {
|
||||
PhXCircle
|
||||
} from "@phosphor-icons/vue"
|
||||
|
||||
const model = defineModel<RPGDate | null>()
|
||||
|
||||
const id = useId()
|
||||
|
||||
const props = defineProps<{
|
||||
initialDate?: RPGDate
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
}>()
|
||||
|
||||
const { getFormattedDateTitle, getMonthName, defaultDate } = useCalendar()
|
||||
const { sortedMonths } = storeToRefs(useCalendar())
|
||||
|
||||
const popoverOpened = ref<boolean>(false)
|
||||
|
||||
const inputPlaceholder = computed<string>(() => {
|
||||
if (model.value) {
|
||||
return getFormattedDateTitle(model.value, true)
|
||||
} else if (props.placeholder) {
|
||||
return props.placeholder
|
||||
}
|
||||
|
||||
return ""
|
||||
})
|
||||
|
||||
const initialDateValue = props.initialDate ? { ...props.initialDate } : null
|
||||
|
||||
const monthTitle = computed<string>(() => {
|
||||
if (model.value) {
|
||||
return getMonthName(model.value.month)
|
||||
}
|
||||
|
||||
return ""
|
||||
})
|
||||
|
||||
const hasValue = computed<boolean>(() => !!model.value)
|
||||
const monthData = computed(() => {
|
||||
if (model.value) {
|
||||
return sortedMonths.value[model.value.month]
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
function setModelDay(day: number) {
|
||||
if (model.value) {
|
||||
model.value.day = day
|
||||
}
|
||||
}
|
||||
|
||||
function handleMonthChange(e: string) {
|
||||
const monthId = Number(e)
|
||||
|
||||
if (model.value) {
|
||||
model.value.month = monthId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click if the date is null
|
||||
*/
|
||||
function handleNullClick() {
|
||||
// Early return
|
||||
if (model.value) return
|
||||
|
||||
if (!model.value) {
|
||||
if (props.initialDate) {
|
||||
model.value = initialDateValue
|
||||
} else {
|
||||
model.value = { ...defaultDate }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleValueReset() {
|
||||
// Early return
|
||||
if (!model.value) return
|
||||
|
||||
model.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiPopover v-model:open="popoverOpened">
|
||||
<UiPopoverTrigger>
|
||||
<div class="flex">
|
||||
<UiButton
|
||||
size='sm'
|
||||
variant="outline"
|
||||
:class="{
|
||||
'text-slate-500': !hasValue,
|
||||
'rounded-tr-none rounded-br-none': model
|
||||
}"
|
||||
@click.prevent="handleNullClick"
|
||||
>
|
||||
{{ inputPlaceholder }}
|
||||
</UiButton>
|
||||
<UiButton
|
||||
v-if="model && !required"
|
||||
size='sm'
|
||||
variant="outline"
|
||||
class="rounded-tl-none rounded-bl-none px-2"
|
||||
@click.prevent="handleValueReset"
|
||||
>
|
||||
<PhXCircle size="14" />
|
||||
</UiButton>
|
||||
</div>
|
||||
</UiPopoverTrigger>
|
||||
<UiPopoverContent
|
||||
v-if="model"
|
||||
align="start"
|
||||
side="bottom"
|
||||
class="border-indigo-200 dark:bg-slate-950 dark:border-indigo-950"
|
||||
>
|
||||
<div class="grid grid-cols-2 items-center gap-x-2 gap-y-3">
|
||||
<UiSelect @update:model-value="handleMonthChange">
|
||||
<UiSelectTrigger>
|
||||
<UiSelectValue :placeholder="monthTitle" />
|
||||
</UiSelectTrigger>
|
||||
<UiSelectContent>
|
||||
<UiSelectGroup class="max-h-[50vh]">
|
||||
<UiSelectItem v-for="(month, i) in sortedMonths" :key="`popover-select-item-${id}-${i}`" :value="i.toString()">
|
||||
{{ month.name }}
|
||||
</UiSelectItem>
|
||||
</UiSelectGroup>
|
||||
</UiSelectContent>
|
||||
</UiSelect>
|
||||
|
||||
<UiInput v-model="model.year" type="number" />
|
||||
|
||||
<hr class="col-span-2" >
|
||||
|
||||
<div class="col-span-2">
|
||||
<div v-if="monthData" class="grid grid-cols-7 gap-1">
|
||||
<button
|
||||
v-for="day in monthData.days"
|
||||
:key="`popover-day-grid-${id}-${day}`"
|
||||
class="aspect-square rounded-full text-[.8em] transition-colors"
|
||||
:class="{
|
||||
'hover:bg-indigo-200 dark:hover:bg-indigo-700': day !== model.day,
|
||||
'bg-indigo-500 hover:bg-indigo-700 text-white': day === model.day
|
||||
}"
|
||||
@click="setModelDay(day)"
|
||||
>
|
||||
{{ day }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</UiPopoverContent>
|
||||
</UiPopover>
|
||||
</template>
|
||||
71
app/components/calendar/input/TodaySelect.vue
Normal file
71
app/components/calendar/input/TodaySelect.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CalendarMonth } from "@@/models/CalendarMonth";
|
||||
import type { RPGDate } from "@@/models/Date";
|
||||
|
||||
import { PhCalendarBlank } from "@phosphor-icons/vue";
|
||||
|
||||
const model = defineModel<RPGDate>({ required: true })
|
||||
|
||||
const props = defineProps<{
|
||||
availableMonths: CalendarMonth[]
|
||||
}>()
|
||||
|
||||
/**
|
||||
*
|
||||
* @param e The name of today's month
|
||||
*/
|
||||
function setTodayMonth(e: string) {
|
||||
model.value.month = e
|
||||
}
|
||||
|
||||
// When the model changes, get the month index from the month name
|
||||
watch(model.value, (n, _o) => {
|
||||
// If the month value is already an index, return early
|
||||
if (!isNaN(+model.value.month)) return
|
||||
|
||||
const monthId = props.availableMonths.findIndex((m) => m.name === n.month)
|
||||
|
||||
if (monthId !== -1) {
|
||||
model.value.month = monthId + 1
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex gap-2 items-stretch">
|
||||
<UiInput id="new-month-current-day" v-model="model.day" type="number" name="newMonthCurrentDay" :placeholder="$t('entity.calendar.months.daysNameSingular')" min="1" step="1" class="invalid:border-red-600" />
|
||||
|
||||
<UiDropdownMenu>
|
||||
<UiDropdownMenuTrigger as-child :disabled="props.availableMonths.length < 1">
|
||||
<UiButton size="sm" variant="secondary">
|
||||
<PhCalendarBlank size="18" weight="fill" />
|
||||
|
||||
<template v-if="props.availableMonths.length < 1">
|
||||
{{ $t('entity.calendar.months.noneAvailable') }}
|
||||
</template>
|
||||
<template v-else-if="model.month && typeof model.month === 'number'">
|
||||
{{ props.availableMonths[model.month - 1].name }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('entity.calendar.months.chooseOne') }}
|
||||
</template>
|
||||
</UiButton>
|
||||
</UiDropdownMenuTrigger>
|
||||
<UiDropdownMenuContent :side="'bottom'" :collision-padding="30">
|
||||
<UiDropdownMenuLabel>
|
||||
{{ $t('entity.calendar.months.available') }}
|
||||
</UiDropdownMenuLabel>
|
||||
<UiDropdownMenuSeparator />
|
||||
<UiDropdownMenuItem
|
||||
v-for="m in props.availableMonths"
|
||||
:key="m.name"
|
||||
@click="setTodayMonth(m.name)"
|
||||
>
|
||||
{{ m.name }}
|
||||
</UiDropdownMenuItem>
|
||||
</UiDropdownMenuContent>
|
||||
</UiDropdownMenu>
|
||||
|
||||
<UiInput id="new-month-current-day" v-model="model.year" type="number" name="newMonthCurrentYear" :placeholder="$t('entity.calendar.years.nameSingular')" step="1" class="invalid:border-red-600" />
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user