- RecordEditView 신규: 저장된 기록의 유통기한을 그래픽 달력 + '시간 추가'로 수정. 날짜 상세 그리드의 길게 누르기 메뉴와 전체화면 뷰어 상단 버튼 양쪽에서 진입 - 설정 '데이터 관리'를 매장별로 분리: 지난 기록이 있는 매장만 행으로 나열해 개별 삭제, 2개 매장 이상일 때만 '모든 매장에서 삭제' 행 노출(각각 확인 대화상자) - 주 시작 요일 설정(일요일/월요일): 월 캘린더와 촬영 플로우 미니 달력의 그리드·요일 기호·주말 색이 모두 따라감 (기본은 기존과 같은 일요일 시작) - CLAUDE.md를 현재 동작(설정 화면, hasTime, 동적 색)에 맞게 갱신 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J2HQuA6RPtkh45QTMN83i2
193 lines
6.3 KiB
Swift
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()
|
|
}
|
|
}
|