233 lines
8.4 KiB
Vue
233 lines
8.4 KiB
Vue
<script setup lang="ts">
|
|
import { toTypedSchema } from "@vee-validate/zod"
|
|
import { ref, defineExpose, watch } from "vue"
|
|
import * as z from "zod"
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog"
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from "@/components/ui/form"
|
|
import { Input } from "@/components/ui/input"
|
|
import Textarea from "../ui/textarea/Textarea.vue"
|
|
import { CalendarEvent } from "@shared/types/calendar"
|
|
|
|
|
|
|
|
function toLocalDateString(d: Date) {
|
|
// yyyy-MM-dd with local time zone
|
|
const y = d.getFullYear()
|
|
const m = String(d.getMonth() + 1).padStart(2, "0")
|
|
const day = String(d.getDate()).padStart(2, "0")
|
|
return `${y}-${m}-${day}`
|
|
}
|
|
function toLocalTimeString(d: Date) {
|
|
const hh = String(d.getHours()).padStart(2, "0")
|
|
const mm = String(d.getMinutes()).padStart(2, "0")
|
|
return `${hh}:${mm}`
|
|
}
|
|
function roundToNextHour(d = new Date()) {
|
|
const t = new Date(d)
|
|
t.setMinutes(0, 0, 0)
|
|
t.setHours(t.getHours() + 1)
|
|
return t
|
|
}
|
|
|
|
|
|
import { calendarEventSchema, parseLocalDateTime } from '@shared/schemas/calendarEventSchema'
|
|
import { createCalendarEvent } from "@/api/calendar"
|
|
const formSchema = toTypedSchema(calendarEventSchema)
|
|
|
|
// ---------- dialog state & defaults ----------
|
|
const dialogOpen = ref(false)
|
|
function openDialog() { dialogOpen.value = true }
|
|
defineExpose({ openDialog })
|
|
|
|
function makeInitialValues() {
|
|
const start = roundToNextHour()
|
|
const end = new Date(start.getTime() + 60 * 60 * 1000)
|
|
return {
|
|
name: "",
|
|
startDate: toLocalDateString(start),
|
|
startTime: toLocalTimeString(start),
|
|
endDate: toLocalDateString(end),
|
|
endTime: toLocalTimeString(end),
|
|
location: "",
|
|
color: "#3b82f6",
|
|
description: "",
|
|
id: null as number | null,
|
|
}
|
|
}
|
|
const initialValues = ref(makeInitialValues())
|
|
|
|
const formKey = ref(0)
|
|
|
|
watch(dialogOpen, (isOpen) => {
|
|
if (!isOpen) {
|
|
formKey.value++ // remounts the form -> picks up fresh initialValues
|
|
}
|
|
})
|
|
// ---------- submit ----------
|
|
function onSubmit(vals: z.infer<typeof calendarEventSchema>) {
|
|
const start = parseLocalDateTime(vals.startDate, vals.startTime)
|
|
const end = parseLocalDateTime(vals.endDate, vals.endTime)
|
|
|
|
const event: CalendarEvent = {
|
|
name: vals.name,
|
|
start,
|
|
end,
|
|
location: vals.location,
|
|
color: vals.color,
|
|
description: vals.description,
|
|
}
|
|
|
|
try {
|
|
console.log("Submitting CalendarEvent:", event)
|
|
createCalendarEvent(event);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
|
|
// close after success
|
|
dialogOpen.value = false
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Form :key="formKey" v-slot="{ handleSubmit, resetForm }" :validation-schema="formSchema"
|
|
:initial-values="initialValues" keep-values as="">
|
|
<Dialog v-model:open="dialogOpen">
|
|
<DialogContent class="sm:max-w-[520px]">
|
|
<DialogHeader>
|
|
<DialogTitle>Create Event</DialogTitle>
|
|
</DialogHeader>
|
|
|
|
<form id="dialogForm" class="grid grid-cols-1 gap-4"
|
|
@submit="handleSubmit($event, (vals) => { onSubmit(vals); resetForm({ values: initialValues }); })">
|
|
|
|
<div class="flex gap-3 items-start w-full">
|
|
<!-- Name -->
|
|
<div class="flex-1">
|
|
<FormField v-slot="{ componentField }" name="name">
|
|
<FormItem>
|
|
<FormLabel>Event Name</FormLabel>
|
|
<FormControl>
|
|
<Input type="text" v-bind="componentField" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
</div>
|
|
|
|
<!-- Color -->
|
|
<div class="w-[60px]">
|
|
<FormField v-slot="{ componentField }" name="color">
|
|
<FormItem>
|
|
<FormLabel>Color</FormLabel>
|
|
<FormControl>
|
|
<Input type="color" class="h-[38px] p-1 cursor-pointer"
|
|
v-bind="componentField" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Start: date + time -->
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<FormField v-slot="{ componentField }" name="startDate">
|
|
<FormItem>
|
|
<FormLabel>Start Date</FormLabel>
|
|
<FormControl>
|
|
<Input type="date" v-bind="componentField" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ componentField }" name="startTime">
|
|
<FormItem>
|
|
<FormLabel>Start Time</FormLabel>
|
|
<FormControl>
|
|
<Input type="text" v-bind="componentField" />
|
|
<!-- If you ever want native picker: type="time" -->
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
</div>
|
|
|
|
<!-- End: date + time -->
|
|
<div class="grid grid-cols-2 gap-3">
|
|
<FormField v-slot="{ componentField }" name="endDate">
|
|
<FormItem>
|
|
<FormLabel>End Date</FormLabel>
|
|
<FormControl>
|
|
<Input type="date" v-bind="componentField" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<FormField v-slot="{ componentField }" name="endTime">
|
|
<FormItem>
|
|
<FormLabel>End Time</FormLabel>
|
|
<FormControl>
|
|
<Input type="text" v-bind="componentField" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
</div>
|
|
|
|
<!-- Location -->
|
|
<FormField v-slot="{ componentField }" name="location">
|
|
<FormItem>
|
|
<FormLabel>Location</FormLabel>
|
|
<FormControl>
|
|
<Input type="text" v-bind="componentField" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
|
|
<!-- Description -->
|
|
<FormField v-slot="{ componentField }" name="description">
|
|
<FormItem>
|
|
<FormLabel>Description</FormLabel>
|
|
<FormControl>
|
|
<Textarea class="resize-none h-32" v-bind="componentField" />
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<!-- Hidden id -->
|
|
<FormField v-slot="{ componentField }" name="id">
|
|
<input type="hidden" v-bind="componentField" />
|
|
</FormField>
|
|
</form>
|
|
|
|
<DialogFooter>
|
|
<Button type="submit" form="dialogForm">Create</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</Form>
|
|
</template>
|