diff --git a/app/components/event/Modal.vue b/app/components/event/Modal.vue
index b658734..415f2ff 100644
--- a/app/components/event/Modal.vue
+++ b/app/components/event/Modal.vue
@@ -27,19 +27,32 @@ watch(filesToUpload, async (newFiles) => {
return
}
const previews = await Promise.all(
- Array.from(newFiles).map(file => new Promise((resolve) => {
- const reader = new FileReader()
- reader.onload = e => resolve({ imageUrl: e.target.result, fileName: file.name })
- reader.readAsDataURL(file)
- })),
+ Array.from(newFiles).map(
+ file =>
+ new Promise((resolve) => {
+ const reader = new FileReader()
+ reader.onload = e =>
+ resolve({
+ imageUrl: e.target.result,
+ fileName: file.name,
+ })
+ reader.readAsDataURL(file)
+ }),
+ ),
)
newEvent.value.eventAssets = previews
})
async function saveEvent() {
// Validate required fields
- if (!newEvent.value.title || !newEvent.value.startTime || !newEvent.value.endTime) {
- alert('Please fill in all required fields (Title, Start Time, End Time)')
+ if (
+ !newEvent.value.title
+ || !newEvent.value.startTime
+ || !newEvent.value.endTime
+ ) {
+ alert(
+ 'Please fill in all required fields (Title, Start Time, End Time)',
+ )
return
}
@@ -80,7 +93,10 @@ async function saveEvent() {
console.log(`✅ Uploaded: ${file.name}`)
}
catch (uploadError) {
- console.error(`❌ Failed to upload ${file.name}:`, uploadError)
+ console.error(
+ `❌ Failed to upload ${file.name}:`,
+ uploadError,
+ )
}
}
}
@@ -236,7 +252,7 @@ function cancel() {
:loading="isSaving"
@click="saveEvent"
>
- {{ isSaving ? 'Creating...' : 'Create Event' }}
+ {{ isSaving ? "Creating..." : "Create Event" }}
diff --git a/app/components/event/Tile.vue b/app/components/event/Tile.vue
index 33b365f..f271ab4 100644
--- a/app/components/event/Tile.vue
+++ b/app/components/event/Tile.vue
@@ -1,18 +1,29 @@
-
+
@@ -25,14 +36,13 @@ defineProps<{
-
+
diff --git a/app/pages/events/[id].vue b/app/pages/events/[id].vue
index 7907c11..e8e2cdb 100644
--- a/app/pages/events/[id].vue
+++ b/app/pages/events/[id].vue
@@ -3,7 +3,6 @@ import 'maplibre-gl/dist/maplibre-gl.css'
import { ref, computed, onMounted } from 'vue'
const route = useRoute()
-const router1 = useRouter()
// Get the ID from the route params
const eventId = route.params.id
@@ -13,7 +12,7 @@ const loading = ref(true)
const notFound = ref(false)
const isEditMode = ref(false)
-const editForm = ref({})
+const editForm = ref({ mobileClinic: false })
// placeholder until we implement auth
const admin = true
@@ -26,7 +25,10 @@ onMounted(async () => {
// Fetch event from your backend API
event.value = await $fetch(`/api/events/${eventId}`)
- editForm.value = { ...event.value }
+ editForm.value = {
+ ...event.value,
+ mobileClinic: Boolean(event.value?.mobileClinicId),
+ }
console.log('✅ Event loaded:', event.value)
loading.value = false
@@ -74,7 +76,10 @@ const zoom = 15
function toggleEditMode() {
if (isEditMode.value) {
// Cancel editing - reset form to original event data
- editForm.value = { ...event.value }
+ editForm.value = {
+ ...event.value,
+ mobileClinic: Boolean(event.value?.mobileClinicId),
+ }
}
isEditMode.value = !isEditMode.value
}
@@ -84,7 +89,7 @@ async function saveChanges() {
console.log('💾 Saving changes...')
// Update event via API
- await $fetch(`/api/events/${event.value.id}`, {
+ const _updatedEvent = await $fetch(`/api/events/${event.value.id}`, {
method: 'PATCH',
body: {
title: editForm.value.title,
@@ -95,6 +100,7 @@ async function saveChanges() {
endTime: new Date(editForm.value.endTime).toISOString(),
allowVolunteers: editForm.value.allowVolunteers,
allowAttendees: editForm.value.allowAttendees,
+ mobileClinic: editForm.value.mobileClinic,
},
})
@@ -219,9 +225,9 @@ const fetchedItems = event.value?.eventAssets.map(
const items = (fetchedItems?.length || 0) > 0 ? fetchedItems : placeholders
-// const backNavigate = computed(() => {
-// return admin ? "/events/manage" : "/events";
-// });
+const backNavigate = computed(() => {
+ return admin ? '/events/manage' : '/events'
+})
@@ -277,7 +283,7 @@ const items = (fetchedItems?.length || 0) > 0 ? fetchedItems : placeholders
icon="i-lucide-arrow-left"
variant="ghost"
class="text-brand4"
- @click="router1.back()"
+ @click="navigateTo(backNavigate)"
/>
0 ? fetchedItems : placeholders
Volunteer Sign-ups
- Allow people to volunteer for this event
+ Allow people to volunteer for this
+ event?
@@ -586,7 +593,7 @@ const items = (fetchedItems?.length || 0) > 0 ? fetchedItems : placeholders
Attendee Registration
- Allow people to register as attendees
+ Allow people to register as attendees?
@@ -610,6 +617,44 @@ const items = (fetchedItems?.length || 0) > 0 ? fetchedItems : placeholders
/>
+
+
+
+
+
+
+ Mobile Clinic
+
+
+ Will this event have a mobile clinic?
+
+
+
+
+
+
diff --git a/app/pages/index.vue b/app/pages/index.vue
index fb3ae86..e79992a 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -1,355 +1,336 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
- UPCOMING EVENTS
-
-
@@ -58,15 +67,58 @@ const zoom = ref(15)
// Pixel values
const snapPoints = ['230', '340', '450']
-const pastEvents = ref([])
const upcomingEvents = ref([])
-// Load all events from API on mount
+// Load both events and mobile clinic schedule on mount
onMounted(async () => {
- console.log('✅ Page mounted - Loading events from API')
+ console.log('✅ Page mounted - Loading events and mobile clinic schedule')
await loadEvents()
+ await loadMobileClinicSchedule()
})
+const eventClick = (id) => {
+ if (!id) return
+ navigateTo(`/events/${id}`)
+}
+
+const handleTileClick = (event) => {
+ if (!event?.location) return
+ mapAdjust(event.location)
+}
+
+const setUpcomingItems = (items) => {
+ upcomingEvents.value = items
+ .map(item => ({
+ ...item,
+ subtitle:
+ item.subtitle ?? new Date(item.startTime).toLocaleString(),
+ }))
+ .sort(
+ (a, b) =>
+ new Date(a.startTime).getTime()
+ - new Date(b.startTime).getTime(),
+ )
+}
+
+async function loadMobileClinicSchedule() {
+ try {
+ const schedule = await $fetch('/api/mobile-clinic/schedule')
+ console.log('✅ Loaded mobile clinic schedule:', schedule)
+
+ const scheduleItems = schedule.map(item => ({
+ ...item,
+ title: `Mobile Clinic`,
+ eventId: null,
+ }))
+
+ setUpcomingItems([...upcomingEvents.value, ...scheduleItems])
+ console.log('📅 Combined schedule items:', scheduleItems.length)
+ }
+ catch (error) {
+ console.error('❌ Error loading mobile clinic schedule:', error)
+ }
+}
+
async function loadEvents() {
try {
const allEvents = await $fetch('/api/events')
@@ -74,28 +126,27 @@ async function loadEvents() {
const now = new Date()
- pastEvents.value = allEvents.filter((event) => {
- const endTime = new Date(event.endTime)
- return endTime < now
- })
-
- upcomingEvents.value = allEvents.filter((event) => {
- const endTime = new Date(event.endTime)
- return endTime >= now
- })
+ const eventItems = allEvents
+ .filter(event => event.mobileClinicId !== null)
+ .filter(event => new Date(event.endTime) >= now)
+ .map(event => ({
+ ...event,
+ eventId: event.id,
+ }))
- console.log('📅 Past events:', pastEvents.value.length)
- console.log('📅 Upcoming events:', upcomingEvents.value.length)
+ setUpcomingItems([...upcomingEvents.value, ...eventItems])
+ console.log('📅 Upcoming events:', eventItems.length)
}
catch (error) {
console.error('❌ Error loading events:', error)
}
}
-async function mapAdjust() {
- const lng = -96.749788
- const lat = 32.985141
+async function mapAdjust(location) {
+ const lng = location.longitude
+ const lat = location.latitude
+ console.log(`📍 Adjusting map to event location: [${lng}, ${lat}]`)
center.value = [lng, lat]
zoom.value = 17
}
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 4dbafbe..09221f3 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -31,7 +31,6 @@ export default defineNuxtConfig({
},
},
eslint: {
- checker: true,
config: {
stylistic: true,
},
diff --git a/prisma/schema/location.prisma b/prisma/schema/location.prisma
index 78d3437..431b194 100644
--- a/prisma/schema/location.prisma
+++ b/prisma/schema/location.prisma
@@ -1,9 +1,8 @@
-model Location{
- id String @id @default(uuid())
- address String @unique
- latitude Float
- longitude Float
- events Event[]
- mobileClinicSchedule Mobile_Clinic_Schedule[]
- mobileClinicTimeline Mobile_Clinic_Timeline[]
-}
\ No newline at end of file
+model Location {
+ id String @id @default(uuid())
+ address String @unique
+ latitude Float
+ longitude Float
+ events Event[]
+ mobileClinicSchedule Mobile_Clinic_Schedule[]
+}
diff --git a/prisma/schema/mobileClinic.prisma b/prisma/schema/mobileClinic.prisma
index 98753db..3dc5d05 100644
--- a/prisma/schema/mobileClinic.prisma
+++ b/prisma/schema/mobileClinic.prisma
@@ -2,7 +2,6 @@ model MobileClinic {
id String @id @default(uuid())
schedule Mobile_Clinic_Schedule[]
- timeline Mobile_Clinic_Timeline[]
event Event[]
@@map("mobileClinic")
@@ -12,20 +11,10 @@ model Mobile_Clinic_Schedule {
id Int @id @default(autoincrement())
mobileClinic MobileClinic @relation(fields: [mobileClinicId], references: [id])
mobileClinicId String
- location Location @relation(fields: [locationId], references: [id])
- locationId String
- datetime DateTime
+ location Location @relation(fields: [locationId], references: [id])
+ locationId String
+ startTime DateTime @unique
+ endTime DateTime @unique
@@map("schedule")
}
-
-model Mobile_Clinic_Timeline {
- id Int @id @default(autoincrement())
- mobile_clinic MobileClinic @relation(fields: [mobileClinicId], references: [id])
- mobileClinicId String
- location Location @relation(fields: [locationId], references: [id])
- locationId String
- datetime DateTime
-
- @@map("timeline")
-}
diff --git a/prisma/seed/schedule.json b/prisma/seed/schedule.json
new file mode 100644
index 0000000..feb2d08
--- /dev/null
+++ b/prisma/seed/schedule.json
@@ -0,0 +1,15 @@
+[
+ {
+ "mobileClinic": {
+ "id": "mobile_clinic_1"
+ },
+ "location":{
+ "longitude": -97.302784,
+ "latitude": 32.707596,
+ "address": "3001 S Riverside Dr, Fort Worth, TX 76119"
+ },
+ "startTime": "2026-05-01T14:00:00.000Z",
+ "endTime": "2026-05-01T22:00:00.000Z"
+ }
+
+]
\ No newline at end of file
diff --git a/server/api/events/[id]/index.patch.ts b/server/api/events/[id]/index.patch.ts
index f23b652..0f0cb53 100644
--- a/server/api/events/[id]/index.patch.ts
+++ b/server/api/events/[id]/index.patch.ts
@@ -85,6 +85,26 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 404, message: 'Event not found' })
}
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const mobileClinicUpdate: Record
= {}
+
+ if (typeof body.mobileClinic === 'boolean') {
+ if (body.mobileClinic) {
+ if (!foundEvent.mobileClinicId) {
+ mobileClinicUpdate.mobileClinic = {
+ create: {},
+ }
+ }
+ }
+ else {
+ if (foundEvent.mobileClinicId) {
+ mobileClinicUpdate.mobileClinic = {
+ disconnect: true,
+ }
+ }
+ }
+ }
+
// Update the event
const updatedEvent = await prisma.event.update({
where: { id },
@@ -98,9 +118,11 @@ export default defineEventHandler(async (event) => {
create: locationData!,
},
},
+ ...mobileClinicUpdate,
},
include: {
eventAssets: true,
+ location: true,
},
})
diff --git a/server/api/events/index.get.ts b/server/api/events/index.get.ts
index bb36201..1fb3940 100644
--- a/server/api/events/index.get.ts
+++ b/server/api/events/index.get.ts
@@ -8,6 +8,7 @@ export default defineEventHandler(async (_event) => {
eventAssets: true,
volunteerHours: true,
participants: true,
+ location: true,
},
orderBy: {
startTime: 'asc',
diff --git a/server/api/mobile-clinic/schedule.get.ts b/server/api/mobile-clinic/schedule.get.ts
new file mode 100644
index 0000000..bb2de3c
--- /dev/null
+++ b/server/api/mobile-clinic/schedule.get.ts
@@ -0,0 +1,80 @@
+import prisma from '~~/server/utils/prisma'
+
+const MS_PER_WEEK = 7 * 24 * 60 * 60 * 1000
+const MAX_OCCURRENCES = 8
+const LOOKAHEAD_WEEKS = 4
+
+const addWeeks = (date: Date, weeks: number) =>
+ new Date(date.getTime() + weeks * MS_PER_WEEK)
+
+export default defineEventHandler(async () => {
+ try {
+ const scheduleEntries = await prisma.mobile_Clinic_Schedule.findMany({
+ include: {
+ location: true,
+ },
+ orderBy: {
+ startTime: 'asc',
+ },
+ })
+
+ const now = new Date()
+ const windowEnd = addWeeks(now, LOOKAHEAD_WEEKS)
+
+ const occurrences = scheduleEntries.flatMap((entry) => {
+ const originStart = new Date(entry.startTime)
+ const originEnd = new Date(entry.endTime)
+ const occurrencesForEntry: Array<{
+ id: string
+ scheduleId: string
+ startTime: string
+ endTime: string
+ location: { latitude: number, longitude: number, address: string }
+ }> = []
+ let startTime = new Date(originStart)
+ let endTime = new Date(originEnd)
+
+ if (endTime < now) {
+ const weeksAhead = Math.ceil(
+ (now.getTime() - endTime.getTime()) / MS_PER_WEEK,
+ )
+ startTime = addWeeks(startTime, weeksAhead)
+ endTime = addWeeks(endTime, weeksAhead)
+ }
+
+ let count = 0
+ while (count < MAX_OCCURRENCES && startTime < windowEnd) {
+ if (endTime >= now) {
+ occurrencesForEntry.push({
+ id: `${entry.id}-${count}`,
+ scheduleId: entry.id,
+ startTime: startTime.toISOString(),
+ endTime: endTime.toISOString(),
+ location: entry.location,
+ })
+ count += 1
+ }
+
+ startTime = addWeeks(startTime, 1)
+ endTime = addWeeks(endTime, 1)
+ }
+
+ return occurrencesForEntry
+ })
+
+ console.log(
+ `Generated ${occurrences.length} mobile clinic occurrences from ${scheduleEntries.length} schedule entries`,
+ )
+ return occurrences.sort(
+ (a, b) =>
+ new Date(a.startTime).getTime() - new Date(b.startTime).getTime(),
+ )
+ }
+ catch (error) {
+ console.error('Failed to fetch mobile clinic schedule', error)
+ throw createError({
+ statusCode: 500,
+ message: 'Failed to fetch mobile clinic schedule',
+ })
+ }
+})
diff --git a/server/utils/seed.ts b/server/utils/seed.ts
index ad44d86..e2de371 100644
--- a/server/utils/seed.ts
+++ b/server/utils/seed.ts
@@ -64,6 +64,19 @@ type RawVolunteer = {
}[]
}
+type RawSchedule = {
+ mobileClinic: {
+ id: string
+ }
+ location: {
+ longitude: number
+ latitude: number
+ address: string
+ }
+ startTime: string
+ endTime: string
+}
+
type RawNotification = {
id: string
title: string
@@ -211,6 +224,49 @@ async function main() {
console.log(volunteerResult)
}
+ // seed mobile clinic schedule
+ console.log('Seeding mobile clinic schedule...')
+ const rawSchedules: RawSchedule[] = JSON.parse(
+ fs.readFileSync('prisma/seed/schedule.json').toString(),
+ )
+ const schedules = rawSchedules.map((schedule) => {
+ return {
+ ...schedule,
+ startTime: new Date(schedule.startTime),
+ endTime: new Date(schedule.endTime),
+ location: {
+ connectOrCreate: {
+ where: {
+ address: schedule.location.address,
+ },
+ create: {
+ longitude: schedule.location.longitude,
+ latitude: schedule.location.latitude,
+ address: schedule.location.address,
+ },
+ },
+ },
+ mobileClinic: {
+ connectOrCreate: {
+ where: {
+ id: schedule.mobileClinic.id,
+ },
+ create: {
+ id: schedule.mobileClinic.id,
+ },
+ },
+ },
+ }
+ })
+ for (const schedule of schedules) {
+ const scheduleResult = await prisma.mobile_Clinic_Schedule.upsert({
+ where: { startTime: schedule.startTime },
+ update: {},
+ create: schedule,
+ })
+ console.log(scheduleResult)
+ }
+
// Seed six notifications - UNFINISHED
console.log('Seeding notifications...')
const rawNotifications: RawNotification[] = JSON.parse(