118 lines
5.2 KiB
TypeScript
118 lines
5.2 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import {
|
||
View, Text, TouchableOpacity, StyleSheet, FlatList, ActivityIndicator,
|
||
} from 'react-native'
|
||
import { COLORS } from '../../constants/Config'
|
||
import { getChangeCalendar } from '../../services/api'
|
||
|
||
const WEEKDAYS = ['일', '월', '화', '수', '목', '금', '토']
|
||
|
||
export default function ChangeCalendarScreen() {
|
||
const [now] = useState(new Date())
|
||
const [year, setYear] = useState(now.getFullYear())
|
||
const [month, setMonth] = useState(now.getMonth() + 1)
|
||
const [changes, setChanges] = useState<any[]>([])
|
||
const [selectedDay, setSelectedDay] = useState<number | null>(null)
|
||
const [loading, setLoading] = useState(false)
|
||
|
||
useEffect(() => {
|
||
setLoading(true)
|
||
getChangeCalendar(`${year}-${String(month).padStart(2, '0')}`)
|
||
.then(r => setChanges(r.data?.items ?? r.data ?? []))
|
||
.catch(() => setChanges([]))
|
||
.finally(() => setLoading(false))
|
||
}, [year, month])
|
||
|
||
const prevMonth = () => { if (month === 1) { setYear(y => y-1); setMonth(12) } else setMonth(m => m-1) }
|
||
const nextMonth = () => { if (month === 12) { setYear(y => y+1); setMonth(1) } else setMonth(m => m+1) }
|
||
|
||
const daysInMonth = new Date(year, month, 0).getDate()
|
||
const firstDay = new Date(year, month - 1, 1).getDay()
|
||
|
||
const hasChange = (day: number) =>
|
||
changes.some(c => new Date(c.scheduled_at ?? c.created_at).getDate() === day)
|
||
|
||
const dayChanges = selectedDay
|
||
? changes.filter(c => new Date(c.scheduled_at ?? c.created_at).getDate() === selectedDay)
|
||
: []
|
||
|
||
const cells: (number | null)[] = [
|
||
...Array(firstDay).fill(null),
|
||
...Array.from({ length: daysInMonth }, (_, i) => i + 1),
|
||
]
|
||
|
||
return (
|
||
<View style={s.container}>
|
||
{/* 헤더 */}
|
||
<View style={s.header}>
|
||
<TouchableOpacity onPress={prevMonth}><Text style={s.arrow}>‹</Text></TouchableOpacity>
|
||
<Text style={s.month}>{year}년 {month}월</Text>
|
||
<TouchableOpacity onPress={nextMonth}><Text style={s.arrow}>›</Text></TouchableOpacity>
|
||
</View>
|
||
|
||
{/* 요일 */}
|
||
<View style={s.weekRow}>
|
||
{WEEKDAYS.map(d => <Text key={d} style={[s.weekDay, d==='일'&&{color:COLORS.danger}, d==='토'&&{color:COLORS.accent}]}>{d}</Text>)}
|
||
</View>
|
||
|
||
{/* 날짜 그리드 */}
|
||
{loading ? <ActivityIndicator color={COLORS.accent} style={{ marginTop: 20 }} /> : (
|
||
<View style={s.grid}>
|
||
{cells.map((day, idx) => (
|
||
<TouchableOpacity
|
||
key={idx}
|
||
style={[s.cell, day === selectedDay && s.cellSelected]}
|
||
onPress={() => day && setSelectedDay(day === selectedDay ? null : day)}
|
||
disabled={!day}
|
||
>
|
||
{day && (
|
||
<>
|
||
<Text style={[s.dayText, day === selectedDay && s.dayTextSelected]}>{day}</Text>
|
||
{hasChange(day) && <View style={s.dot} />}
|
||
</>
|
||
)}
|
||
</TouchableOpacity>
|
||
))}
|
||
</View>
|
||
)}
|
||
|
||
{/* 선택 날짜 변경 목록 */}
|
||
{selectedDay && (
|
||
<View style={s.list}>
|
||
<Text style={s.listTitle}>{month}/{selectedDay} 변경 관리</Text>
|
||
{dayChanges.length === 0
|
||
? <Text style={s.empty}>해당 날짜의 변경 항목이 없습니다.</Text>
|
||
: dayChanges.map((c, i) => (
|
||
<View key={i} style={s.changeCard}>
|
||
<Text style={s.changeTitle}>{c.title ?? c.subject}</Text>
|
||
<Text style={s.changeMeta}>{c.status} · {c.requester ?? c.requested_by}</Text>
|
||
</View>
|
||
))
|
||
}
|
||
</View>
|
||
)}
|
||
</View>
|
||
)
|
||
}
|
||
|
||
const s = StyleSheet.create({
|
||
container: { flex: 1, backgroundColor: COLORS.bg },
|
||
header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', padding: 16, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border },
|
||
arrow: { fontSize: 24, color: COLORS.accent, paddingHorizontal: 8 },
|
||
month: { fontSize: 16, fontWeight: '700', color: COLORS.text },
|
||
weekRow: { flexDirection: 'row', backgroundColor: '#fff', paddingVertical: 6 },
|
||
weekDay: { flex: 1, textAlign: 'center', fontSize: 12, fontWeight: '600', color: COLORS.muted },
|
||
grid: { flexDirection: 'row', flexWrap: 'wrap', backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: COLORS.border },
|
||
cell: { width: '14.28%', aspectRatio: 1, alignItems: 'center', justifyContent: 'center' },
|
||
cellSelected: { backgroundColor: COLORS.light },
|
||
dayText: { fontSize: 14, color: COLORS.text },
|
||
dayTextSelected: { color: COLORS.accent, fontWeight: '700' },
|
||
dot: { width: 4, height: 4, borderRadius: 2, backgroundColor: COLORS.accent, marginTop: 2 },
|
||
list: { flex: 1, padding: 14 },
|
||
listTitle: { fontSize: 15, fontWeight: '700', color: COLORS.text, marginBottom: 10 },
|
||
empty: { color: COLORS.muted, fontSize: 14 },
|
||
changeCard: { backgroundColor: '#fff', borderRadius: 8, padding: 10, marginBottom: 6, elevation: 1 },
|
||
changeTitle: { fontSize: 14, fontWeight: '600', color: COLORS.text },
|
||
changeMeta: { fontSize: 12, color: COLORS.muted, marginTop: 2 },
|
||
})
|