Skip to content

Commit dbf7b22

Browse files
authored
Merge pull request #34 from aapis/feature/hierarchy-record-list
New feature: Show records associated with job
2 parents 44b27c8 + c7dfa16 commit dbf7b22

File tree

6 files changed

+272
-19
lines changed

6 files changed

+272
-19
lines changed

KlockWork-iOS/KlockWork-iOS.xcodeproj/project.pbxproj

+14-2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
539135052C040D7500B48494 /* MiniTitleBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539135042C040D7500B48494 /* MiniTitleBar.swift */; };
5454
53B2C3692C1F7D65003507CE /* DefaultObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53B2C3682C1F7D59003507CE /* DefaultObjects.swift */; };
5555
53B2C36B2C22708D003507CE /* PageMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53B2C36A2C22708A003507CE /* PageMode.swift */; };
56+
53C059B82C33BCAE0066111C /* RecordFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53C059B72C33BCAA0066111C /* RecordFilter.swift */; };
5657
53D4CB572BFE930100AFEDEA /* KlockWork_iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D4CB562BFE930100AFEDEA /* KlockWork_iOSApp.swift */; };
5758
53D4CB642BFE930300AFEDEA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53D4CB632BFE930300AFEDEA /* Preview Assets.xcassets */; };
5859
53D4CB7B2BFE93B000AFEDEA /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D4CB6A2BFE93B000AFEDEA /* Main.swift */; };
@@ -164,6 +165,7 @@
164165
539135042C040D7500B48494 /* MiniTitleBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiniTitleBar.swift; sourceTree = "<group>"; };
165166
53B2C3682C1F7D59003507CE /* DefaultObjects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultObjects.swift; sourceTree = "<group>"; };
166167
53B2C36A2C22708A003507CE /* PageMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageMode.swift; sourceTree = "<group>"; };
168+
53C059B72C33BCAA0066111C /* RecordFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordFilter.swift; sourceTree = "<group>"; };
167169
53D4CB532BFE930100AFEDEA /* KlockWork-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "KlockWork-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
168170
53D4CB562BFE930100AFEDEA /* KlockWork_iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KlockWork_iOSApp.swift; sourceTree = "<group>"; };
169171
53D4CB612BFE930300AFEDEA /* KlockWork_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = KlockWork_iOS.entitlements; sourceTree = "<group>"; };
@@ -330,6 +332,14 @@
330332
path = Libraries;
331333
sourceTree = "<group>";
332334
};
335+
53C059B92C33BCB20066111C /* Filter */ = {
336+
isa = PBXGroup;
337+
children = (
338+
53C059B72C33BCAA0066111C /* RecordFilter.swift */,
339+
);
340+
path = Filter;
341+
sourceTree = "<group>";
342+
};
333343
53D4CB4A2BFE930100AFEDEA = {
334344
isa = PBXGroup;
335345
children = (
@@ -437,6 +447,7 @@
437447
53D4CB7A2BFE93B000AFEDEA /* Entities */ = {
438448
isa = PBXGroup;
439449
children = (
450+
53C059B92C33BCB20066111C /* Filter */,
440451
53D4CB752BFE93B000AFEDEA /* Detail */,
441452
53D4CB762BFE93B000AFEDEA /* Companies.swift */,
442453
53D4CB772BFE93B000AFEDEA /* Jobs.swift */,
@@ -653,6 +664,7 @@
653664
532441682C0531C800659F14 /* Rollups.swift in Sources */,
654665
539134F62C039F2600B48494 /* People.swift in Sources */,
655666
5311F3FD2C179CC100FB3071 /* DataExplorer.swift in Sources */,
667+
53C059B82C33BCAE0066111C /* RecordFilter.swift in Sources */,
656668
536B32B22C1A03BB00E01517 /* AppState.swift in Sources */,
657669
539135002C03AD9A00B48494 /* ListRow.swift in Sources */,
658670
53D4CB832BFE93B000AFEDEA /* NoteDetail.swift in Sources */,
@@ -805,7 +817,7 @@
805817
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
806818
CODE_SIGN_ENTITLEMENTS = "KlockWork-iOS/KlockWork_iOS.entitlements";
807819
CODE_SIGN_STYLE = Automatic;
808-
CURRENT_PROJECT_VERSION = 14;
820+
CURRENT_PROJECT_VERSION = 15;
809821
DEAD_CODE_STRIPPING = YES;
810822
DEVELOPMENT_ASSET_PATHS = "\"KlockWork-iOS/Preview Content\"";
811823
DEVELOPMENT_TEAM = 6DT7L2N5X6;
@@ -847,7 +859,7 @@
847859
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
848860
CODE_SIGN_ENTITLEMENTS = "KlockWork-iOS/KlockWork_iOS.entitlements";
849861
CODE_SIGN_STYLE = Automatic;
850-
CURRENT_PROJECT_VERSION = 14;
862+
CURRENT_PROJECT_VERSION = 15;
851863
DEAD_CODE_STRIPPING = YES;
852864
DEVELOPMENT_ASSET_PATHS = "\"KlockWork-iOS/Preview Content\"";
853865
DEVELOPMENT_TEAM = 6DT7L2N5X6;

KlockWork-iOS/KlockWork-iOS/Entities/Detail/CompanyDetail.swift

-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ struct CompanyDetail: View {
8989
.background(self.page.primaryColour)
9090
.onAppear(perform: self.actionOnAppear)
9191
.navigationTitle(self.company != nil ? "Company" : "New Company")
92-
.background(self.page.primaryColour)
9392
.scrollContentBackground(.hidden)
9493
.navigationBarTitleDisplayMode(.inline)
9594
.toolbarBackground(Theme.textBackground.opacity(0.7), for: .navigationBar)

KlockWork-iOS/KlockWork-iOS/Entities/Detail/RecordDetail.swift

+102-12
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,33 @@
88
import SwiftUI
99

1010
struct RecordDetail: View {
11-
public let record: LogRecord
12-
11+
@EnvironmentObject private var state: AppState
12+
@Environment(\.dismiss) private var dismiss
13+
public var record: LogRecord?
1314
@State private var timestamp = Date()
1415
@State private var message: String = ""
16+
@State public var job: Job?
17+
@State private var alive: Bool = false
18+
@State private var isJobSelectorPresented: Bool = false
19+
@State private var isSaveAlertPresented: Bool = false
20+
@State private var isDeleteAlertPresented: Bool = false
21+
public var page: PageConfiguration.AppPage = .create
1522

1623
var body: some View {
1724
VStack {
1825
List {
26+
Widget.JobSelector.FormField(
27+
job: $job,
28+
isJobSelectorPresented: $isJobSelectorPresented
29+
)
30+
31+
Section("Message") {
32+
TextField("Record content", text: $message, axis: .vertical)
33+
}
34+
.listRowBackground(Theme.textBackground)
35+
1936
Section("Settings") {
37+
Toggle("Published", isOn: $alive)
2038
DatePicker(
2139
"Created",
2240
selection: $timestamp,
@@ -26,33 +44,105 @@ struct RecordDetail: View {
2644
}
2745
.listRowBackground(Theme.textBackground)
2846

29-
Section("Message") {
30-
TextField("Record content", text: $message, axis: .vertical)
47+
if self.record != nil {
48+
Button("Delete Record", role: .destructive, action: self.actionInitiateDelete)
49+
.alert("Are you sure?", isPresented: $isDeleteAlertPresented) {
50+
Button("Yes", role: .destructive) {
51+
self.actionOnDelete()
52+
}
53+
} message: {
54+
Text("This record will be permanently deleted.")
55+
}
56+
.listRowBackground(Color.red)
57+
.foregroundStyle(.white)
3158
}
32-
.listRowBackground(Theme.textBackground)
3359
}
3460
Spacer()
3561
}
3662
.onAppear(perform: actionOnAppear)
37-
.navigationTitle(message.count >= 10 ? "\(message.prefix(10).capitalized)..." : message.capitalized)
63+
.navigationTitle(self.record != nil ? "Record" : "New Record")
3864
.toolbarBackground(Theme.textBackground.opacity(0.7), for: .navigationBar)
3965
.toolbarBackground(.visible, for: .navigationBar)
4066
.toolbar {
41-
Button("Save") {
42-
67+
ToolbarItem(placement: .topBarTrailing) {
68+
// Creates new entity on tap, then sends user back to Today
69+
Button {
70+
self.actionOnSave()
71+
} label: {
72+
Text("Save")
73+
}
74+
.foregroundStyle(self.state.theme.tint)
75+
.alert("Saved", isPresented: $isSaveAlertPresented) {
76+
Button("OK") {
77+
dismiss()
78+
}
79+
} message: {
80+
Text("It is done.")
81+
}
4382
}
4483
}
84+
.sheet(isPresented: $isJobSelectorPresented) {
85+
Widget.JobSelector.Single(
86+
showing: $isJobSelectorPresented,
87+
job: $job
88+
)
89+
.presentationBackground(self.page.primaryColour)
90+
}
4591
}
4692
}
4793

4894
extension RecordDetail {
95+
/// Onload handler. Sets timestamp and message fields.
96+
/// - Returns: Void
4997
private func actionOnAppear() -> Void {
50-
if let tmstmp = record.timestamp {
51-
timestamp = tmstmp
98+
if self.record != nil {
99+
if let tmstmp = self.record!.timestamp {
100+
self.timestamp = tmstmp
101+
}
102+
103+
if let msg = self.record!.message {
104+
self.message = msg
105+
}
106+
107+
self.alive = self.record!.alive
108+
self.job = self.record!.job
52109
}
110+
}
53111

54-
if let msg = record.message {
55-
message = msg
112+
/// Save handler
113+
/// - Returns: Void
114+
private func actionOnSave() -> Void {
115+
if self.record != nil {
116+
self.record!.message = self.message
117+
self.record!.job = self.job
118+
self.record!.alive = self.alive
119+
} else {
120+
CoreDataRecords(moc: self.state.moc).create(
121+
message: self.message,
122+
timestamp: self.timestamp,
123+
job: self.job,
124+
saveByDefault: false
125+
)
56126
}
127+
128+
isSaveAlertPresented.toggle()
129+
PersistenceController.shared.save()
130+
}
131+
132+
/// Soft delete a Task
133+
/// - Returns: Void
134+
private func actionOnDelete() -> Void {
135+
if self.record != nil {
136+
self.state.moc.delete(self.record!)
137+
}
138+
139+
PersistenceController.shared.save()
140+
dismiss()
141+
}
142+
143+
/// Opens the delete object alert
144+
/// - Returns: Void
145+
private func actionInitiateDelete() -> Void {
146+
self.isDeleteAlertPresented.toggle()
57147
}
58148
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//
2+
// RecordFilter.swift
3+
// KlockWork-iOS
4+
//
5+
// Created by Ryan Priebe on 2024-07-01.
6+
//
7+
8+
import SwiftUI
9+
10+
struct RecordsGroupedByDate: Identifiable {
11+
var id: UUID = UUID()
12+
var date: Date
13+
var records: [LogRecord]
14+
}
15+
16+
struct RecordFilter: View {
17+
typealias Button = Tabs.Content.Individual.SingleRecord
18+
public let job: Job
19+
public var page: PageConfiguration.AppPage = .create
20+
@FetchRequest private var records: FetchedResults<LogRecord>
21+
@State private var groupedRecords: [RecordsGroupedByDate] = []
22+
23+
var body: some View {
24+
VStack(alignment: .leading, spacing: 0) {
25+
ScrollView(showsIndicators: false) {
26+
ForEach(groupedRecords) { group in
27+
VStack(alignment: .leading, spacing: 1) {
28+
HStack(alignment: .center, spacing: 5) {
29+
Image(systemName: "calendar")
30+
Text(group.date.formatted(date: .abbreviated, time: .omitted))
31+
Spacer()
32+
}
33+
.padding(8)
34+
35+
ForEach(records) { record in
36+
Button(record: record)
37+
}
38+
}
39+
}
40+
}
41+
}
42+
.onAppear(perform: self.actionOnAppear)
43+
.navigationTitle("Records")
44+
.background(self.page.primaryColour)
45+
.scrollContentBackground(.hidden)
46+
.navigationBarTitleDisplayMode(.inline)
47+
.toolbarBackground(Theme.textBackground.opacity(0.7), for: .navigationBar)
48+
.toolbarBackground(.visible, for: .navigationBar)
49+
.scrollDismissesKeyboard(.immediately)
50+
}
51+
52+
init(job: Job) {
53+
self.job = job
54+
_records = CoreDataRecords.fetch(job: self.job)
55+
}
56+
}
57+
58+
extension RecordFilter {
59+
/// Onload handler
60+
/// - Returns: Void
61+
private func actionOnAppear() -> Void {
62+
self.groupedRecords = []
63+
if self.records.count > 0 {
64+
let grouped = Array(self.records)
65+
.sliced(by: [.year, .month, .day], for: \.timestamp!)
66+
.sorted(by: {$0.key > $1.key})
67+
68+
for group in grouped {
69+
self.groupedRecords.append(
70+
RecordsGroupedByDate(date: group.key, records: group.value)
71+
)
72+
}
73+
}
74+
}
75+
}
76+
77+
/// Thank you https://stackoverflow.com/a/64496966
78+
extension Array {
79+
func sliced(by dateComponents: Set<Calendar.Component>, for key: KeyPath<Element, Date>) -> [Date: [Element]] {
80+
let initial: [Date: [Element]] = [:]
81+
let groupedByDateComponents = reduce(into: initial) { acc, cur in
82+
let components = Calendar.current.dateComponents(dateComponents, from: cur[keyPath: key])
83+
let date = Calendar.current.date(from: components)!
84+
let existing = acc[date] ?? []
85+
acc[date] = existing + [cur]
86+
}
87+
88+
return groupedByDateComponents
89+
}
90+
}

0 commit comments

Comments
 (0)