diff --git a/Sources/ManualDCore/EffectiveLength.swift b/Sources/ManualDCore/EffectiveLength.swift index 379d4cb..0df9ba5 100644 --- a/Sources/ManualDCore/EffectiveLength.swift +++ b/Sources/ManualDCore/EffectiveLength.swift @@ -1,3 +1,4 @@ +import Dependencies import Foundation // TODO: Not sure how to model effective length groups in the database. @@ -85,3 +86,40 @@ extension EffectiveLength { } } } + +#if DEBUG + + extension EffectiveLength { + public static let mocks: [Self] = [ + .init( + id: UUID(0), + projectID: UUID(0), + name: "Test Supply - 1", + type: .supply, + straightLengths: [10, 20, 25], + groups: [ + .init(group: 1, letter: "a", value: 20), + .init(group: 2, letter: "b", value: 15, quantity: 2), + .init(group: 3, letter: "c", value: 10, quantity: 1), + ], + createdAt: Date(), + updatedAt: Date() + ), + .init( + id: UUID(1), + projectID: UUID(0), + name: "Test Return - 1", + type: .return, + straightLengths: [10, 20, 25], + groups: [ + .init(group: 1, letter: "a", value: 20), + .init(group: 2, letter: "b", value: 15, quantity: 2), + .init(group: 3, letter: "c", value: 10, quantity: 1), + ], + createdAt: Date(), + updatedAt: Date() + ), + ] + } + +#endif diff --git a/Sources/ManualDCore/Routes/ViewRoute.swift b/Sources/ManualDCore/Routes/ViewRoute.swift index 789fbb0..1d947c6 100644 --- a/Sources/ManualDCore/Routes/ViewRoute.swift +++ b/Sources/ManualDCore/Routes/ViewRoute.swift @@ -10,6 +10,7 @@ extension SiteRoute { case project(ProjectRoute) case room(RoomRoute) case frictionRate(FrictionRateRoute) + case effectiveLength(EffectiveLengthRoute) public static let router = OneOf { Route(.case(Self.project)) { @@ -21,6 +22,9 @@ extension SiteRoute { Route(.case(Self.frictionRate)) { SiteRoute.View.FrictionRateRoute.router } + Route(.case(Self.effectiveLength)) { + SiteRoute.View.EffectiveLengthRoute.router + } } } } @@ -125,3 +129,29 @@ extension SiteRoute.View.FrictionRateRoute { case componentPressureLoss } } + +extension SiteRoute.View { + public enum EffectiveLengthRoute: Equatable, Sendable { + case form(dismiss: Bool = false) + case index + + static let rootPath = "effective-lengths" + + public static let router = OneOf { + Route(.case(Self.index)) { + Path { rootPath } + Method.get + } + Route(.case(Self.form(dismiss:))) { + Path { + rootPath + "create" + } + Method.get + Query { + Field("dismiss", default: false) { Bool.parser() } + } + } + } + } +} diff --git a/Sources/ViewController/Live.swift b/Sources/ViewController/Live.swift index 869b041..23ec423 100644 --- a/Sources/ViewController/Live.swift +++ b/Sources/ViewController/Live.swift @@ -11,6 +11,8 @@ extension ViewController.Request { return try await route.renderView(isHtmxRequest: isHtmxRequest) case .frictionRate(let route): return try await route.renderView(isHtmxRequest: isHtmxRequest) + case .effectiveLength(let route): + return try await route.renderView(isHtmxRequest: isHtmxRequest) default: // FIX: FIX return mainPage @@ -86,6 +88,23 @@ extension SiteRoute.View.FrictionRateRoute.FormType { } } +extension SiteRoute.View.EffectiveLengthRoute { + + func renderView(isHtmxRequest: Bool) async throws -> AnySendableHTML { + switch self { + case .index: + return MainPage { + EffectiveLengthsView(effectiveLengths: EffectiveLength.mocks) + } + case .form(let dismiss): + guard !dismiss else { + return div(.id("effectiveLengthForm")) {} + } + return EffectiveLengthForm() + } + } +} + private let mainPage: AnySendableHTML = { MainPage { div { diff --git a/Sources/ViewController/Views/EffectiveLength/EffectiveLengthForm.swift b/Sources/ViewController/Views/EffectiveLength/EffectiveLengthForm.swift new file mode 100644 index 0000000..28dffca --- /dev/null +++ b/Sources/ViewController/Views/EffectiveLength/EffectiveLengthForm.swift @@ -0,0 +1,38 @@ +import Elementary +import ElementaryHTMX +import ManualDCore +import Styleguide + +struct EffectiveLengthForm: HTML, Sendable { + + var body: some HTML { + div( + .id("effectiveLengthForm"), + .class( + """ + fixed top-40 left-[25vw] w-1/2 z-50 text-gray-800 + bg-gray-200 border border-gray-400 + rounded-lg shadow-lg mx-10 + """ + ) + ) { + h1(.class("text-2xl font-bold")) { "Effective Length" } + form(.class("space-y-4 p-4")) { + // FIX: Add fields + + Row { + div {} + div { + CancelButton() + .attributes( + .hx.get(route: .effectiveLength(.form(dismiss: true))), + .hx.target("#effectiveLengthForm"), + .hx.swap(.outerHTML) + ) + SubmitButton() + } + } + } + } + } +} diff --git a/Sources/ViewController/Views/EffectiveLength/EffectiveLengthsView.swift b/Sources/ViewController/Views/EffectiveLength/EffectiveLengthsView.swift new file mode 100644 index 0000000..9102ef0 --- /dev/null +++ b/Sources/ViewController/Views/EffectiveLength/EffectiveLengthsView.swift @@ -0,0 +1,106 @@ +import Elementary +import ElementaryHTMX +import ManualDCore +import Styleguide + +struct EffectiveLengthsView: HTML, Sendable { + + let effectiveLengths: [EffectiveLength] + + var body: some HTML { + div( + .class( + """ + m-4 + """ + ) + ) { + Row { + h1(.class("text-2xl font-bold")) { "Effective Lengths" } + button( + .hx.get(route: .effectiveLength(.form(dismiss: false))), + .hx.target("#effectiveLengthForm"), + .hx.swap(.outerHTML) + ) { + Icon(.circlePlus) + } + } + .attributes(.class("pb-6")) + + div( + .id("effectiveLengths"), + .class( + """ + border border-gray-200 rounded-lg shadow-lg + """ + ) + ) { + for row in effectiveLengths { + EffectiveLengthView(effectiveLength: row) + } + } + + div(.id("effectiveLengthForm")) {} + } + } + + private struct EffectiveLengthView: HTML, Sendable { + + let effectiveLength: EffectiveLength + + var straightLengthsTotal: Int { + effectiveLength.straightLengths + .reduce(into: 0) { $0 += $1 } + } + + var groupsTotal: Double { + effectiveLength.groups.reduce(into: 0) { + $0 += ($1.value * Double($1.quantity)) + } + } + + var body: some HTML { + div( + .class( + """ + pb-6 + """ + ) + ) { + Row { + span(.class("text-xl font-bold")) { effectiveLength.name } + } + Row { + Label("Straight Lengths") + } + for length in effectiveLength.straightLengths { + Row { + div {} + Number(length) + } + } + + Row { + Label("Groups") + Label("Equivalent Length") + Label("Quantity") + } + .attributes(.class("border-b border-gray-200")) + + for group in effectiveLength.groups { + Row { + span { "\(group.group)-\(group.letter)" } + Number(group.value) + Number(group.quantity) + } + } + Row { + Label("Total") + Number(Double(straightLengthsTotal) + groupsTotal, digits: 0) + .attributes(.class("text-xl font-bold")) + } + .attributes(.class("border border-gray-200")) + } + } + } +} diff --git a/Sources/ViewController/Views/Sidebar.swift b/Sources/ViewController/Views/Sidebar.swift index f6bfcb9..eec40cf 100644 --- a/Sources/ViewController/Views/Sidebar.swift +++ b/Sources/ViewController/Views/Sidebar.swift @@ -16,13 +16,14 @@ struct Sidebar: HTML { ) { row(title: "Project", icon: .mapPin, href: "/projects") row(title: "Rooms", icon: .doorClosed, href: "/rooms") - row(title: "Equivalent Lengths", icon: .rulerDimensionLine, href: "#") + row(title: "Equivalent Lengths", icon: .rulerDimensionLine, href: "/effective-lengths") row(title: "Friction Rate", icon: .squareFunction, href: "/friction-rate") .attributes(.data("active", value: "true")) row(title: "Duct Sizes", icon: .wind, href: "#") } } + // TODO: Use SiteRoute.View routes as href. private func row( title: String, icon: Icon.Key,