Files
milsim-site-v4/ui/src/components/calendar/CreateCalendarEvent.vue
2025-11-27 13:08:33 -05:00

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>