From 450791b37e0a5b568d2a6eeaba1b09f4afd3561e Mon Sep 17 00:00:00 2001 From: Michael Housh Date: Wed, 14 Jan 2026 10:32:57 -0500 Subject: [PATCH] fix: Fixes duct sizing rooms table not showing forms correctly, updates the table styles. --- Sources/DatabaseClient/Rooms.swift | 15 ++ Sources/ManualDCore/Routes/ViewRoute.swift | 15 +- ...dingPath.swift => String+extensions.swift} | 4 + .../Extensions/UUID+idString.swift | 2 +- Sources/ViewController/Live.swift | 24 ++-- .../Views/DuctSizing/DuctSizingView.swift | 48 +++---- .../DuctSizing/RectangularSizeForm.swift | 62 +++------ .../Views/DuctSizing/RoomsTable.swift | 131 ++++++++++-------- 8 files changed, 158 insertions(+), 143 deletions(-) rename Sources/ViewController/Extensions/{String+appendingPath.swift => String+extensions.swift} (88%) diff --git a/Sources/DatabaseClient/Rooms.swift b/Sources/DatabaseClient/Rooms.swift index 5365b11..2d497ba 100644 --- a/Sources/DatabaseClient/Rooms.swift +++ b/Sources/DatabaseClient/Rooms.swift @@ -14,6 +14,8 @@ extension DatabaseClient { public var get: @Sendable (Room.ID) async throws -> Room? public var fetch: @Sendable (Project.ID) async throws -> [Room] public var update: @Sendable (Room.ID, Room.Update) async throws -> Room + public var updateRectangularSize: + @Sendable (Room.ID, DuctSizing.RectangularDuct) async throws -> Room } } @@ -67,6 +69,19 @@ extension DatabaseClient.Rooms: TestDependencyKey { try await model.save(on: database) } return try model.toDTO() + }, + updateRectangularSize: { id, size in + guard let model = try await RoomModel.find(id, on: database) else { + throw NotFoundError() + } + var rectangularSizes = model.rectangularSizes ?? [] + rectangularSizes.removeAll { + $0.id == size.id + } + rectangularSizes.append(size) + model.rectangularSizes = rectangularSizes + try await model.save(on: database) + return try model.toDTO() } ) } diff --git a/Sources/ManualDCore/Routes/ViewRoute.swift b/Sources/ManualDCore/Routes/ViewRoute.swift index 1117ddf..3ccfe5a 100644 --- a/Sources/ManualDCore/Routes/ViewRoute.swift +++ b/Sources/ManualDCore/Routes/ViewRoute.swift @@ -607,7 +607,7 @@ extension SiteRoute.View.ProjectRoute { public enum DuctSizingRoute: Equatable, Sendable { case index - case deleteRectangularSize(Room.ID, DuctSizing.RectangularDuct.ID) + case deleteRectangularSize(Room.ID, DeleteRectangularDuct) case roomRectangularForm(Room.ID, RoomRectangularForm) case trunk(TrunkRoute) @@ -628,7 +628,9 @@ extension SiteRoute.View.ProjectRoute { Method.delete Query { Field("rectangularSize") { DuctSizing.RectangularDuct.ID.parser() } + Field("register") { Int.parser() } } + .map(.memberwise(DeleteRectangularDuct.init)) } Route(.case(Self.roomRectangularForm)) { Path { @@ -654,6 +656,17 @@ extension SiteRoute.View.ProjectRoute { } } + public struct DeleteRectangularDuct: Equatable, Sendable { + + public let rectangularSizeID: DuctSizing.RectangularDuct.ID + public let register: Int + + public init(rectangularSizeID: DuctSizing.RectangularDuct.ID, register: Int) { + self.rectangularSizeID = rectangularSizeID + self.register = register + } + } + public enum TrunkRoute: Equatable, Sendable { case delete(DuctSizing.TrunkSize.ID) case submit(TrunkSizeForm) diff --git a/Sources/ViewController/Extensions/String+appendingPath.swift b/Sources/ViewController/Extensions/String+extensions.swift similarity index 88% rename from Sources/ViewController/Extensions/String+appendingPath.swift rename to Sources/ViewController/Extensions/String+extensions.swift index 882ee6c..304e2e5 100644 --- a/Sources/ViewController/Extensions/String+appendingPath.swift +++ b/Sources/ViewController/Extensions/String+extensions.swift @@ -17,4 +17,8 @@ extension String { func appendingPath(_ id: UUID) -> Self { return appendingPath(id.uuidString) } + + var idString: Self { + replacing("-", with: "") + } } diff --git a/Sources/ViewController/Extensions/UUID+idString.swift b/Sources/ViewController/Extensions/UUID+idString.swift index 759974f..4f34fd9 100644 --- a/Sources/ViewController/Extensions/UUID+idString.swift +++ b/Sources/ViewController/Extensions/UUID+idString.swift @@ -2,6 +2,6 @@ import Foundation extension UUID { var idString: String { - uuidString.replacing("-", with: "") + uuidString.idString } } diff --git a/Sources/ViewController/Live.swift b/Sources/ViewController/Live.swift index 2214300..5d658c5 100644 --- a/Sources/ViewController/Live.swift +++ b/Sources/ViewController/Live.swift @@ -538,33 +538,29 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute { case .index: return await view(on: request, projectID: projectID) - case .deleteRectangularSize(let roomID, let rectangularSizeID): + case .deleteRectangularSize(let roomID, let request): return await ResultView { - let room = try await database.rooms.deleteRectangularSize(roomID, rectangularSizeID) + let room = try await database.rooms.deleteRectangularSize(roomID, request.rectangularSizeID) return try await database.calculateDuctSizes(projectID: projectID) .rooms - .filter({ $0.roomID == room.id }) + .filter({ $0.roomID == room.id && $0.roomRegister == request.register }) .first! - } onSuccess: { container in - DuctSizingView.RoomRow(room: container) + } onSuccess: { room in + DuctSizingView.RoomRow(room: room) } case .roomRectangularForm(let roomID, let form): return await ResultView { - let room = try await database.rooms.update( + let room = try await database.rooms.updateRectangularSize( roomID, - .init( - rectangularSizes: [ - .init(id: form.id ?? .init(), register: form.register, height: form.height) - ] - ) + .init(id: form.id ?? .init(), register: form.register, height: form.height) ) return try await database.calculateDuctSizes(projectID: projectID) .rooms - .filter({ $0.roomID == room.id }) + .filter({ $0.roomID == room.id && $0.roomRegister == form.register }) .first! - } onSuccess: { container in - DuctSizingView.RoomRow(room: container) + } onSuccess: { room in + DuctSizingView.RoomRow(room: room) } case .trunk(let route): diff --git a/Sources/ViewController/Views/DuctSizing/DuctSizingView.swift b/Sources/ViewController/Views/DuctSizing/DuctSizingView.swift index 95b1ab7..8fcaaed 100644 --- a/Sources/ViewController/Views/DuctSizing/DuctSizingView.swift +++ b/Sources/ViewController/Views/DuctSizing/DuctSizingView.swift @@ -31,30 +31,30 @@ struct DuctSizingView: HTML, Sendable { RoomsTable(rooms: rooms) } - // Row { - // h2(.class("text-2xl font-bold")) { "Trunk Sizes" } - // - // PlusButton() - // .attributes( - // .class("me-6"), - // .showModal(id: TrunkSizeForm.id()) - // ) - // } - // .attributes(.class("mt-6")) - // - // div(.class("divider -mt-2")) {} - // - // if supplyTrunks.count > 0 { - // h2(.class("text-lg font-bold text-info")) { "Supply Trunks" } - // TrunkTable(trunks: supplyTrunks, rooms: rooms) - // } - // - // if returnTrunks.count > 0 { - // h2(.class("text-lg font-bold text-error")) { "Return Trunks" } - // TrunkTable(trunks: returnTrunks, rooms: rooms) - // } - // - // TrunkSizeForm(rooms: rooms, dismiss: true) + Row { + h2(.class("text-2xl font-bold")) { "Trunk Sizes" } + + PlusButton() + .attributes( + .class("me-6"), + .showModal(id: TrunkSizeForm.id()) + ) + } + .attributes(.class("mt-6")) + + div(.class("divider -mt-2")) {} + + if supplyTrunks.count > 0 { + h2(.class("text-lg font-bold text-info")) { "Supply Trunks" } + TrunkTable(trunks: supplyTrunks, rooms: rooms) + } + + if returnTrunks.count > 0 { + h2(.class("text-lg font-bold text-error")) { "Return Trunks" } + TrunkTable(trunks: returnTrunks, rooms: rooms) + } + + TrunkSizeForm(rooms: rooms, dismiss: true) } } diff --git a/Sources/ViewController/Views/DuctSizing/RectangularSizeForm.swift b/Sources/ViewController/Views/DuctSizing/RectangularSizeForm.swift index 7b1e36e..64c9a07 100644 --- a/Sources/ViewController/Views/DuctSizing/RectangularSizeForm.swift +++ b/Sources/ViewController/Views/DuctSizing/RectangularSizeForm.swift @@ -5,58 +5,25 @@ import Styleguide struct RectangularSizeForm: HTML, Sendable { - static func id(_ roomID: Room.ID? = nil) -> String { - let base = "rectangularSize" - guard let roomID else { return base } - return "\(base)_\(roomID.idString)" - } - static func id(_ room: DuctSizing.RoomContainer) -> String { - return id(room.roomID) + let base = "rectangularSize" + return "\(base)_\(room.registerID.idString)" } @Environment(ProjectViewValue.$projectID) var projectID let id: String - let roomID: Room.ID - let rectangularSizeID: DuctSizing.RectangularDuct.ID? - let register: Int - let height: Int? + let room: DuctSizing.RoomContainer let dismiss: Bool - init( - id: String? = nil, - roomID: Room.ID, - rectangularSizeID: DuctSizing.RectangularDuct.ID? = nil, - register: Int, - height: Int? = nil, - dismiss: Bool = true - ) { - self.id = id ?? Self.id(roomID) - self.roomID = roomID - self.rectangularSizeID = rectangularSizeID - self.register = register - self.height = height - self.dismiss = dismiss - } - init( id: String? = nil, room: DuctSizing.RoomContainer, dismiss: Bool = true ) { - let register = - room.rectangularSize?.register - ?? (Int("\(room.roomName.last!)") ?? 1) - - self.init( - id: id, - roomID: room.roomID, - rectangularSizeID: room.rectangularSize?.id, - register: register, - height: room.rectangularSize?.height, - dismiss: dismiss - ) + self.id = Self.id(room) + self.room = room + self.dismiss = dismiss } var route: String { @@ -64,23 +31,30 @@ struct RectangularSizeForm: HTML, Sendable { for: .project(.detail(projectID, .ductSizing(.index))) ) .appendingPath("room") - .appendingPath(roomID) + .appendingPath(room.roomID) } + var rowID: String { + DuctSizingView.RoomRow.id(room) + } + + var height: Int? { + room.rectangularSize?.height + } + var body: some HTML { ModalForm(id: id, dismiss: dismiss) { - h1(.class("text-lg pb-6")) { "Rectangular Size" } form( .class("space-y-4"), .hx.post(route), - .hx.target("closest tr"), + .hx.target("#\(rowID)"), .hx.swap(.outerHTML) ) { - input(.class("hidden"), .name("register"), .value(register)) - input(.class("hidden"), .name("id"), .value(rectangularSizeID)) + input(.class("hidden"), .name("register"), .value(room.roomRegister)) + input(.class("hidden"), .name("id"), .value(room.rectangularSize?.id)) LabeledInput( "Height", diff --git a/Sources/ViewController/Views/DuctSizing/RoomsTable.swift b/Sources/ViewController/Views/DuctSizing/RoomsTable.swift index cf6a3f5..4123d18 100644 --- a/Sources/ViewController/Views/DuctSizing/RoomsTable.swift +++ b/Sources/ViewController/Views/DuctSizing/RoomsTable.swift @@ -21,10 +21,8 @@ extension DuctSizingView { th { "Name" } th { "BTU" } th { "CFM" } - th(.class("hidden 2xl:table-cell")) { "Round Size" } th { "Velocity" } th { "Size" } - th {} } } tbody { @@ -39,6 +37,10 @@ extension DuctSizingView { struct RoomRow: HTML, Sendable { + static func id(_ room: DuctSizing.RoomContainer) -> String { + "roomRow_\(room.registerID.idString)" + } + @Environment(ProjectViewValue.$projectID) var projectID let room: DuctSizing.RoomContainer @@ -51,14 +53,20 @@ extension DuctSizingView { for: .project( .detail( projectID, - .ductSizing(.deleteRectangularSize(room.roomID, id)) + .ductSizing( + .deleteRectangularSize( + room.roomID, + .init(rectangularSizeID: id, register: room.roomRegister) + )) ) ) ) } + var rowID: String { Self.id(room) } + var body: some HTML { - tr(.class("text-lg items-baseline"), .id(room.roomID.idString)) { + tr(.class("text-lg items-baseline"), .id(rowID)) { td { room.registerID } td { room.roomName } td { @@ -75,84 +83,89 @@ extension DuctSizingView { div(.class("grid grid-cols-2 gap-2")) { span(.class("label")) { "Design" } - Badge(number: room.designCFM.value, digits: 0) + div(.class("flex justify-center")) { + Badge(number: room.designCFM.value, digits: 0) + } span(.class("label")) { "Heating" } - Number(room.heatingCFM, digits: 0) + div(.class("flex justify-center")) { + Number(room.heatingCFM, digits: 0) + } span(.class("label")) { "Cooling" } - Number(room.coolingCFM, digits: 0) + div(.class("flex justify-center")) { + Number(room.coolingCFM, digits: 0) + } } } - td(.class("hidden 2xl:table-cell")) { Number(room.roundSize, digits: 1) } td { Number(room.velocity) } td { - div(.class("grid grid-cols-2 gap-2")) { + div(.class("grid grid-cols-3 gap-2")) { - span(.class("label")) { "Final" } - Badge(number: room.finalSize) - .attributes(.class("badge-secondary")) + div(.class("label")) { "Calculated" } + div(.class("flex justify-center")) { + Badge(number: room.roundSize, digits: 1) + } + div {} - span(.class("label")) { "Flex" } - Badge(number: room.flexSize) - .attributes(.class("badge-primary")) + div(.class("label")) { "Final" } + div(.class("flex justify-center")) { + Badge(number: room.finalSize) + .attributes(.class("badge-secondary")) + } + div {} - if let width = room.rectangularWidth, - let height = room.rectangularSize?.height - { - span(.class("label")) { "Rectangular" } - Badge { - span { "\(width) x \(height)" } + div(.class("label")) { "Flex" } + div(.class("flex justify-center")) { + Badge(number: room.flexSize) + .attributes(.class("badge-primary")) + } + div {} + + div(.class("label")) { "Rectangular" } + div(.class("flex justify-center")) { + if let width = room.rectangularWidth, + let height = room.rectangularSize?.height + { + Badge { + span { "\(width) x \(height)" } + } + .attributes(.class("badge-info")) } } - } - } + div(.class("flex justify-end")) { + div(.class("join")) { + if room.rectangularSize != nil { + Tooltip("Delete Size", position: .bottom) { + TrashButton() + .attributes(.class("join-item btn-ghost")) + .attributes( + .hx.delete(deleteRoute), + .hx.target("#\(rowID)"), + .hx.swap(.outerHTML), + when: room.rectangularSize != nil + ) + } + } + + Tooltip("Edit Size", position: .bottom) { + EditButton() + .attributes( + .class("join-item btn-ghost"), + .showModal(id: RectangularSizeForm.id(room)) + ) + } - td { - div(.class("flex justify-end space-x-4")) { - div(.class("join")) { - if room.rectangularSize != nil { - // FIX: Delete rectangular size from room. - TrashButton() - .attributes(.class("join-item btn-ghost")) - .attributes( - .hx.delete(deleteRoute), - .hx.target("closest tr"), - .hx.swap(.outerHTML), - when: room.rectangularSize != nil - ) } - - EditButton() - .attributes( - .class("join-item btn-ghost"), - .showModal(id: formID) - // .showModal(id: RectangularSizeForm.id(room)) - ) - } + RectangularSizeForm(room: room) } - - // FakeForm(id: formID) - RectangularSizeForm(id: formID, room: room) - // .attributes(.class("modal-open")) - } } } } - struct FakeForm: HTML, Sendable { - let id: String - - var body: some HTML { - ModalForm(id: id, dismiss: true) { - div { "Fake Form" } - } - } - } - }