overhauled attendance visualization to be more useful for planning
Some checks failed
Continuous Deployment / Update Deployment (push) Failing after 1m22s
Some checks failed
Continuous Deployment / Update Deployment (push) Failing after 1m22s
This commit is contained in:
@@ -123,15 +123,9 @@ export async function setAttendanceStatus(memberID: number, eventID: number, sta
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getEventAttendance(eventID: number): Promise<CalendarSignup[]> {
|
export async function getEventAttendance(eventID: number): Promise<CalendarSignup[]> {
|
||||||
const sql = `
|
|
||||||
SELECT
|
|
||||||
s.member_id,
|
|
||||||
s.status,
|
|
||||||
m.name AS member_name
|
|
||||||
FROM calendar_events_signups s
|
|
||||||
LEFT JOIN members m ON s.member_id = m.id
|
|
||||||
WHERE s.event_id = ?
|
|
||||||
`;
|
|
||||||
|
|
||||||
return await pool.query(sql, [eventID]);
|
const sql = "CALL `sp_GetCalendarEventSignups`(?)"
|
||||||
|
const res = await pool.query(sql, [eventID]);
|
||||||
|
console.log(res[0]);
|
||||||
|
return res[0];
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,7 @@ export interface CalendarSignup {
|
|||||||
eventID: number;
|
eventID: number;
|
||||||
status: CalendarAttendance;
|
status: CalendarAttendance;
|
||||||
member_name?: string;
|
member_name?: string;
|
||||||
|
member_unit?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarEventShort {
|
export interface CalendarEventShort {
|
||||||
|
|||||||
@@ -108,6 +108,67 @@ const isPast = computed(() => {
|
|||||||
return new Date() < end;
|
return new Date() < end;
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const attendanceTab = ref<"Alpha" | "Echo" | "Other">("Alpha");
|
||||||
|
const attendanceList = computed<CalendarSignup[]>(() => {
|
||||||
|
let out: CalendarSignup[] = [];
|
||||||
|
if (attendanceTab.value === 'Alpha') {
|
||||||
|
out = activeEvent.value.eventSignups?.filter((s) => s.member_unit === 'Alpha Company');
|
||||||
|
} else if (attendanceTab.value === 'Echo') {
|
||||||
|
out = activeEvent.value.eventSignups?.filter((s) => s.member_unit === 'Echo Company')
|
||||||
|
} else {
|
||||||
|
out = activeEvent.value.eventSignups?.filter((s) => s.member_unit != 'Alpha Company' && s.member_unit != 'Echo Company')
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusOrder: Record<CalendarAttendance, number> = {
|
||||||
|
[CalendarAttendance.Attending]: 1,
|
||||||
|
[CalendarAttendance.Maybe]: 2,
|
||||||
|
[CalendarAttendance.NotAttending]: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
out.sort((a, b) => statusOrder[a.status] - statusOrder[b.status]);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
})
|
||||||
|
|
||||||
|
const attendanceCounts = computed(() => {
|
||||||
|
const signups = activeEvent.value.eventSignups ?? [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
Alpha: signups.filter(s => s.member_unit === "Alpha Company").length,
|
||||||
|
Echo: signups.filter(s => s.member_unit === "Echo Company").length,
|
||||||
|
Other: signups.filter(s =>
|
||||||
|
s.member_unit !== "Alpha Company" &&
|
||||||
|
s.member_unit !== "Echo Company"
|
||||||
|
).length,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusColor = (status: CalendarAttendance) => {
|
||||||
|
switch (status) {
|
||||||
|
case CalendarAttendance.Attending:
|
||||||
|
return "text-success";
|
||||||
|
case CalendarAttendance.Maybe:
|
||||||
|
return "text-yellow-600";
|
||||||
|
case CalendarAttendance.NotAttending:
|
||||||
|
return "text-destructive";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const displayStatus = (status: CalendarAttendance) => {
|
||||||
|
switch (status) {
|
||||||
|
case CalendarAttendance.Attending:
|
||||||
|
return "Attending";
|
||||||
|
case CalendarAttendance.Maybe:
|
||||||
|
return "Maybe";
|
||||||
|
case CalendarAttendance.NotAttending:
|
||||||
|
return "Declined";
|
||||||
|
default:
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
defineExpose({ forceReload })
|
defineExpose({ forceReload })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -188,46 +249,34 @@ defineExpose({ forceReload })
|
|||||||
{{ activeEvent.description }}
|
{{ activeEvent.description }}
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<!-- Attendance -->
|
<!-- attendance -->
|
||||||
<section class="space-y-2 w-full">
|
<section class="space-y-2 w-full">
|
||||||
<p class="text-lg font-semibold">Attendance</p>
|
<p class="text-lg font-semibold">Attendance</p>
|
||||||
<div class="flex flex-col border bg-muted/50 rounded-lg min-h-24 my-2">
|
<div class="flex flex-col border bg-muted/50 rounded-lg min-h-24 my-2">
|
||||||
<div class="flex w-full pt-2 border-b *:w-full *:text-center *:pb-1 *:cursor-pointer">
|
<div class="flex w-full pt-2 border-b *:w-full *:text-center *:pb-1 *:cursor-pointer">
|
||||||
<label
|
<label :class="attendanceTab === 'Alpha' ? 'border-b-3 border-foreground' : 'mb-[2px]'"
|
||||||
:class="viewedState === CalendarAttendance.Attending ? 'border-b-3 border-foreground' : 'mb-[2px]'"
|
@click="attendanceTab = 'Alpha'">Alpha {{ attendanceCounts.Alpha }}</label>
|
||||||
@click="viewedState = CalendarAttendance.Attending">Going {{ attending.length }}</label>
|
<label :class="attendanceTab === 'Echo' ? 'border-b-3 border-foreground' : 'mb-[2px]'"
|
||||||
<label
|
@click="attendanceTab = 'Echo'">Echo {{ attendanceCounts.Echo }}</label>
|
||||||
:class="viewedState === CalendarAttendance.Maybe ? 'border-b-3 border-foreground' : 'mb-[2px]'"
|
<label :class="attendanceTab === 'Other' ? 'border-b-3 border-foreground' : 'mb-[2px]'"
|
||||||
@click="viewedState = CalendarAttendance.Maybe">Maybe {{ maybe.length }}</label>
|
@click="attendanceTab = 'Other'">Other {{ attendanceCounts.Other }}</label>
|
||||||
<label
|
|
||||||
:class="viewedState === CalendarAttendance.NotAttending ? 'border-b-3 border-foreground' : 'mb-[2px]'"
|
|
||||||
@click="viewedState = CalendarAttendance.NotAttending">Declined {{ declined.length
|
|
||||||
}}</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="px-5 py-4 min-h-28">
|
<div class="pb-1 min-h-48">
|
||||||
<div v-if="viewedState === CalendarAttendance.Attending" v-for="person in attending">
|
<div class="grid grid-cols-2 font-semibold text-muted-foreground border-b py-1 px-3 mb-2">
|
||||||
<p>{{ person.member_name }}</p>
|
<p>Name</p>
|
||||||
|
<p class="text-right">Status</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="viewedState === CalendarAttendance.Maybe" v-for="person in maybe">
|
|
||||||
<p>{{ person.member_name }}</p>
|
<div v-for="person in attendanceList" :key="person.member_id"
|
||||||
</div>
|
class="grid grid-cols-2 py-1 *:px-3 hover:bg-muted">
|
||||||
<div v-if="viewedState === CalendarAttendance.NotAttending" v-for="person in declined">
|
|
||||||
<p>{{ person.member_name }}</p>
|
<p>{{ person.member_name }}</p>
|
||||||
|
<p :class="statusColor(person.status)" class="text-right">
|
||||||
|
{{ displayStatus(person.status) }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<!-- Footer (optional actions) -->
|
|
||||||
<!-- <div class="border-t px-4 py-3 flex items-center justify-end gap-2">
|
|
||||||
<button class="rounded-md border px-3 py-1.5 text-sm hover:bg-muted/40 transition">
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="rounded-md bg-primary text-primary-foreground px-3 py-1.5 text-sm hover:opacity-90 transition">
|
|
||||||
Open details
|
|
||||||
</button>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
Reference in New Issue
Block a user