You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
6.0 KiB
146 lines
6.0 KiB
import Foundation
|
|
|
|
// Datenformat ist bewusst identisch mit dem HTML-Tool (leben-und-dienst.json),
|
|
// damit dessen Dateien direkt importiert und Sicherungen dort geöffnet werden können.
|
|
|
|
enum TaskGroup: String, Codable, CaseIterable, Identifiable {
|
|
case diener, schulung
|
|
var id: String { rawValue }
|
|
var title: String { self == .diener ? "Aufgaben für Diener (Ernannte)" : "Schulungsaufgaben" }
|
|
var shortTitle: String { self == .diener ? "Diener (Ernannte)" : "Schulung" }
|
|
}
|
|
|
|
struct DutyTask: Codable, Identifiable, Hashable {
|
|
var id: String
|
|
var name: String
|
|
var group: TaskGroup
|
|
var withPartner: Bool = false
|
|
var isPartner: Bool? = nil
|
|
|
|
var partnerColumn: Bool { isPartner == true }
|
|
}
|
|
|
|
struct Person: Codable, Identifiable, Hashable {
|
|
var id: String
|
|
var name: String
|
|
var gender: String = ""
|
|
var note: String = ""
|
|
var tasks: [String] = []
|
|
|
|
func may(_ taskID: String) -> Bool { tasks.contains(taskID) }
|
|
}
|
|
|
|
struct Entry: Codable, Identifiable, Hashable {
|
|
var id: String
|
|
var date: String // YYYY-MM-DD
|
|
var task: String
|
|
var person: String
|
|
var partner: String = ""
|
|
var note: String = ""
|
|
}
|
|
|
|
struct Database: Codable {
|
|
var app = "leben-und-dienst-uebersicht"
|
|
var version = 1
|
|
var updated = ""
|
|
var tasks: [DutyTask]
|
|
var persons: [Person]
|
|
var entries: [Entry]
|
|
|
|
static let defaultTasks: [DutyTask] = ([
|
|
("vorsitz", "Vorsitz", .diener, false), ("gebet", "Gebet", .diener, false),
|
|
("schaetze", "Schätze aus Gottes Wort", .diener, false), ("graben", "Schätze graben", .diener, false),
|
|
("leben", "Leben als Christ", .diener, false), ("vbs-leitung", "VBS-Leitung", .diener, false),
|
|
("vbs-leser", "VBS-Leser", .diener, false), ("bibellesung", "Bibellesung", .schulung, false),
|
|
("gespraeche", "Gespräche beginnen", .schulung, true), ("interesse", "Interesse fördern", .schulung, true),
|
|
("juenger", "Menschen zu Jüngern machen", .schulung, true), ("glauben", "Glaubensansichten erklären", .schulung, true),
|
|
("sagen", "Was würdest du sagen?", .schulung, true), ("vortrag", "Vortrag", .schulung, false),
|
|
("partner", "Partner", .schulung, false),
|
|
] as [(String, String, TaskGroup, Bool)]).map { DutyTask(id: $0.0, name: $0.1, group: $0.2, withPartner: $0.3, isPartner: $0.0 == "partner" ? true : nil) }
|
|
|
|
static var empty: Database { Database(tasks: defaultTasks, persons: [], entries: []) }
|
|
|
|
// Tolerant gegenüber Dateien des HTML-Tools mit fehlenden Feldern.
|
|
init(tasks: [DutyTask], persons: [Person], entries: [Entry]) {
|
|
self.tasks = tasks; self.persons = persons; self.entries = entries
|
|
}
|
|
init(from decoder: Decoder) throws {
|
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
app = try c.decodeIfPresent(String.self, forKey: .app) ?? app
|
|
version = try c.decodeIfPresent(Int.self, forKey: .version) ?? 1
|
|
updated = try c.decodeIfPresent(String.self, forKey: .updated) ?? ""
|
|
let t = try c.decodeIfPresent([DutyTask].self, forKey: .tasks) ?? []
|
|
tasks = t.isEmpty ? Database.defaultTasks : t
|
|
persons = try c.decode([Person].self, forKey: .persons)
|
|
entries = try c.decode([Entry].self, forKey: .entries)
|
|
}
|
|
}
|
|
|
|
extension Person {
|
|
init(from decoder: Decoder) throws {
|
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
id = try c.decode(String.self, forKey: .id)
|
|
name = try c.decode(String.self, forKey: .name)
|
|
gender = try c.decodeIfPresent(String.self, forKey: .gender) ?? ""
|
|
note = try c.decodeIfPresent(String.self, forKey: .note) ?? ""
|
|
tasks = try c.decodeIfPresent([String].self, forKey: .tasks) ?? []
|
|
}
|
|
}
|
|
|
|
extension Entry {
|
|
init(from decoder: Decoder) throws {
|
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
id = try c.decode(String.self, forKey: .id)
|
|
date = try c.decode(String.self, forKey: .date)
|
|
task = try c.decode(String.self, forKey: .task)
|
|
person = try c.decode(String.self, forKey: .person)
|
|
partner = try c.decodeIfPresent(String.self, forKey: .partner) ?? ""
|
|
note = try c.decodeIfPresent(String.self, forKey: .note) ?? ""
|
|
}
|
|
}
|
|
|
|
extension DutyTask {
|
|
init(from decoder: Decoder) throws {
|
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
id = try c.decode(String.self, forKey: .id)
|
|
name = try c.decode(String.self, forKey: .name)
|
|
group = (try? c.decode(TaskGroup.self, forKey: .group)) ?? .schulung
|
|
withPartner = try c.decodeIfPresent(Bool.self, forKey: .withPartner) ?? false
|
|
isPartner = try c.decodeIfPresent(Bool.self, forKey: .isPartner)
|
|
}
|
|
}
|
|
|
|
// MARK: - Datum
|
|
|
|
enum Day {
|
|
nonisolated(unsafe) private static let iso: DateFormatter = {
|
|
let f = DateFormatter(); f.calendar = Calendar(identifier: .gregorian)
|
|
f.locale = Locale(identifier: "en_US_POSIX"); f.timeZone = .current; f.dateFormat = "yyyy-MM-dd"; return f
|
|
}()
|
|
nonisolated(unsafe) private static let short: DateFormatter = {
|
|
let f = DateFormatter(); f.locale = Locale(identifier: "de_DE"); f.dateFormat = "dd.MM.yy"; return f
|
|
}()
|
|
|
|
static func string(_ d: Date) -> String { iso.string(from: d) }
|
|
static func date(_ s: String) -> Date? { iso.date(from: s) }
|
|
static var today: String { string(Date()) }
|
|
static func display(_ s: String) -> String { date(s).map { short.string(from: $0) } ?? s }
|
|
|
|
static func weeksAgo(_ s: String) -> Int? {
|
|
guard let d = date(s), let t = date(today) else { return nil }
|
|
let days = Calendar.current.dateComponents([.day], from: d, to: t).day ?? 0
|
|
return days < 0 ? -1 : days / 7
|
|
}
|
|
static func agoText(_ s: String) -> String {
|
|
switch weeksAgo(s) {
|
|
case .none: return ""
|
|
case .some(let w) where w < 0: return "geplant"
|
|
case 0: return "diese Woche"
|
|
case 1: return "vor 1 Woche"
|
|
case .some(let w): return "vor \(w) Wochen"
|
|
}
|
|
}
|
|
}
|
|
|
|
func newID(_ prefix: String) -> String { prefix + UUID().uuidString.prefix(8).lowercased() }
|