105 lines
4.9 KiB
TypeScript
105 lines
4.9 KiB
TypeScript
import React, { useState, useCallback } from 'react'
|
|
import { View, Text, TouchableOpacity, FlatList, StyleSheet, RefreshControl } from 'react-native'
|
|
import { useFocusEffect } from 'expo-router'
|
|
import { COLORS } from '../../constants/Config'
|
|
import client from '../../services/api'
|
|
|
|
const WEEKDAYS = ['일', '월', '화', '수', '목', '금', '토']
|
|
|
|
export default function WorkCalendarScreen() {
|
|
const today = new Date()
|
|
const [year, setYear] = useState(today.getFullYear())
|
|
const [month, setMonth] = useState(today.getMonth())
|
|
const [events, setEvents] = useState<any[]>([])
|
|
const [selected, setSelected] = useState<string>('')
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
const load = useCallback(async (y: number, m: number) => {
|
|
setLoading(true)
|
|
try {
|
|
const r = await client.get('/api/mobile2/work-calendar', { params: { year: y, month: m + 1 } })
|
|
setEvents(r.data?.events ?? r.data?.items ?? [])
|
|
} catch { setEvents([]) } finally { setLoading(false) }
|
|
}, [])
|
|
|
|
useFocusEffect(useCallback(() => { load(year, month) }, [year, month, load]))
|
|
|
|
const firstDay = new Date(year, month, 1).getDay()
|
|
const daysInMonth = new Date(year, month + 1, 0).getDate()
|
|
const cells = Array.from({ length: firstDay }, () => null).concat(Array.from({ length: daysInMonth }, (_, i) => i + 1))
|
|
|
|
const eventsOn = (day: number) => {
|
|
const key = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
|
|
return events.filter(e => (e.date ?? e.scheduled_at ?? '').startsWith(key))
|
|
}
|
|
|
|
const selectedEvents = selected ? events.filter(e => (e.date ?? e.scheduled_at ?? '').startsWith(selected)) : []
|
|
|
|
const prev = () => { if (month === 0) { setYear(y => y - 1); setMonth(11) } else setMonth(m => m - 1) }
|
|
const next = () => { if (month === 11) { setYear(y => y + 1); setMonth(0) } else setMonth(m => m + 1) }
|
|
|
|
return (
|
|
<View style={s.container}>
|
|
<View style={s.nav}>
|
|
<TouchableOpacity onPress={prev} style={s.navBtn}><Text style={s.navText}>◀</Text></TouchableOpacity>
|
|
<Text style={s.monthLabel}>{year}년 {month + 1}월</Text>
|
|
<TouchableOpacity onPress={next} style={s.navBtn}><Text style={s.navText}>▶</Text></TouchableOpacity>
|
|
</View>
|
|
|
|
<View style={s.weekRow}>
|
|
{WEEKDAYS.map(d => <Text key={d} style={[s.weekDay, d === '일' ? { color: COLORS.danger } : d === '토' ? { color: COLORS.blue } : {}]}>{d}</Text>)}
|
|
</View>
|
|
|
|
<View style={s.grid}>
|
|
{cells.map((day, i) => {
|
|
if (!day) return <View key={i} style={s.cell} />
|
|
const key = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
|
|
const dayEvents = eventsOn(day)
|
|
const isToday = day === today.getDate() && month === today.getMonth() && year === today.getFullYear()
|
|
const isSel = key === selected
|
|
return (
|
|
<TouchableOpacity key={i} style={[s.cell, isToday && s.today, isSel && s.selCell]} onPress={() => setSelected(isSel ? '' : key)}>
|
|
<Text style={[s.dayNum, isToday && { color: '#fff' }]}>{day}</Text>
|
|
{dayEvents.length > 0 && <View style={s.dot} />}
|
|
</TouchableOpacity>
|
|
)
|
|
})}
|
|
</View>
|
|
|
|
{selectedEvents.length > 0 && (
|
|
<FlatList
|
|
data={selectedEvents}
|
|
keyExtractor={(_, i) => String(i)}
|
|
style={s.eventList}
|
|
renderItem={({ item }) => (
|
|
<View style={s.eventCard}>
|
|
<Text style={s.eventTitle}>{item.title ?? item.name}</Text>
|
|
<Text style={s.eventMeta}>{item.start_time ?? ''} · {item.category ?? item.type ?? ''}</Text>
|
|
</View>
|
|
)}
|
|
/>
|
|
)}
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const s = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: COLORS.bg },
|
|
nav: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 12 },
|
|
navBtn: { padding: 8 },
|
|
navText: { fontSize: 16, color: COLORS.accent },
|
|
monthLabel: { fontSize: 18, fontWeight: '800', color: COLORS.text },
|
|
weekRow: { flexDirection: 'row', paddingHorizontal: 4 },
|
|
weekDay: { flex: 1, textAlign: 'center', fontSize: 11, fontWeight: '700', color: COLORS.muted, paddingVertical: 4 },
|
|
grid: { flexDirection: 'row', flexWrap: 'wrap', paddingHorizontal: 4 },
|
|
cell: { width: '14.28%', aspectRatio: 1, alignItems: 'center', justifyContent: 'center', padding: 2 },
|
|
today: { backgroundColor: COLORS.accent, borderRadius: 20 },
|
|
selCell: { backgroundColor: COLORS.accent + '30', borderRadius: 20 },
|
|
dayNum: { fontSize: 13, color: COLORS.text },
|
|
dot: { width: 5, height: 5, borderRadius: 3, backgroundColor: COLORS.danger, marginTop: 2 },
|
|
eventList: { flex: 1, padding: 12 },
|
|
eventCard: { backgroundColor: '#fff', borderRadius: 8, padding: 12, marginBottom: 6, elevation: 1 },
|
|
eventTitle: { fontSize: 13, fontWeight: '700', color: COLORS.text, marginBottom: 2 },
|
|
eventMeta: { fontSize: 11, color: COLORS.muted },
|
|
})
|