244 lines
8.5 KiB
TypeScript
244 lines
8.5 KiB
TypeScript
'use server'
|
|
|
|
import { supabase } from '@/lib/supabase'
|
|
import { revalidatePath } from 'next/cache'
|
|
|
|
export async function generateInstantSchedule(prevState: any, formData: FormData) {
|
|
const tanggal = formData.get('tanggal') as string
|
|
const startStr = (formData.get('jam_mulai') as string) || '08:00'
|
|
const endStr = (formData.get('jam_selesai') as string) || '11:00'
|
|
const editedBy = formData.get('edited_by') as string
|
|
|
|
if (!tanggal) {
|
|
return { success: false, message: 'Tanggal pelaksanaan harus dipilih.' }
|
|
}
|
|
|
|
// Lead time validation: Must be at least tomorrow
|
|
const inputDate = new Date(tanggal)
|
|
const tomorrow = new Date()
|
|
tomorrow.setHours(0, 0, 0, 0)
|
|
tomorrow.setDate(tomorrow.getDate() + 1)
|
|
|
|
if (inputDate < tomorrow) {
|
|
return { success: false, message: 'Penjadwalan harus dilakukan minimal 1 hari sebelumnya (Besok atau lusa).' }
|
|
}
|
|
|
|
try {
|
|
// 1. Fetch all registered Posyandu
|
|
const { data: allPosyandu, error: fetchErr } = await supabase
|
|
.from('detail_posyandu')
|
|
.select('id, nama_posyandu')
|
|
|
|
if (fetchErr) throw fetchErr
|
|
if (!allPosyandu || allPosyandu.length === 0) {
|
|
return { success: false, message: 'Tidak ada data Posyandu terdaftar.' }
|
|
}
|
|
|
|
// 2. Shuffle Posyandu randomly
|
|
const shuffled = [...allPosyandu].sort(() => Math.random() - 0.5)
|
|
|
|
// 3. Prepare schedules
|
|
// Calculate duration of the first session
|
|
const [hStart, mStart] = startStr.split(':').map(Number)
|
|
const [hEnd, mEnd] = endStr.split(':').map(Number)
|
|
|
|
const durationMinutes = (hEnd * 60 + mEnd) - (hStart * 60 + mStart)
|
|
if (durationMinutes <= 0) {
|
|
return { success: false, message: 'Jam selesai harus setelah jam mulai.' }
|
|
}
|
|
|
|
const schedulePack = shuffled.map((posyandu, index) => {
|
|
const dayOffset = Math.floor(index / 3)
|
|
const positionInDay = index % 3
|
|
|
|
// Calculate date for this posyandu
|
|
const dateObj = new Date(tanggal)
|
|
dateObj.setDate(dateObj.getDate() + dayOffset)
|
|
const currentTanggal = dateObj.toISOString().split('T')[0]
|
|
|
|
// Calculate time for this posyandu
|
|
let startTime = new Date(`1970-01-01T${startStr}:00`)
|
|
if (positionInDay > 0) {
|
|
// Add (duration + gap) for each previous session in the same day
|
|
startTime.setMinutes(startTime.getMinutes() + positionInDay * (durationMinutes + 60))
|
|
}
|
|
|
|
const sessionEnd = new Date(startTime)
|
|
sessionEnd.setMinutes(sessionEnd.getMinutes() + durationMinutes)
|
|
|
|
const pad = (n: number) => n.toString().padStart(2, '0')
|
|
|
|
const jam_mulai = `${pad(startTime.getHours())}:${pad(startTime.getMinutes())}:00`
|
|
const jam_selesai = `${pad(sessionEnd.getHours())}:${pad(sessionEnd.getMinutes())}:00`
|
|
|
|
return {
|
|
posyandu_id: posyandu.id,
|
|
tanggal: currentTanggal,
|
|
jam_mulai,
|
|
jam_selesai,
|
|
diedit_oleh: editedBy
|
|
}
|
|
})
|
|
|
|
// 4. Batch Insert
|
|
const { error: insertErr } = await supabase
|
|
.from('jadwal_posyandu')
|
|
.insert(schedulePack)
|
|
|
|
if (insertErr) throw insertErr
|
|
|
|
revalidatePath('/dashboard/kelola-jadwal')
|
|
return { success: true, message: `Berhasil menjadwalkan ${shuffled.length} Posyandu!` }
|
|
|
|
} catch (error: any) {
|
|
console.error('Scheduling error:', error)
|
|
return { success: false, message: error.message || 'Terjadi kesalahan saat membuat jadwal.' }
|
|
}
|
|
}
|
|
|
|
export async function deleteSchedulesByDate(date: string) {
|
|
try {
|
|
const { error } = await supabase
|
|
.from('jadwal_posyandu')
|
|
.delete()
|
|
.eq('tanggal', date)
|
|
|
|
if (error) throw error
|
|
revalidatePath('/dashboard/kelola-jadwal')
|
|
return { success: true, message: 'Jadwal hari tersebut telah dihapus.' }
|
|
} catch (error: any) {
|
|
return { success: false, message: error.message }
|
|
}
|
|
}
|
|
|
|
export async function deleteJadwal(id: string) {
|
|
try {
|
|
const { error } = await supabase
|
|
.from('jadwal_posyandu')
|
|
.delete()
|
|
.eq('id', id)
|
|
|
|
if (error) throw error
|
|
revalidatePath('/dashboard/kelola-jadwal')
|
|
return { success: true, message: 'Jadwal berhasil dihapus.' }
|
|
} catch (error: any) {
|
|
return { success: false, message: error.message }
|
|
}
|
|
}
|
|
|
|
export async function updateJadwal(prevState: any, formData: FormData) {
|
|
const id = formData.get('id') as string
|
|
const tanggal = formData.get('tanggal') as string
|
|
const jam_mulai = formData.get('jam_mulai') as string
|
|
const jam_selesai = formData.get('jam_selesai') as string
|
|
const editedBy = formData.get('edited_by') as string
|
|
|
|
if (!id || !tanggal || !jam_mulai || !jam_selesai) {
|
|
return { success: false, message: 'Field wajib tidak boleh kosong.' }
|
|
}
|
|
|
|
try {
|
|
const { error } = await supabase
|
|
.from('jadwal_posyandu')
|
|
.update({
|
|
tanggal,
|
|
jam_mulai: jam_mulai.length === 5 ? `${jam_mulai}:00` : jam_mulai,
|
|
jam_selesai: jam_selesai.length === 5 ? `${jam_selesai}:00` : jam_selesai,
|
|
diedit_oleh: editedBy,
|
|
updated_at: new Date().toISOString()
|
|
})
|
|
.eq('id', id)
|
|
|
|
if (error) throw error
|
|
|
|
revalidatePath('/dashboard/kelola-jadwal')
|
|
return { success: true, message: 'Jadwal berhasil diperbarui!' }
|
|
} catch (error: any) {
|
|
console.error('Update error:', error)
|
|
return { success: false, message: error.message || 'Gagal memperbarui jadwal.' }
|
|
}
|
|
}
|
|
|
|
export async function getOccupiedSlots(date: string) {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('jadwal_posyandu')
|
|
.select('jam_mulai, jam_selesai, posyandu_id')
|
|
.eq('tanggal', date)
|
|
|
|
if (error) throw error
|
|
|
|
return {
|
|
success: true,
|
|
slots: data.map(s => ({
|
|
start: s.jam_mulai.slice(0, 5),
|
|
end: s.jam_selesai.slice(0, 5),
|
|
posyandu_id: s.posyandu_id
|
|
}))
|
|
}
|
|
} catch (error: any) {
|
|
return { success: false, slots: [] }
|
|
}
|
|
}
|
|
|
|
export async function archiveSchedulesByMonth(month: number, year: number) {
|
|
try {
|
|
const startOfMonth = `${year}-${String(month + 1).padStart(2, '0')}-01`
|
|
const endOfMonth = new Date(year, month + 1, 0).toISOString().split('T')[0]
|
|
|
|
const { data: targets, error: fetchErr } = await supabase
|
|
.from('jadwal_posyandu')
|
|
.select('id, diedit_oleh')
|
|
.gte('tanggal', startOfMonth)
|
|
.lte('tanggal', endOfMonth)
|
|
.not('diedit_oleh', 'ilike', '[HISTORY]%')
|
|
|
|
if (fetchErr) throw fetchErr
|
|
if (!targets || targets.length === 0) return { success: true, message: 'Tidak ada jadwal aktif untuk diarsipkan.' }
|
|
|
|
// Update each target to have [HISTORY] prefix
|
|
for (const item of targets) {
|
|
const { error: updErr } = await supabase
|
|
.from('jadwal_posyandu')
|
|
.update({ diedit_oleh: `[HISTORY] ${item.diedit_oleh}` })
|
|
.eq('id', item.id)
|
|
if (updErr) throw updErr
|
|
}
|
|
|
|
revalidatePath('/dashboard/kelola-jadwal')
|
|
return { success: true, message: 'Seluruh jadwal bulan ini telah dipindahkan ke Histori!' }
|
|
} catch (error: any) {
|
|
return { success: false, message: error.message || 'Gagal mengarsipkan jadwal.' }
|
|
}
|
|
}
|
|
|
|
export async function deleteAllHistory() {
|
|
try {
|
|
const { error } = await supabase
|
|
.from('jadwal_posyandu')
|
|
.delete()
|
|
.ilike('diedit_oleh', '[HISTORY]%')
|
|
|
|
if (error) throw error
|
|
|
|
revalidatePath('/dashboard/kelola-jadwal')
|
|
return { success: true, message: 'Seluruh riwayat jadwal berhasil dihapus permanen!' }
|
|
} catch (error: any) {
|
|
return { success: false, message: error.message || 'Gagal menghapus histori.' }
|
|
}
|
|
}
|
|
|
|
export async function getPetugasLokalByPosyandu(posyanduId: string) {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('petugas_posyandu_lokal')
|
|
.select('nama_petugas, nomor_hp, jabatan')
|
|
.eq('posyandu_id', posyanduId)
|
|
|
|
if (error) throw error
|
|
return { success: true, petugas: data }
|
|
} catch (error: any) {
|
|
return { success: false, petugas: [] }
|
|
}
|
|
}
|