import { Schema, model, Document, Types } from 'mongoose'; interface IGeoPoint { type: "Point"; coordinates: [number, number]; // [lng, lat] } interface IEventDayPhoto { url: string; publicId: string; // Cloudinary public ID for deletion caption?: string; uploadedBy: Types.ObjectId; uploadedAt: Date; } interface IEventDayAttendance { user: Types.ObjectId; checkInTime: Date; checkInMethod: 'gps' | 'qr' | 'manual' | 'hotspot'; checkInLocation?: IGeoPoint; verifiedBy?: Types.ObjectId; // Trainer who verified (for manual) } export interface IEventDay extends Document { // Reference to parent event event: Types.ObjectId; // Date information date: Date; // Specific date (YYYY-MM-DD) dayNumber: number; // Day 1, Day 2, etc. // Time slots startTime: Date; // Planned start time for this day's session endTime: Date; // Planned end time for this day's session // Photos photos: IEventDayPhoto[]; // Attendance attendance: IEventDayAttendance[]; // Optional metadata for disaster management training notes?: string; // Trainer's notes for the day weatherCondition?: string; // e.g., "Sunny", "Rainy", "Cloudy" activitiesConducted?: string[]; // List of activities/drills conducted incidentsReported?: string; // Any incidents/issues during the day equipmentUsed?: string[]; // Equipment/resources used cvDetectedCount?: number; // Number of people detected in photos via CV (optional) // Status hasStarted: boolean; // Event day has been started by trainer isCompleted: boolean; // Mark day as completed completedAt?: Date; // Metadata createdAt: Date; updatedAt: Date; } const EventDaySchema = new Schema({ event: { type: Schema.Types.ObjectId, ref: 'Event', required: true, index: true }, date: { type: Date, required: true, index: true }, dayNumber: { type: Number, required: true, min: 1 }, startTime: { type: Date, required: true }, endTime: { type: Date, required: true }, photos: [{ url: { type: String, required: true }, publicId: { type: String, required: true }, caption: { type: String, trim: true }, uploadedBy: { type: Schema.Types.ObjectId, ref: 'User', required: true }, uploadedAt: { type: Date, default: Date.now } }], attendance: [{ user: { type: Schema.Types.ObjectId, ref: 'User', required: true }, checkInTime: { type: Date, default: Date.now }, checkInMethod: { type: String, enum: ['gps', 'qr', 'manual', 'hotspot'], required: true }, checkInLocation: { type: { type: String, enum: ['Point'] }, coordinates: { type: [Number] } }, verifiedBy: { type: Schema.Types.ObjectId, ref: 'User' } }], notes: { type: String, trim: true }, weatherCondition: { type: String, trim: true }, activitiesConducted: [{ type: String, trim: true }], incidentsReported: { type: String, trim: true }, equipmentUsed: [{ type: String, trim: true }], cvDetectedCount: { type: Number, min: 0 }, hasStarted: { type: Boolean, default: false }, isCompleted: { type: Boolean, default: false }, completedAt: { type: Date } }, { timestamps: true }); // Indexes EventDaySchema.index({ event: 1, date: 1 }, { unique: true }); // Unique day per event EventDaySchema.index({ event: 1, dayNumber: 1 }); // Sequential access EventDaySchema.index({ 'attendance.user': 1 }); // User attendance queries EventDaySchema.index({ date: 1 }); // Date-based queries // Pre-save hook to validate and set completion status EventDaySchema.pre('save', function (this: IEventDay) { // Validate time slots if (this.endTime <= this.startTime) { throw new Error('End time must be after start time'); } // Validate completion requirements if (this.isModified('isCompleted') && this.isCompleted) { // Ensure photos and attendance exist if (this.photos.length === 0) { throw new Error('Cannot mark as completed: No photos uploaded'); } if (this.attendance.length === 0) { throw new Error('Cannot mark as completed: No attendance recorded'); } // Set completedAt timestamp if (!this.completedAt) { this.completedAt = new Date(); } } }); export default model('EventDay', EventDaySchema);