diff --git a/app/composables/useAdmin.ts b/app/composables/useAdmin.ts index 685b6bb..ece8700 100644 --- a/app/composables/useAdmin.ts +++ b/app/composables/useAdmin.ts @@ -8,12 +8,12 @@ export const useAdmin = () => { const callFormApi = async (method: 'GET' | 'POST' | 'PUT' | 'DELETE', params: Record = {}, body?: Record): Promise => { const queryString = method === 'GET' || method === 'DELETE' ? `?${new URLSearchParams(Object.entries(params).reduce((acc, [key, value]) => { - if (value !== undefined && value !== null) { - acc[key] = String(value) - } + if (value !== undefined && value !== null) { + acc[key] = String(value) + } - return acc - }, {} as Record)).toString()}` + return acc + }, {} as Record)).toString()}` : '' return await $fetch(`/api/form${queryString}`, { @@ -48,6 +48,13 @@ export const useAdmin = () => { return formatYmdLocal(parsed) } + //if the value is an ISO string, extract just the "YYYY-MM-DD" portion to avoid timezone shift + const isoMatch = value.match(/^(\d{4}-\d{2}-\d{2})/) + if (isoMatch && isoMatch[1]) { + const datePart = parseLocalDate(isoMatch[1]) + if (datePart) return formatYmdLocal(datePart) + } + const fallback = new Date(value) if (Number.isNaN(fallback.getTime())) { return '' @@ -104,10 +111,10 @@ export const useAdmin = () => { } // ── Builder state ── - const builderSubTab = useState<'history' | 'creation'>('builderSubTab', () => 'history') - const formTitle = useState('formTitle', () => '') - const editingFormId = useState('editingFormId', () => null) - const questions = useState('questions', () => []) + const builderSubTab = useState<'history' | 'creation'>('builderSubTab', () => 'history') + const formTitle = useState('formTitle', () => '') + const editingFormId = useState('editingFormId', () => null) + const questions = useState('questions', () => []) // Week/day pickers — default to current Monday const todayDate = new Date() @@ -116,8 +123,8 @@ export const useAdmin = () => { mon.setDate(todayDate.getDate() - dayOff) const monStr = formatYmdLocal(mon) - const formWeekStart = useState('formWeekStart', () => monStr) - const formDays = useState('formDays', () => ['Monday']) + const formWeekStart = useState('formWeekStart', () => monStr) + const formDays = useState('formDays', () => ['Monday']) const historyWeekStart = useState('historyWeekStart', () => '') const historyStatusSelection = useState>('historyStatusSelection', () => ['published', 'unpublished']) const historyGroupStartDate = useState('historyGroupStartDate', () => '') @@ -182,11 +189,12 @@ export const useAdmin = () => { } const defaultQuestions = (): any[] => [ - { id: Date.now(), type: 'video', text: '', textEs: '', reference: '', referenceEs: '', url: '' }, - { id: Date.now() + 1, type: 'text', text: '', textEs: '', reference: '', referenceEs: '', url: '' }, - { id: Date.now() + 2, type: 'mcq', text: '', textEs: '', reference: '', referenceEs: '', url: '', + { id: Date.now(), type: 'video', text: '', textEs: '', reference: '', referenceEs: '', url: '' }, + { id: Date.now() + 1, type: 'text', text: '', textEs: '', reference: '', referenceEs: '', url: '' }, + { + id: Date.now() + 2, type: 'mcq', text: '', textEs: '', reference: '', referenceEs: '', url: '', choices: [ - { text: '', correct: true }, + { text: '', correct: true }, { text: '', correct: false }, { text: '', correct: false }, { text: '', correct: false }, @@ -295,7 +303,7 @@ export const useAdmin = () => { const q: any = { id: Date.now(), type, text: '', textEs: '', reference: '', referenceEs: '', url: '' } if (type === 'mcq') { q.choices = [ - { text: '', correct: true }, + { text: '', correct: true }, { text: '', correct: false }, { text: '', correct: false }, { text: '', correct: false }, @@ -305,12 +313,12 @@ export const useAdmin = () => { } const publishForm = async () => { - if (!formTitle.value) { alert('Please enter a title!'); return } + if (!formTitle.value) { alert('Please enter a title!'); return } if (!formDays.value.length) { alert('Please select at least one day!'); return } try { const weekStart = getLastMonday(formWeekStart.value || '') - const days = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'] + const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] if (editingFormId.value) { const targetDay = formDays.value[0] || 'Monday' @@ -374,6 +382,39 @@ export const useAdmin = () => { }) } + //create duplicate forms for any additional selected days + const additionalDays = formDays.value.slice(1) + for (const day of additionalDays) { + const dayIdx = days.indexOf(day) + if (dayIdx === -1) continue + + const extraStartDate = parseLocalDate(weekStart) + if (!extraStartDate) continue + extraStartDate.setDate(extraStartDate.getDate() + dayIdx) + + const createdFormResponse = await callFormApi('POST', {}, { + action: 'createForm', + startDate: formatYmdLocal(extraStartDate), + published: true, + title: formTitle.value, + }) + + const createdForm = createdFormResponse?.data + if (!createdForm?.id) continue + + for (let index = 0; index < questions.value.length; index++) { + const question = questions.value[index] + await callFormApi('POST', {}, { + action: 'createComponent', + form: createdForm.id, + order: index, + questionType: question.type, + questionText: toApiQuestionText(question, formTitle.value), + questionOptions: buildQuestionOptions(question), + }) + } + } + await loadPublishedForms() editingFormId.value = null builderSubTab.value = 'history' @@ -395,12 +436,12 @@ export const useAdmin = () => { startDate.setDate(startDate.getDate() + dayIndex) - const createdFormResponse = await callFormApi('POST', {}, { - action: 'createForm', - startDate: formatYmdLocal(startDate), - published: true, - title: formTitle.value, - }) + const createdFormResponse = await callFormApi('POST', {}, { + action: 'createForm', + startDate: formatYmdLocal(startDate), + published: true, + title: formTitle.value, + }) const createdForm = createdFormResponse?.data @@ -431,10 +472,10 @@ export const useAdmin = () => { } const editPublishedForm = (form: any) => { - formTitle.value = form.title + formTitle.value = form.title formWeekStart.value = form.weekStart || formWeekStart.value - formDays.value = [form.day || 'Monday'] - questions.value = JSON.parse(JSON.stringify(form.questions)) + formDays.value = [form.day || 'Monday'] + questions.value = JSON.parse(JSON.stringify(form.questions)) editingFormId.value = form.id builderSubTab.value = 'creation' navigateTo('/admin/builder') @@ -451,12 +492,12 @@ export const useAdmin = () => { // ── Students / Progress ── const students = useState('adminStudents', () => [ { id: 1, name: 'Aiden Smith', initials: 'AS', email: 'aiden@school.edu', tickets: 12, streak: 4, lastActive: '2 hours ago' }, - { id: 2, name: 'Nevin Kumar', initials: 'NK', email: 'nevin@school.edu', tickets: 14, streak: 5, lastActive: 'Just now' }, - { id: 3, name: 'Swarna Jay', initials: 'SJ', email: 'swarna@school.edu',tickets: 8, streak: 2, lastActive: 'Yesterday' }, + { id: 2, name: 'Nevin Kumar', initials: 'NK', email: 'nevin@school.edu', tickets: 14, streak: 5, lastActive: 'Just now' }, + { id: 3, name: 'Swarna Jay', initials: 'SJ', email: 'swarna@school.edu', tickets: 8, streak: 2, lastActive: 'Yesterday' }, ]) const searchStudent = useState('searchStudent', () => '') - const sortStudent = useState('sortStudent', () => 'tickets') + const sortStudent = useState('sortStudent', () => 'tickets') const filteredAndSortedStudents = computed(() => { let res = students.value @@ -466,8 +507,8 @@ export const useAdmin = () => { } return [...res].sort((a, b) => { if (sortStudent.value === 'tickets') return b.tickets - a.tickets - if (sortStudent.value === 'streak') return b.streak - a.streak - if (sortStudent.value === 'name') return a.name.localeCompare(b.name) + if (sortStudent.value === 'streak') return b.streak - a.streak + if (sortStudent.value === 'name') return a.name.localeCompare(b.name) return 0 }) }) @@ -476,11 +517,11 @@ export const useAdmin = () => { const announcementSubTab = useState<'creation' | 'history'>('announcementSubTab', () => 'creation') const announcements = useState('announcements', () => [ - { id: 1, title: 'Summer Reading Challenge!', content: 'Log 20 books this month to win a Super Sage badge!', icon: '🌟', startDate: '2026-03-01', endDate: '2026-03-31', weekStart: '2026-03-02', day: 'Monday' }, - { id: 2, title: 'New Badges Available', content: 'Check the shop for new limited edition themes.', icon: '🎉', startDate: '2026-03-05', endDate: '', weekStart: '2026-03-02', day: 'Thursday' }, - { id: 3, title: 'Friday Game Night', content: 'Join us in the library for board games and snacks!', icon: '🎲', startDate: '2026-03-06', endDate: '', weekStart: '2026-03-02', day: 'Friday' }, - { id: 4, title: 'Week 10 Progress', content: 'You are doing amazing! Keep up the streak.', icon: '📈', startDate: '2026-03-09', endDate: '', weekStart: '2026-03-09', day: 'Monday' }, - { id: 5, title: 'Author Visit', content: 'Virtual session this Wednesday at 10 AM.', icon: '✍️', startDate: '2026-03-11', endDate: '', weekStart: '2026-03-09', day: 'Wednesday' }, + { id: 1, title: 'Summer Reading Challenge!', content: 'Log 20 books this month to win a Super Sage badge!', icon: '🌟', startDate: '2026-03-01', endDate: '2026-03-31', weekStart: '2026-03-02', day: 'Monday' }, + { id: 2, title: 'New Badges Available', content: 'Check the shop for new limited edition themes.', icon: '🎉', startDate: '2026-03-05', endDate: '', weekStart: '2026-03-02', day: 'Thursday' }, + { id: 3, title: 'Friday Game Night', content: 'Join us in the library for board games and snacks!', icon: '🎲', startDate: '2026-03-06', endDate: '', weekStart: '2026-03-02', day: 'Friday' }, + { id: 4, title: 'Week 10 Progress', content: 'You are doing amazing! Keep up the streak.', icon: '📈', startDate: '2026-03-09', endDate: '', weekStart: '2026-03-09', day: 'Monday' }, + { id: 5, title: 'Author Visit', content: 'Virtual session this Wednesday at 10 AM.', icon: '✍️', startDate: '2026-03-11', endDate: '', weekStart: '2026-03-09', day: 'Wednesday' }, ]) const newAnnouncement = useState('newAnnouncement', () => ({ @@ -499,7 +540,7 @@ export const useAdmin = () => { const isAnnouncementActive = (ann: any): boolean => { const now = new Date().toISOString().split('T')[0] - if(!now) return false // in case of invalid date + if (!now) return false // in case of invalid date if (ann.startDate > now) return false if (ann.endDate && ann.endDate < now) return false return true diff --git a/app/pages/reader/forms.vue b/app/pages/reader/forms.vue index 374602a..98f4d21 100644 --- a/app/pages/reader/forms.vue +++ b/app/pages/reader/forms.vue @@ -5,6 +5,14 @@ const { student, settings, updateExp } = useCurrentStudent() const { FormGroup } = useCurrentFormGroup() const { tickets, completedFormIds, logFormSubmission, logSubmissionResponse } = useCurrentStudentProgress() +//parse a date string (ISO or YYYY-MM-DD) into a local-midnight Date to avoid timezone shift when displaying weekday/day of month +function toLocalDate(dateStr: string): Date { + //extract the YYYY-MM-DD + const ymd = dateStr.split('T')[0] + const [y, m, d] = ymd.split('-').map(Number) + return new Date(y, m - 1, d) +} + const stats = computed(() => ({ xp: student.value ? student.value.exp : 0, tickets: tickets.value? tickets.value : 0, @@ -266,14 +274,14 @@ function getBadgeClass(type: string) { ? 'background:#f59e0b; color:white' : 'background:rgba(224,96,77,0.1); color:var(--brand-indigo)'" > - {{ new Date(form.startDate).toLocaleDateString('en-US', {weekday:'short'}) }} - {{ new Date(form.startDate).getDate() }} + {{ toLocalDate(form.startDate).toLocaleDateString('en-US', {weekday:'short'}) }} + {{ toLocalDate(form.startDate).getDate() }}

{{ form.title }}

- {{ new Date(form.startDate).toLocaleDateString('en-US', {weekday:'long'}) }} • + {{ toLocalDate(form.startDate).toLocaleDateString('en-US', {weekday:'long'}) }} • {{ (FormGroup.formComponents[form.id] || []).length }} Steps

✓ Done diff --git a/server/api/form/index.ts b/server/api/form/index.ts index f78e1e0..2ae5cb6 100644 --- a/server/api/form/index.ts +++ b/server/api/form/index.ts @@ -113,7 +113,7 @@ const formatDayName = (value: Date | null | undefined) => { return '' } - return WEEKDAY_NAMES[value.getUTCDay()] + return WEEKDAY_NAMES[value.getDay()] } const formatDisplayDate = (value: Date | null | undefined) => {