mycode/myApp/Expiranner/IOS/RecordEditView.swift
songyc macbook ceb0ab8e16 feat(expiranner): 기록 날짜·시간 수정 + 매장별 지난 데이터 정리 + 주 시작 요일 설정
- RecordEditView 신규: 저장된 기록의 유통기한을 그래픽 달력 + '시간 추가'로 수정.
  날짜 상세 그리드의 길게 누르기 메뉴와 전체화면 뷰어 상단 버튼 양쪽에서 진입
- 설정 '데이터 관리'를 매장별로 분리: 지난 기록이 있는 매장만 행으로 나열해
  개별 삭제, 2개 매장 이상일 때만 '모든 매장에서 삭제' 행 노출(각각 확인 대화상자)
- 주 시작 요일 설정(일요일/월요일): 월 캘린더와 촬영 플로우 미니 달력의
  그리드·요일 기호·주말 색이 모두 따라감 (기본은 기존과 같은 일요일 시작)
- CLAUDE.md를 현재 동작(설정 화면, hasTime, 동적 색)에 맞게 갱신

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J2HQuA6RPtkh45QTMN83i2
2026-07-28 02:29:36 +09:00

193 lines
6.3 KiB
Swift

import SwiftUI
import SwiftData
/// (·) .
/// : , ' '.
struct RecordEditView: View {
let record: ProductRecord
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var context
@State private var selectedDate: Date
@State private var includeTime: Bool
@State private var timeSelection: Date
init(record: ProductRecord) {
self.record = record
_selectedDate = State(initialValue: record.expiryDate)
_includeTime = State(initialValue: record.hasTime)
_timeSelection = State(initialValue: record.hasTime
? record.expiryDate
: (KoreanCalendar.calendar.date(bySettingHour: 18, minute: 0, second: 0, of: .now) ?? .now))
}
var body: some View {
NavigationStack {
ZStack {
Theme.bg.ignoresSafeArea()
ScrollView {
VStack(spacing: 16) {
currentHeader
datePickerCard
timeRow
saveButton
}
.padding(20)
}
.scrollIndicators(.hidden)
}
.navigationTitle("유통기한 수정")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("취소") { dismiss() }
}
}
}
.presentationDragIndicator(.visible)
}
// MARK: -
private var currentHeader: some View {
HStack(spacing: 12) {
Group {
if let image = UIImage(data: record.thumbnailData) {
Image(uiImage: image)
.resizable()
.scaledToFill()
} else {
Theme.surfaceHi
}
}
.frame(width: 56, height: 56)
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.overlay(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.strokeBorder(Theme.stroke, lineWidth: 1)
)
VStack(alignment: .leading, spacing: 3) {
Text("현재 유통기한")
.font(.caption)
.foregroundStyle(Theme.textSecondary)
Text(currentText)
.font(.system(.headline, design: .rounded))
.foregroundStyle(Theme.textPrimary)
}
Spacer()
}
.padding(14)
.cardStyle()
}
private var currentText: String {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ko_KR")
formatter.dateFormat = record.hasTime ? "M월 d일 (E) a h:mm" : "M월 d일 (E)"
return formatter.string(from: record.expiryDate)
}
// MARK: -
private var datePickerCard: some View {
DatePicker(
"유통기한 날짜",
selection: $selectedDate,
displayedComponents: .date
)
.datePickerStyle(.graphical)
.tint(Theme.accent)
.environment(\.locale, Locale(identifier: "ko_KR"))
.environment(\.calendar, KoreanCalendar.calendar)
.padding(10)
.cardStyle()
}
// MARK: - ()
@ViewBuilder
private var timeRow: some View {
if includeTime {
HStack(spacing: 10) {
Image(systemName: "clock.fill")
.font(.footnote)
.foregroundStyle(Theme.accent)
Text("유통 시각")
.font(.system(.subheadline, design: .rounded).weight(.semibold))
.foregroundStyle(Theme.textPrimary)
Spacer()
DatePicker(
"",
selection: $timeSelection,
displayedComponents: .hourAndMinute
)
.labelsHidden()
.tint(Theme.accent)
Button {
withAnimation(.snappy) { includeTime = false }
Haptics.tap()
} label: {
Image(systemName: "xmark.circle.fill")
.font(.title3)
.foregroundStyle(Theme.textSecondary)
}
.buttonStyle(.plain)
}
.padding(.horizontal, 14)
.padding(.vertical, 10)
.cardStyle()
} else {
Button {
withAnimation(.snappy) { includeTime = true }
Haptics.tap()
} label: {
Label("시간 추가", systemImage: "clock")
.font(.system(.footnote, design: .rounded).weight(.semibold))
.foregroundStyle(Theme.textSecondary)
.padding(.horizontal, 14)
.padding(.vertical, 8)
.background(Capsule().fill(Theme.surfaceHi))
}
.buttonStyle(.plain)
}
}
// MARK: -
private var saveButton: some View {
Button {
save()
} label: {
Label("저장하기", systemImage: "checkmark.circle.fill")
.font(.system(.body, design: .rounded).weight(.bold))
.foregroundStyle(Theme.onAccent)
.frame(maxWidth: .infinity)
.frame(height: 54)
.background(Capsule().fill(Theme.accentGradient))
}
.padding(.top, 4)
}
private func save() {
let calendar = KoreanCalendar.calendar
let base = calendar.startOfDay(for: selectedDate)
if includeTime {
let t = calendar.dateComponents([.hour, .minute], from: timeSelection)
record.expiryDate = calendar.date(
bySettingHour: t.hour ?? 0,
minute: t.minute ?? 0,
second: 0,
of: base
) ?? base
} else {
record.expiryDate = base
}
record.hasTime = includeTime
try? context.save()
Haptics.success()
dismiss()
}
}