20-calendar-system #37
@@ -1,5 +1,5 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { createEvent, getEventAttendance, getEventDetails, getShortEventsInRange, setAttendanceStatus } from "../services/calendarService";
|
import { createEvent, getEventAttendance, getEventDetails, getShortEventsInRange, setAttendanceStatus, setEventCancelled } from "../services/calendarService";
|
||||||
import { CalendarAttendance, CalendarEvent } from "@app/shared/types/calendar";
|
import { CalendarAttendance, CalendarEvent } from "@app/shared/types/calendar";
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
@@ -35,6 +35,28 @@ r.get('/upcoming', async (req, res) => {
|
|||||||
res.sendStatus(501);
|
res.sendStatus(501);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
r.post('/:id/cancel', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const eventID = Number(req.params.id);
|
||||||
|
setEventCancelled(eventID, true);
|
||||||
|
res.sendStatus(200);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting cancel status:', error);
|
||||||
|
res.status(500).send('Error setting cancel status');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
r.post('/:id/uncancel', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const eventID = Number(req.params.id);
|
||||||
|
setEventCancelled(eventID, false);
|
||||||
|
res.sendStatus(200);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting cancel status:', error);
|
||||||
|
res.status(500).send('Error setting cancel status');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
r.post('/:id/attendance', async (req: Request, res: Response) => {
|
r.post('/:id/attendance', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
let member = req.user.id;
|
let member = req.user.id;
|
||||||
@@ -78,4 +100,5 @@ r.post('/', async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
module.exports.calendarRouter = r;
|
module.exports.calendarRouter = r;
|
||||||
@@ -52,13 +52,15 @@ export async function updateEvent(eventObject: CalendarEvent) {
|
|||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cancelEvent(eventID: number) {
|
export async function setEventCancelled(eventID: number, cancelled: boolean) {
|
||||||
|
const input = cancelled ? 1 : 0;
|
||||||
|
console.log(cancelled, input);
|
||||||
const sql = `
|
const sql = `
|
||||||
UPDATE calendar_events
|
UPDATE calendar_events
|
||||||
SET cancelled = 1
|
SET cancelled = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`;
|
`;
|
||||||
await pool.query(sql, [eventID]);
|
await pool.query(sql, [input, eventID]);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,12 +86,20 @@ export async function editCalendarEvent(eventData: CalendarEvent) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cancelCalendarEvent(eventID: number) {
|
export async function setCancelCalendarEvent(eventID: number, cancel: boolean) {
|
||||||
|
let route = cancel ? "cancel" : "uncancel";
|
||||||
|
|
||||||
}
|
console.log(route);
|
||||||
|
let res = await fetch(`${addr}/calendar/${eventID}/${route}`, {
|
||||||
export async function adminCancelCalendarEvent(eventID: number) {
|
method: "POST",
|
||||||
|
credentials: "include"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to set attendance: ${res.status} ${res.statusText}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setCalendarEventAttendance(eventID: number, state: CalendarAttendance) {
|
export async function setCalendarEventAttendance(eventID: number, state: CalendarAttendance) {
|
||||||
|
|||||||
@@ -49,12 +49,23 @@ import { createCalendarEvent } from "@/api/calendar"
|
|||||||
const formSchema = toTypedSchema(calendarEventSchema)
|
const formSchema = toTypedSchema(calendarEventSchema)
|
||||||
|
|
||||||
// ---------- dialog state & defaults ----------
|
// ---------- dialog state & defaults ----------
|
||||||
|
const clickedDate = ref<string | null>(null);
|
||||||
const dialogOpen = ref(false)
|
const dialogOpen = ref(false)
|
||||||
function openDialog() { dialogOpen.value = true }
|
function openDialog(dateStr?: string) {
|
||||||
|
clickedDate.value=dateStr ?? null;
|
||||||
|
dialogOpen.value = true
|
||||||
|
initialValues.value = makeInitialValues()
|
||||||
|
}
|
||||||
defineExpose({ openDialog })
|
defineExpose({ openDialog })
|
||||||
|
|
||||||
function makeInitialValues() {
|
function makeInitialValues() {
|
||||||
const start = roundToNextHour()
|
let start: Date;
|
||||||
|
if (clickedDate.value) {
|
||||||
|
const local = new Date(clickedDate.value + "T00:00:00");
|
||||||
|
start = roundToNextHour(local);
|
||||||
|
} else {
|
||||||
|
start = roundToNextHour();
|
||||||
|
}
|
||||||
const end = new Date(start.getTime() + 60 * 60 * 1000)
|
const end = new Date(start.getTime() + 60 * 60 * 1000)
|
||||||
return {
|
return {
|
||||||
name: "",
|
name: "",
|
||||||
@@ -68,7 +79,7 @@ function makeInitialValues() {
|
|||||||
id: null as number | null,
|
id: null as number | null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const initialValues = ref(makeInitialValues())
|
const initialValues = ref(null)
|
||||||
|
|
||||||
const formKey = ref(0)
|
const formKey = ref(0)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { CalendarEvent, CalendarSignup } from '@shared/types/calendar'
|
import type { CalendarEvent, CalendarSignup } from '@shared/types/calendar'
|
||||||
import { CircleAlert, Clock, MapPin, User, X } from 'lucide-vue-next';
|
import { CircleAlert, Clock, EllipsisVertical, MapPin, User, X } from 'lucide-vue-next';
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
import ButtonGroup from '../ui/button-group/ButtonGroup.vue';
|
import ButtonGroup from '../ui/button-group/ButtonGroup.vue';
|
||||||
import Button from '../ui/button/Button.vue';
|
import Button from '../ui/button/Button.vue';
|
||||||
import { CalendarAttendance, getCalendarEvent, setCalendarEventAttendance } from '@/api/calendar';
|
import { CalendarAttendance, getCalendarEvent, setCalendarEventAttendance, setCancelCalendarEvent } from '@/api/calendar';
|
||||||
import { useUserStore } from '@/stores/user';
|
import { useUserStore } from '@/stores/user';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
import DropdownMenu from '../ui/dropdown-menu/DropdownMenu.vue';
|
||||||
|
import DropdownMenuTrigger from '../ui/dropdown-menu/DropdownMenuTrigger.vue';
|
||||||
|
import DropdownMenuContent from '../ui/dropdown-menu/DropdownMenuContent.vue';
|
||||||
|
import DropdownMenuItem from '../ui/dropdown-menu/DropdownMenuItem.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
// const eventID = computed(() => {
|
// const eventID = computed(() => {
|
||||||
@@ -37,6 +41,7 @@ watch(
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'close'): void
|
(e: 'close'): void
|
||||||
|
(e: 'reload'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// const activeEvent = computed(() => props.event)
|
// const activeEvent = computed(() => props.event)
|
||||||
@@ -75,6 +80,17 @@ async function setAttendance(state: CalendarAttendance) {
|
|||||||
//refresh event data
|
//refresh event data
|
||||||
activeEvent.value = await getCalendarEvent(activeEvent.value.id);
|
activeEvent.value = await getCalendarEvent(activeEvent.value.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canEditEvent = computed(() => {
|
||||||
|
if (user.user.id == activeEvent.value.creator_id)
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function setCancel(isCancelled: boolean) {
|
||||||
|
await setCancelCalendarEvent(activeEvent.value.id, isCancelled);
|
||||||
|
emit("reload");
|
||||||
|
activeEvent.value = await getCalendarEvent(activeEvent.value.id);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -84,16 +100,40 @@ async function setAttendance(state: CalendarAttendance) {
|
|||||||
<h2 class="text-lg font-semibold break-all">
|
<h2 class="text-lg font-semibold break-all">
|
||||||
{{ activeEvent?.name || 'Event' }}
|
{{ activeEvent?.name || 'Event' }}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<div class="flex gap-4">
|
||||||
class="inline-flex flex-none size-8 items-center justify-center rounded-md border hover:bg-muted/40 transition"
|
<DropdownMenu v-if="canEditEvent">
|
||||||
aria-label="Close" @click="emit('close')">
|
<DropdownMenuTrigger>
|
||||||
<X class="size-4" />
|
<button
|
||||||
</button>
|
class="inline-flex flex-none size-8 items-center justify-center cursor-pointer rounded-md hover:bg-muted/40 transition">
|
||||||
|
<EllipsisVertical class="size-6" />
|
||||||
|
</button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
Edit
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem v-if="activeEvent.cancelled"
|
||||||
|
@click="setCancel(false)">
|
||||||
|
Un-Cancel
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem v-else @click="setCancel(true)">
|
||||||
|
Cancel
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<button
|
||||||
|
class="inline-flex flex-none size-8 items-center justify-center rounded-md border hover:bg-muted/40 transition cursor-pointer"
|
||||||
|
aria-label="Close" @click="emit('close')">
|
||||||
|
<X class="size-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Body -->
|
<!-- Body -->
|
||||||
<div class="flex-1 flex flex-col items-center min-h-0 overflow-y-auto px-4 py-4 space-y-6 w-full">
|
<div class="flex-1 flex flex-col items-center min-h-0 overflow-y-auto px-4 py-4 space-y-6 w-full">
|
||||||
<section v-if="activeEvent.cancelled == true" class="w-full">
|
<section v-if="activeEvent.cancelled == true" class="w-full">
|
||||||
<div class="flex p-2 rounded-md w-full bg-destructive gap-3"><CircleAlert></CircleAlert> This event has been cancelled</div>
|
<div class="flex p-2 rounded-md w-full bg-destructive gap-3">
|
||||||
|
<CircleAlert></CircleAlert> This event has been cancelled
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section class="w-full">
|
<section class="w-full">
|
||||||
<ButtonGroup class="flex w-full">
|
<ButtonGroup class="flex w-full">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ function buildFullDate(month: number, year: number): Date {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { selectedMonth, selectedYear, years, goPrev, goNext, goToday, onDatesSet, goToSelectedDate } = useCalendarNavigation(api)
|
const { selectedMonth, selectedYear, years, goPrev, goNext, goToday, onDatesSet, goToSelectedDate } = useCalendarNavigation(api)
|
||||||
const { events } = useCalendarEvents(selectedMonth, selectedYear);
|
const { events, loadEvents} = useCalendarEvents(selectedMonth, selectedYear);
|
||||||
|
|
||||||
const panelOpen = ref(false)
|
const panelOpen = ref(false)
|
||||||
const activeEvent = ref<CalendarEvent | null>(null)
|
const activeEvent = ref<CalendarEvent | null>(null)
|
||||||
@@ -48,7 +48,7 @@ const dialogRef = ref<any>(null)
|
|||||||
|
|
||||||
// NEW: handle day/time slot clicks to start creating an event
|
// NEW: handle day/time slot clicks to start creating an event
|
||||||
function onDateClick(arg: { dateStr: string }) {
|
function onDateClick(arg: { dateStr: string }) {
|
||||||
dialogRef.value?.openDialog();
|
dialogRef.value?.openDialog(arg.dateStr);
|
||||||
// For now, just open the panel with a draft payload.
|
// For now, just open the panel with a draft payload.
|
||||||
// activeEvent.value = {
|
// activeEvent.value = {
|
||||||
// id: '__draft__',
|
// id: '__draft__',
|
||||||
@@ -215,7 +215,7 @@ onMounted(() => {
|
|||||||
<aside v-if="panelOpen"
|
<aside v-if="panelOpen"
|
||||||
class="3xl:w-lg 2xl:w-md border-l bg-card text-foreground flex flex-col overflow-auto scrollbar-themed"
|
class="3xl:w-lg 2xl:w-md border-l bg-card text-foreground flex flex-col overflow-auto scrollbar-themed"
|
||||||
:style="{ height: 'calc(100vh - 61px)', position: 'sticky', top: '64px' }">
|
:style="{ height: 'calc(100vh - 61px)', position: 'sticky', top: '64px' }">
|
||||||
<ViewCalendarEvent @close="() => { router.push('/calendar'); }">
|
<ViewCalendarEvent @close="() => { router.push('/calendar'); }" @reload="loadEvents()">
|
||||||
</ViewCalendarEvent>
|
</ViewCalendarEvent>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user