import SwiftUI import SwiftData /// 첫 화면: 점검할 매장을 고르거나 새로 추가한다. struct StoreListView: View { @Environment(\.modelContext) private var context @Query(sort: \Store.createdAt) private var stores: [Store] @Query private var allRecords: [ProductRecord] @State private var showingAdd = false @State private var newName = "" @State private var renameTarget: Store? @State private var renameText = "" @State private var deleteTarget: Store? var body: some View { NavigationStack { ZStack { Theme.bg.ignoresSafeArea() ScrollView { VStack(alignment: .leading, spacing: 24) { header if stores.isEmpty { emptyState } else { storeList } } .padding(20) } } .navigationDestination(for: Store.self) { store in YearMonthView(store: store) } .toolbar(.hidden, for: .navigationBar) } .alert("새 매장 추가", isPresented: $showingAdd) { TextField("매장 이름", text: $newName) Button("추가") { addStore() } Button("취소", role: .cancel) { newName = "" } } message: { Text("점검할 매장의 이름을 입력하세요.") } .alert("이름 변경", isPresented: isRenaming) { TextField("매장 이름", text: $renameText) Button("변경") { renameStore() } Button("취소", role: .cancel) { renameTarget = nil } } .confirmationDialog( deleteMessage, isPresented: isDeleting, titleVisibility: .visible ) { Button("삭제", role: .destructive) { deleteStore() } Button("취소", role: .cancel) { deleteTarget = nil } } } // MARK: - 헤더 private var header: some View { HStack(alignment: .top) { VStack(alignment: .leading, spacing: 6) { Text("유통기한 점검") .font(.caption.weight(.bold)) .foregroundStyle(Theme.accent) .kerning(1) Text("어느 매장을\n점검할까요?") .font(.system(size: 30, weight: .bold, design: .rounded)) .foregroundStyle(Theme.textPrimary) .lineSpacing(3) } Spacer() Button { newName = "" showingAdd = true } label: { Image(systemName: "plus") .font(.title3.weight(.semibold)) .foregroundStyle(Theme.onAccent) .frame(width: 44, height: 44) .background(Circle().fill(Theme.accentGradient)) .shadow(color: Theme.accent.opacity(0.35), radius: 12, y: 4) } } .padding(.top, 12) } // MARK: - 매장 목록 private var storeList: some View { VStack(spacing: 12) { ForEach(stores) { store in NavigationLink(value: store) { storeCard(store) } .buttonStyle(.plain) .contextMenu { Button { renameText = store.name renameTarget = store } label: { Label("이름 변경", systemImage: "pencil") } Button(role: .destructive) { deleteTarget = store } label: { Label("매장 삭제", systemImage: "trash") } } } } } private func storeCard(_ store: Store) -> some View { let counts = recordCounts(for: store) return HStack(spacing: 14) { ZStack { RoundedRectangle(cornerRadius: 14, style: .continuous) .fill(Theme.accentSoft) .frame(width: 50, height: 50) Image(systemName: "storefront.fill") .font(.title3) .foregroundStyle(Theme.accent) } VStack(alignment: .leading, spacing: 4) { Text(store.name) .font(.system(.headline, design: .rounded)) .foregroundStyle(Theme.textPrimary) Text(countLine(counts)) .font(.caption) .foregroundStyle(Theme.textSecondary) } Spacer() Image(systemName: "chevron.right") .font(.footnote.weight(.semibold)) .foregroundStyle(Theme.textSecondary) } .padding(16) .cardStyle() } private func countLine(_ counts: (total: Int, thisMonth: Int)) -> String { counts.total == 0 ? "아직 기록이 없어요" : "전체 \(counts.total)개 · 이번 달 \(counts.thisMonth)개" } // MARK: - 빈 상태 private var emptyState: some View { VStack(spacing: 14) { Image(systemName: "storefront") .font(.system(size: 44)) .foregroundStyle(Theme.accent.opacity(0.7)) Text("등록된 매장이 없어요") .font(.system(.headline, design: .rounded)) .foregroundStyle(Theme.textPrimary) Text("오른쪽 위 + 버튼으로\n첫 매장을 추가해 보세요.") .font(.subheadline) .foregroundStyle(Theme.textSecondary) .multilineTextAlignment(.center) } .frame(maxWidth: .infinity) .padding(.vertical, 70) .cardStyle() } // MARK: - 동작 private func recordCounts(for store: Store) -> (total: Int, thisMonth: Int) { let records = allRecords.filter { $0.store === store } let now = KoreanCalendar.calendar.dateComponents([.year, .month], from: .now) let thisMonth = records.filter { let c = KoreanCalendar.calendar.dateComponents([.year, .month], from: $0.expiryDate) return c.year == now.year && c.month == now.month } return (records.count, thisMonth.count) } private func addStore() { let name = newName.trimmingCharacters(in: .whitespacesAndNewlines) newName = "" guard !name.isEmpty else { return } context.insert(Store(name: name)) try? context.save() Haptics.success() } private func renameStore() { guard let store = renameTarget else { return } let name = renameText.trimmingCharacters(in: .whitespacesAndNewlines) renameTarget = nil guard !name.isEmpty else { return } store.name = name try? context.save() Haptics.tap() } private func deleteStore() { guard let store = deleteTarget else { return } deleteTarget = nil context.delete(store) try? context.save() Haptics.warning() } private var isRenaming: Binding { Binding( get: { renameTarget != nil }, set: { if !$0 { renameTarget = nil } } ) } private var isDeleting: Binding { Binding( get: { deleteTarget != nil }, set: { if !$0 { deleteTarget = nil } } ) } private var deleteMessage: String { guard let store = deleteTarget else { return "" } let count = allRecords.filter { $0.store === store }.count return count > 0 ? "'\(store.name)'과 기록 \(count)개를 모두 삭제할까요?" : "'\(store.name)'을(를) 삭제할까요?" } }