feat-WIP: Adds update to trunk-size form, currently height / width is not working though.

This commit is contained in:
2026-01-13 20:23:25 -05:00
parent f990c4b6db
commit dfee50de8e
7 changed files with 264 additions and 83 deletions

View File

@@ -5231,6 +5231,9 @@
.m-1 {
margin: calc(var(--spacing) * 1);
}
.m-4 {
margin: calc(var(--spacing) * 4);
}
.m-6 {
margin: calc(var(--spacing) * 6);
}
@@ -5374,6 +5377,9 @@
.-my-2 {
margin-block: calc(var(--spacing) * -2);
}
.-my-4 {
margin-block: calc(var(--spacing) * -4);
}
.my-1 {
margin-block: calc(var(--spacing) * 1);
}
@@ -5646,6 +5652,9 @@
.mt-1 {
margin-top: calc(var(--spacing) * 1);
}
.mt-2 {
margin-top: calc(var(--spacing) * 2);
}
.mt-4 {
margin-top: calc(var(--spacing) * 4);
}
@@ -6604,9 +6613,6 @@
.max-w-1\/3 {
max-width: calc(1/3 * 100%);
}
.max-w-\[300px\] {
max-width: 300px;
}
.flex-1 {
flex: 1;
}
@@ -6812,6 +6818,9 @@
.flex-col {
flex-direction: column;
}
.flex-row {
flex-direction: row;
}
.flex-wrap {
flex-wrap: wrap;
}
@@ -7905,6 +7914,12 @@
.py-2 {
padding-block: calc(var(--spacing) * 2);
}
.py-4 {
padding-block: calc(var(--spacing) * 4);
}
.py-6 {
padding-block: calc(var(--spacing) * 6);
}
.ps-2 {
padding-inline-start: calc(var(--spacing) * 2);
}
@@ -7982,6 +7997,14 @@
font-size: var(--text-3xl);
line-height: var(--tw-leading, var(--text-3xl--line-height));
}
.text-4xl {
font-size: var(--text-4xl);
line-height: var(--tw-leading, var(--text-4xl--line-height));
}
.text-base {
font-size: var(--text-base);
line-height: var(--tw-leading, var(--text-base--line-height));
}
.text-lg {
font-size: var(--text-lg);
line-height: var(--tw-leading, var(--text-lg--line-height));

View File

@@ -11,6 +11,9 @@ extension DatabaseClient {
public var delete: @Sendable (DuctSizing.TrunkSize.ID) async throws -> Void
public var fetch: @Sendable (Project.ID) async throws -> [DuctSizing.TrunkSize]
public var get: @Sendable (DuctSizing.TrunkSize.ID) async throws -> DuctSizing.TrunkSize?
public var update:
@Sendable (DuctSizing.TrunkSize.ID, DuctSizing.TrunkSize.Update) async throws ->
DuctSizing.TrunkSize
}
}
@@ -82,6 +85,21 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
return nil
}
return try await model.toDTO(on: database)
},
update: { id, updates in
guard
let model =
try await TrunkModel
.query(on: database)
.with(\.$rooms)
.filter(\.$id == id)
.first()
else {
throw NotFoundError()
}
try updates.validate()
try await model.applyUpdates(updates, on: database)
return try await model.toDTO(on: database)
}
)
}
@@ -107,7 +125,21 @@ extension DuctSizing.TrunkSize.Create {
height: height
)
}
}
extension DuctSizing.TrunkSize.Update {
func validate() throws(ValidationError) {
if let rooms {
guard rooms.count > 0 else {
throw ValidationError("Trunk size should have associated rooms / registers.")
}
}
if let height {
guard height > 0 else {
throw ValidationError("Trunk size height should be greater than 0.")
}
}
}
}
extension DuctSizing.TrunkSize {
@@ -250,4 +282,62 @@ final class TrunkModel: Model, @unchecked Sendable {
)
}
func applyUpdates(
_ updates: DuctSizing.TrunkSize.Update,
on database: any Database
) async throws {
if let type = updates.type, type.rawValue != self.type {
self.type = type.rawValue
}
if let height = updates.height, height != self.height {
self.height = height
}
if hasChanges {
try await self.save(on: database)
}
guard let updateRooms = updates.rooms else {
return
}
// Update rooms.
let rooms = try await TrunkRoomModel.query(on: database)
.with(\.$room)
.filter(\.$trunk.$id == requireID())
.all()
for (roomID, registers) in updateRooms {
if let currRoom = rooms.first(where: { $0.$room.id == roomID }) {
database.logger.debug("CURRENT ROOM: \(currRoom.room.name)")
if registers != currRoom.registers {
database.logger.debug("Updating registers for: \(currRoom.room.name)")
currRoom.registers = registers
}
if currRoom.hasChanges {
try await currRoom.save(on: database)
}
} else {
database.logger.debug("CREATING NEW TrunkRoomModel")
let newModel = try TrunkRoomModel(
trunkID: requireID(),
roomID: roomID,
registers: registers,
type: .init(rawValue: type)!
)
try await newModel.save(on: database)
}
}
let roomsToDelete = rooms.filter {
!updateRooms.keys.contains($0.$room.id)
}
for room in roomsToDelete {
try await room.delete(on: database)
}
database.logger.debug("DONE WITH UPDATES")
}
}

View File

@@ -126,6 +126,8 @@ public enum DuctSizing {
extension DuctSizing {
// Represents the database model that the duct sizes have been calculated
// for.
public struct TrunkContainer: Codable, Equatable, Identifiable, Sendable {
public var id: TrunkSize.ID { trunk.id }
@@ -141,6 +143,7 @@ extension DuctSizing {
}
}
// Represents the database model.
public struct TrunkSize: Codable, Equatable, Identifiable, Sendable {
public let id: UUID
@@ -187,6 +190,23 @@ extension DuctSizing.TrunkSize {
}
}
public struct Update: Codable, Equatable, Sendable {
public let type: TrunkType?
public let rooms: [Room.ID: [Int]]?
public let height: Int?
public init(
type: DuctSizing.TrunkSize.TrunkType? = nil,
rooms: [Room.ID: [Int]]? = nil,
height: Int? = nil
) {
self.type = type
self.rooms = rooms
self.height = height
}
}
// TODO: Make registers non-optional
public struct RoomProxy: Codable, Equatable, Identifiable, Sendable {

View File

@@ -13,6 +13,14 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute.TrunkSizeForm {
)
}
func toUpdate(logger: Logger? = nil) throws -> DuctSizing.TrunkSize.Update {
try .init(
type: type,
rooms: makeRooms(logger: logger),
height: height
)
}
func makeRooms(logger: Logger?) throws -> [Room.ID: [Int]] {
var retval = [Room.ID: [Int]]()
for room in rooms {

View File

@@ -581,8 +581,9 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute {
}
case .update(let id, let form):
// FIX:
fatalError()
return await view(on: request, projectID: projectID) {
_ = try await database.trunkSizes.update(id, form.toUpdate())
}
}
}
}

View File

@@ -176,6 +176,7 @@ struct DuctSizingView: HTML, Sendable {
}
struct TrunkTable: HTML, Sendable {
let trunks: [DuctSizing.TrunkContainer]
let rooms: [DuctSizing.RoomContainer]
@@ -196,86 +197,89 @@ struct DuctSizingView: HTML, Sendable {
}
tbody {
for trunk in trunks {
tr {
td(.class("space-x-2")) {
// div(.class("flex flex-wrap space-x-2 max-w-1/3")) {
for id in registerIDS(trunk.trunk) {
Badge { id }
}
// }
}
td {
Number(trunk.ductSize.designCFM.value, digits: 0)
}
td {
Number(trunk.ductSize.roundSize, digits: 1)
}
td {
Number(trunk.ductSize.velocity)
}
td {
Badge(number: trunk.ductSize.finalSize)
.attributes(.class("badge-secondary"))
}
td {
Badge(number: trunk.ductSize.flexSize)
.attributes(.class("badge-primary"))
}
td {
if let width = trunk.ductSize.width {
Number(width)
}
}
td {
div(.class("flex justify-between items-center space-x-4")) {
div {
if let height = trunk.ductSize.height {
Number(height)
}
}
div {
div(.class("join")) {
TrashButton()
.attributes(.class("join-item btn-ghost"))
.attributes(
// .hx.delete(
// route: .project(
// .detail(
// projectID,
// .ductSizing(
// .deleteRectangularSize(
// room.roomID,
// room.rectangularSize?.id ?? .init())
// )
// )
// )
// ),
.hx.target("closest tr"),
.hx.swap(.outerHTML)
// when: room.rectangularSize != nil
)
EditButton()
.attributes(
.class("join-item btn-ghost"),
// .showModal(id: RectangularSizeForm.id(room))
)
}
}
}
// FIX: Add Trunk form.
}
}
TrunkRow(trunk: trunk, rooms: rooms)
}
}
}
}
}
func registerIDS(_ trunk: DuctSizing.TrunkSize) -> [String] {
}
struct TrunkRow: HTML, Sendable {
@Environment(ProjectViewValue.$projectID) var projectID
let trunk: DuctSizing.TrunkContainer
let rooms: [DuctSizing.RoomContainer]
var body: some HTML<HTMLTag.tr> {
tr {
td(.class("space-x-2")) {
for id in registerIDS(trunk.trunk) {
Badge { id }
}
}
td {
Number(trunk.ductSize.designCFM.value, digits: 0)
}
td {
Number(trunk.ductSize.roundSize, digits: 1)
}
td {
Number(trunk.ductSize.velocity)
}
td {
Badge(number: trunk.ductSize.finalSize)
.attributes(.class("badge-secondary"))
}
td {
Badge(number: trunk.ductSize.flexSize)
.attributes(.class("badge-primary"))
}
td {
if let width = trunk.ductSize.width {
Number(width)
}
}
td {
div(.class("flex justify-between items-center space-x-4")) {
div {
if let height = trunk.ductSize.height {
Number(height)
}
}
div {
div(.class("join")) {
TrashButton()
.attributes(.class("join-item btn-ghost"))
.attributes(
.hx.delete(route: deleteRoute),
.hx.target("closest tr"),
.hx.swap(.outerHTML)
)
EditButton()
.attributes(
.class("join-item btn-ghost"),
.showModal(id: TrunkSizeForm.id(trunk))
)
}
}
}
TrunkSizeForm(trunk: trunk, rooms: rooms, dismiss: true)
}
}
}
private var deleteRoute: SiteRoute.View {
.project(.detail(projectID, .ductSizing(.trunk(.delete(trunk.id)))))
}
private func registerIDS(_ trunk: DuctSizing.TrunkSize) -> [String] {
trunk.rooms.reduce(into: []) { array, room in
array = room.registers.reduce(into: array) { array, register in
if let room =

View File

@@ -5,27 +5,47 @@ import Styleguide
struct TrunkSizeForm: HTML, Sendable {
static func id() -> String {
"trunkSizeForm"
static func id(_ trunk: DuctSizing.TrunkContainer? = nil) -> String {
let base = "trunkSizeForm"
guard let trunk else { return base }
return "\(base)_\(trunk.id.idString)"
}
@Environment(ProjectViewValue.$projectID) var projectID
let container: DuctSizing.TrunkContainer?
let rooms: [DuctSizing.RoomContainer]
let dismiss: Bool
var trunk: DuctSizing.TrunkSize? {
container?.trunk
}
init(
trunk: DuctSizing.TrunkContainer? = nil,
rooms: [DuctSizing.RoomContainer],
dismiss: Bool = true
) {
self.container = trunk
self.rooms = rooms
self.dismiss = dismiss
}
var route: String {
SiteRoute.View.router
.path(for: .project(.detail(projectID, .ductSizing(.index))))
.appendingPath(SiteRoute.View.ProjectRoute.DuctSizingRoute.TrunkRoute.rootPath)
.appendingPath(trunk?.id)
}
var body: some HTML {
ModalForm(id: Self.id(), dismiss: dismiss) {
ModalForm(id: Self.id(container), dismiss: dismiss) {
h1(.class("text-lg font-bold mb-4")) { "Trunk Size" }
form(
.class("space-y-4"),
.hx.post(route),
trunk == nil
? .hx.post(route)
: .hx.patch(route),
.hx.target("body"),
.hx.swap(.outerHTML)
) {
@@ -38,6 +58,7 @@ struct TrunkSizeForm: HTML, Sendable {
select(.name("type")) {
for type in DuctSizing.TrunkSize.TrunkType.allCases {
option(.value(type.rawValue)) { type.rawValue.capitalized }
.attributes(.selected, when: trunk?.type == type)
}
}
}
@@ -46,6 +67,7 @@ struct TrunkSizeForm: HTML, Sendable {
"Height",
.type(.text),
.name("height"),
.value(trunk?.height),
.placeholder("8 (Optional)"),
)
}
@@ -63,6 +85,10 @@ struct TrunkSizeForm: HTML, Sendable {
.name("rooms"),
.value("\(room.roomID)_\(room.roomRegister)")
)
.attributes(
.checked,
when: trunk == nil ? false : trunk!.rooms.hasRoom(room)
)
}
}
}
@@ -75,3 +101,12 @@ struct TrunkSizeForm: HTML, Sendable {
}
}
extension Array where Element == DuctSizing.TrunkSize.RoomProxy {
func hasRoom(_ room: DuctSizing.RoomContainer) -> Bool {
first {
$0.id == room.roomID
&& $0.registers.contains(room.roomRegister)
} != nil
}
}