From 582d94d13bb562baf8d28f221c336477e5cfde5e Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Thu, 1 Jan 2026 08:47:23 -0500 Subject: [PATCH] WIP: Sidebar improvements, working on other views. --- Sources/ManualDCore/Project.swift | 18 ++++++ Sources/Styleguide/Number.swift | 31 ++++++++++ Sources/ViewController/Live.swift | 2 +- .../FrictionRate/ComponentLossTable.swift | 62 ------------------- .../FrictionRate/ComponentLossesView.swift | 50 +++++++++++++++ .../FrictionRate/EquipmentInfoView.swift | 6 +- .../Views/FrictionRate/FrictionRateView.swift | 4 +- Sources/ViewController/Views/MainPage.swift | 10 +-- .../Views/Project/ProjectView.swift | 54 ++++++++++++++++ Sources/ViewController/Views/Sidebar.swift | 14 +++-- 10 files changed, 175 insertions(+), 76 deletions(-) create mode 100644 Sources/Styleguide/Number.swift delete mode 100644 Sources/ViewController/Views/FrictionRate/ComponentLossTable.swift create mode 100644 Sources/ViewController/Views/FrictionRate/ComponentLossesView.swift create mode 100644 Sources/ViewController/Views/Project/ProjectView.swift diff --git a/Sources/ManualDCore/Project.swift b/Sources/ManualDCore/Project.swift index 0101215..c70e723 100644 --- a/Sources/ManualDCore/Project.swift +++ b/Sources/ManualDCore/Project.swift @@ -1,3 +1,4 @@ +import Dependencies import Foundation public struct Project: Codable, Equatable, Identifiable, Sendable { @@ -57,3 +58,20 @@ extension Project { } } } + +#if DEBUG + + extension Project { + public static let mock = Self( + id: UUID(0), + name: "Testy McTestface", + streetAddress: "1234 Sesame Street", + city: "Monroe", + state: "OH", + zipCode: "55555", + createdAt: Date(), + updatedAt: Date() + ) + } + +#endif diff --git a/Sources/Styleguide/Number.swift b/Sources/Styleguide/Number.swift new file mode 100644 index 0000000..1227a0a --- /dev/null +++ b/Sources/Styleguide/Number.swift @@ -0,0 +1,31 @@ +import Elementary +import Foundation + +public struct Number: HTML, Sendable { + let fractionDigits: Int + let value: Double + + private var formatter: NumberFormatter { + let formatter = NumberFormatter() + formatter.maximumFractionDigits = fractionDigits + return formatter + } + + public init( + _ value: Double, + digits fractionDigits: Int = 2 + ) { + self.value = value + self.fractionDigits = fractionDigits + } + + public init( + _ value: Int + ) { + self.init(Double(value), digits: 0) + } + + public var body: some HTML { + span { formatter.string(for: value) ?? "N/A" } + } +} diff --git a/Sources/ViewController/Live.swift b/Sources/ViewController/Live.swift index 8284ca9..41cc022 100644 --- a/Sources/ViewController/Live.swift +++ b/Sources/ViewController/Live.swift @@ -23,7 +23,7 @@ extension SiteRoute.View.ProjectRoute { switch self { case .index: return MainPage { - ProjectForm() + ProjectView(project: .mock) } case .form: return MainPage { diff --git a/Sources/ViewController/Views/FrictionRate/ComponentLossTable.swift b/Sources/ViewController/Views/FrictionRate/ComponentLossTable.swift deleted file mode 100644 index 972ab70..0000000 --- a/Sources/ViewController/Views/FrictionRate/ComponentLossTable.swift +++ /dev/null @@ -1,62 +0,0 @@ -import Elementary -import ElementaryHTMX -import ManualDCore -import Styleguide - -struct ComponentPressureLossTable: HTML, Sendable { - - let componentPressureLosses: [ComponentPressureLoss] - - var body: some HTML { - div(.id("cplTable")) { - h1(.class("text-2xl font-bold pb-4")) { "Component Pressure Losses" } - table( - .class( - "w-full border-collapse border border-gray-200 table-fixed" - ) - ) { - thead { tableHeader } - tbody(.id("cplTableBody")) { - Rows(componentPressureLosses: componentPressureLosses) - } - } - } - div(.id("componentLossForm")) {} - } - - private var tableHeader: some HTML { - tr { - th(.class("border border-gray-200 text-xl font-bold")) { "Name" } - th(.class("border border-gray-200 text-xl font-bold")) { "Pressure Loss" } - th(.class("border border-gray-200 text-xl font-bold")) { - Row { - div {} - button( - .class("px-2"), - .hx.get(route: .frictionRate(.form(.componentPressureLoss))), - .hx.target("#componentLossForm"), - .hx.swap(.outerHTML) - ) { - Icon(.circlePlus) - } - } - } - } - } - - private struct Rows: HTML, Sendable { - let componentPressureLosses: [ComponentPressureLoss] - - var body: some HTML { - for cpl in componentPressureLosses { - tr { - td(.class("border border-gray-200 p-2")) { cpl.name } - td(.class("border border-gray-200 p-2")) { "\(cpl.value)" } - td(.class("border border-gray-200 p-2")) { - // FIX: Add edit button - } - } - } - } - } -} diff --git a/Sources/ViewController/Views/FrictionRate/ComponentLossesView.swift b/Sources/ViewController/Views/FrictionRate/ComponentLossesView.swift new file mode 100644 index 0000000..13cc9b7 --- /dev/null +++ b/Sources/ViewController/Views/FrictionRate/ComponentLossesView.swift @@ -0,0 +1,50 @@ +import Elementary +import ElementaryHTMX +import ManualDCore +import Styleguide + +struct ComponentPressureLossesView: HTML, Sendable { + + let componentPressureLosses: [ComponentPressureLoss] + + private var total: Double { + componentPressureLosses.reduce(into: 0) { $0 += $1.value } + } + + var body: some HTML { + div( + .class( + """ + border border-gray-200 rounded-lg shadow-lg space-y-4 p-4 + """ + ) + ) { + Row { + h1(.class("text-2xl font-bold")) { "Component Pressure Losses" } + button( + .hx.get(route: .frictionRate(.form(.componentPressureLoss, dismiss: false))), + .hx.target("#componentLossForm"), + .hx.swap(.outerHTML) + ) { + Icon(.circlePlus) + } + } + + for row in componentPressureLosses { + Row { + Label { row.name } + Number(row.value) + } + .attributes(.class("border-b border-gray-200")) + } + + Row { + Label { "Total" } + Number(total) + .attributes(.class("text-xl font-bold")) + } + } + div(.id("componentLossForm")) {} + } + +} diff --git a/Sources/ViewController/Views/FrictionRate/EquipmentInfoView.swift b/Sources/ViewController/Views/FrictionRate/EquipmentInfoView.swift index 5b5631a..36acae0 100644 --- a/Sources/ViewController/Views/FrictionRate/EquipmentInfoView.swift +++ b/Sources/ViewController/Views/FrictionRate/EquipmentInfoView.swift @@ -13,19 +13,19 @@ struct EquipmentInfoView: HTML, Sendable { Row { Label { "Static Pressure" } - span { "\(equipmentInfo.staticPressure)" } + Number(equipmentInfo.staticPressure) } .attributes(.class("border-b border-gray-200")) Row { Label { "Heating CFM" } - span { "\(equipmentInfo.heatingCFM)" } + Number(equipmentInfo.heatingCFM) } .attributes(.class("border-b border-gray-200")) Row { Label { "Cooling CFM" } - span { "\(equipmentInfo.coolingCFM)" } + Number(equipmentInfo.coolingCFM) } .attributes(.class("border-b border-gray-200")) diff --git a/Sources/ViewController/Views/FrictionRate/FrictionRateView.swift b/Sources/ViewController/Views/FrictionRate/FrictionRateView.swift index 0e3fb49..7ec024e 100644 --- a/Sources/ViewController/Views/FrictionRate/FrictionRateView.swift +++ b/Sources/ViewController/Views/FrictionRate/FrictionRateView.swift @@ -5,10 +5,10 @@ import Styleguide struct FrictionRateView: HTML, Sendable { var body: some HTML { - div(.class("w-full flex-1 p-4 space-y-6")) { + div(.class("p-4 space-y-6")) { h1(.class("text-4xl font-bold pb-6")) { "Friction Rate" } EquipmentInfoView(equipmentInfo: EquipmentInfo.mock) - ComponentPressureLossTable(componentPressureLosses: ComponentPressureLoss.mock) + ComponentPressureLossesView(componentPressureLosses: ComponentPressureLoss.mock) } } } diff --git a/Sources/ViewController/Views/MainPage.swift b/Sources/ViewController/Views/MainPage.swift index 4f966e7..667a64d 100644 --- a/Sources/ViewController/Views/MainPage.swift +++ b/Sources/ViewController/Views/MainPage.swift @@ -20,10 +20,12 @@ public struct MainPage: SendableHTMLDocument where Inner: Sendable } public var body: some HTML { - div(.class("flex bg-white dark:bg-gray-800 dark:text-white")) { - Sidebar() - main { - inner + div(.class("bg-white dark:bg-gray-800 dark:text-white")) { + div(.class("flex flex-row")) { + Sidebar() + main(.class("flex flex-col h-screen w-full")) { + inner + } } } script(.src("https://unpkg.com/lucide@latest")) {} diff --git a/Sources/ViewController/Views/Project/ProjectView.swift b/Sources/ViewController/Views/Project/ProjectView.swift new file mode 100644 index 0000000..b2e20a6 --- /dev/null +++ b/Sources/ViewController/Views/Project/ProjectView.swift @@ -0,0 +1,54 @@ +import Elementary +import ElementaryHTMX +import ManualDCore +import Styleguide + +struct ProjectView: HTML, Sendable { + let project: Project + + var body: some HTML { + div( + .class( + """ + border border-gray-200 rounded-lg shadow-lg space-y-4 p-4 m-4 + """ + ) + ) { + Row { + h1(.class("text-2xl font-bold")) { "Project" } + // FIX: Add edit button. + } + + Row { + Label("Name") + span { project.name } + } + .attributes(.class("border-b border-gray-200")) + + Row { + Label("Address") + span { project.streetAddress } + } + .attributes(.class("border-b border-gray-200")) + + Row { + Label("City") + span { project.city } + } + .attributes(.class("border-b border-gray-200")) + + Row { + Label("State") + span { project.state } + } + .attributes(.class("border-b border-gray-200")) + + Row { + Label("Zip") + span { project.zipCode } + } + } + + div(.id("projectForm")) {} + } +} diff --git a/Sources/ViewController/Views/Sidebar.swift b/Sources/ViewController/Views/Sidebar.swift index 2fba872..f6bfcb9 100644 --- a/Sources/ViewController/Views/Sidebar.swift +++ b/Sources/ViewController/Views/Sidebar.swift @@ -8,7 +8,9 @@ struct Sidebar: HTML { aside( .class( """ - h-screen sticky top-0 min-w-[280px] flex-none border border-r-3 border-gray-800 bg-gray-100 shadow + h-screen sticky top-0 max-w-[280px] flex-none + border-r-2 border-gray-200 + shadow-lg """ ) ) { @@ -16,6 +18,7 @@ struct Sidebar: HTML { row(title: "Rooms", icon: .doorClosed, href: "/rooms") row(title: "Equivalent Lengths", icon: .rulerDimensionLine, href: "#") row(title: "Friction Rate", icon: .squareFunction, href: "/friction-rate") + .attributes(.data("active", value: "true")) row(title: "Duct Sizes", icon: .wind, href: "#") } } @@ -24,17 +27,20 @@ struct Sidebar: HTML { title: String, icon: Icon.Key, href: String - ) -> some HTML { + ) -> some HTML { a( .class( """ - flex w-full items-center gap-4 text-gray-800 hover:bg-gray-300 px-4 py-2 + flex w-full items-center gap-4 + hover:bg-gray-300 hover:text-gray-800 + data-[active=true]:bg-gray-300 data-[active=true]:text-gray-800 + px-4 py-2 """ ), .href(href) ) { Icon(icon) - span(.class("text-xl font-bold")) { + span(.class("text-xl")) { title } }