mycode/myApp/Expiranner/IOS/MonthCalendarView.swift
2026-07-09 19:23:40 +09:00

188 lines
6.9 KiB
Swift

//
// MonthCalendarView.swift
// Expiranner
//
// : +
import SwiftUI
import SwiftData
struct MonthCalendarView: View {
let year: Int
let month: Int
@Environment(\.modelContext) private var modelContext
@Query private var items: [ProductItem]
@State private var selectedDay: Int?
@State private var showScanner = false
@State private var editingItem: ProductItem?
private let columns = Array(repeating: GridItem(.flexible()), count: 7)
private let weekdaySymbols = ["", "", "", "", "", "", ""]
init(year: Int, month: Int) {
self.year = year
self.month = month
let (start, end) = MonthCell.monthBounds(year: year, month: month)
_items = Query(
filter: #Predicate<ProductItem> { $0.date >= start && $0.date < end },
sort: \ProductItem.date
)
}
private var daysInMonth: Int {
let calendar = Calendar.current
let start = calendar.date(from: DateComponents(year: year, month: month, day: 1)) ?? Date()
return calendar.range(of: .day, in: .month, for: start)?.count ?? 30
}
/// 1 ( )
private var firstWeekdayOffset: Int {
let calendar = Calendar.current
let start = calendar.date(from: DateComponents(year: year, month: month, day: 1)) ?? Date()
return calendar.component(.weekday, from: start) - 1
}
private func day(of item: ProductItem) -> Int {
Calendar.current.component(.day, from: item.date)
}
private var selectedDayItems: [ProductItem] {
guard let selectedDay else { return [] }
return items.filter { day(of: $0) == selectedDay }
}
var body: some View {
ZStack(alignment: .bottomTrailing) {
VStack {
LazyVGrid(columns: columns, spacing: 8) {
ForEach(weekdaySymbols, id: \.self) { symbol in
Text(symbol)
.font(.caption)
.foregroundStyle(.secondary)
}
ForEach(0..<firstWeekdayOffset, id: \.self) { _ in
Text("")
}
ForEach(1...daysInMonth, id: \.self) { day in
let count = items.filter { self.day(of: $0) == day }.count
Button {
selectedDay = day
} label: {
VStack(spacing: 2) {
Text("\(day)")
.fontWeight(selectedDay == day ? .bold : .regular)
Circle()
.fill(count > 0 ? .red : .clear)
.frame(width: 6, height: 6)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 4)
.background(
RoundedRectangle(cornerRadius: 6)
.fill(selectedDay == day ? Color(.systemGray4) : .clear)
)
}
.buttonStyle(.plain)
}
}
.padding(.horizontal)
if let selectedDay {
List {
Section("\(month)\(selectedDay)일 저장된 항목") {
if selectedDayItems.isEmpty {
Text("저장된 항목이 없습니다")
.foregroundStyle(.secondary)
}
ForEach(selectedDayItems) { item in
Button {
editingItem = item
} label: {
HStack {
if let data = item.imageData, let image = UIImage(data: data) {
Image(uiImage: image)
.resizable()
.scaledToFill()
.frame(width: 44, height: 44)
.clipShape(RoundedRectangle(cornerRadius: 6))
}
Text(item.name)
Spacer()
}
}
.buttonStyle(.plain)
}
.onDelete(perform: deleteItems)
}
}
.listStyle(.insetGrouped)
} else {
Spacer()
Text("날짜를 선택하면 저장된 항목이 표시됩니다")
.foregroundStyle(.secondary)
Spacer()
}
}
FloatingScanButton {
showScanner = true
}
}
.navigationTitle("\(String(year))\(month)")
.navigationBarTitleDisplayMode(.inline)
.fullScreenCover(isPresented: $showScanner) {
ScannerView(year: year, month: month)
}
.sheet(item: $editingItem) { item in
ItemEditView(item: item)
}
}
private func deleteItems(at offsets: IndexSet) {
let targets = offsets.map { selectedDayItems[$0] }
for item in targets {
print("항목 삭제: \(item.name)")
modelContext.delete(item)
}
}
}
/// (//)
struct ItemEditView: View {
@Bindable var item: ProductItem
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
Form {
Section("제품명") {
TextField("제품명", text: $item.name)
}
Section("날짜 이동") {
DatePicker("날짜", selection: $item.date, displayedComponents: .date)
.datePickerStyle(.graphical)
}
}
.navigationTitle("항목 수정")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("완료") {
print("항목 수정 완료: \(item.name)\(item.date)")
dismiss()
}
}
}
}
}
}
#Preview {
NavigationStack {
MonthCalendarView(year: 2026, month: 7)
}
.modelContainer(for: ProductItem.self, inMemory: true)
}