WIP: Begin cleaning up duct sizing routes.
This commit is contained in:
@@ -4801,9 +4801,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.col-span-1 {
|
||||||
|
grid-column: span 1 / span 1;
|
||||||
|
}
|
||||||
.col-span-2 {
|
.col-span-2 {
|
||||||
grid-column: span 2 / span 2;
|
grid-column: span 2 / span 2;
|
||||||
}
|
}
|
||||||
|
.col-span-5 {
|
||||||
|
grid-column: span 5 / span 5;
|
||||||
|
}
|
||||||
.timeline-end {
|
.timeline-end {
|
||||||
@layer daisyui.l1.l2.l3 {
|
@layer daisyui.l1.l2.l3 {
|
||||||
grid-column-start: 1;
|
grid-column-start: 1;
|
||||||
@@ -5368,6 +5374,9 @@
|
|||||||
.-my-2 {
|
.-my-2 {
|
||||||
margin-block: calc(var(--spacing) * -2);
|
margin-block: calc(var(--spacing) * -2);
|
||||||
}
|
}
|
||||||
|
.my-1 {
|
||||||
|
margin-block: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.my-1\.5 {
|
.my-1\.5 {
|
||||||
margin-block: calc(var(--spacing) * 1.5);
|
margin-block: calc(var(--spacing) * 1.5);
|
||||||
}
|
}
|
||||||
@@ -5586,6 +5595,9 @@
|
|||||||
.me-4 {
|
.me-4 {
|
||||||
margin-inline-end: calc(var(--spacing) * 4);
|
margin-inline-end: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
|
.me-6 {
|
||||||
|
margin-inline-end: calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
.me-8 {
|
.me-8 {
|
||||||
margin-inline-end: calc(var(--spacing) * 8);
|
margin-inline-end: calc(var(--spacing) * 8);
|
||||||
}
|
}
|
||||||
@@ -5628,6 +5640,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.-mt-2 {
|
||||||
|
margin-top: calc(var(--spacing) * -2);
|
||||||
|
}
|
||||||
|
.mt-1 {
|
||||||
|
margin-top: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.mt-4 {
|
.mt-4 {
|
||||||
margin-top: calc(var(--spacing) * 4);
|
margin-top: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
@@ -6580,6 +6598,12 @@
|
|||||||
.w-full {
|
.w-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.max-w-1 {
|
||||||
|
max-width: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
|
.max-w-1\/3 {
|
||||||
|
max-width: calc(1/3 * 100%);
|
||||||
|
}
|
||||||
.max-w-\[300px\] {
|
.max-w-\[300px\] {
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
@@ -6824,6 +6848,23 @@
|
|||||||
.gap-4 {
|
.gap-4 {
|
||||||
gap: calc(var(--spacing) * 4);
|
gap: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
|
.gap-6 {
|
||||||
|
gap: calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
|
.space-y-1 {
|
||||||
|
:where(& > :not(:last-child)) {
|
||||||
|
--tw-space-y-reverse: 0;
|
||||||
|
margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));
|
||||||
|
margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.space-y-2 {
|
||||||
|
:where(& > :not(:last-child)) {
|
||||||
|
--tw-space-y-reverse: 0;
|
||||||
|
margin-block-start: calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));
|
||||||
|
margin-block-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)));
|
||||||
|
}
|
||||||
|
}
|
||||||
.space-y-4 {
|
.space-y-4 {
|
||||||
:where(& > :not(:last-child)) {
|
:where(& > :not(:last-child)) {
|
||||||
--tw-space-y-reverse: 0;
|
--tw-space-y-reverse: 0;
|
||||||
@@ -6838,6 +6879,9 @@
|
|||||||
margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
|
margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.gap-x-4 {
|
||||||
|
column-gap: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
.space-x-2 {
|
.space-x-2 {
|
||||||
:where(& > :not(:last-child)) {
|
:where(& > :not(:last-child)) {
|
||||||
--tw-space-x-reverse: 0;
|
--tw-space-x-reverse: 0;
|
||||||
@@ -6859,6 +6903,9 @@
|
|||||||
margin-inline-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)));
|
margin-inline-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-x-reverse)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.gap-y-6 {
|
||||||
|
row-gap: calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
.overflow-x-auto {
|
.overflow-x-auto {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
@@ -7840,12 +7887,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.px-2 {
|
||||||
|
padding-inline: calc(var(--spacing) * 2);
|
||||||
|
}
|
||||||
.px-3 {
|
.px-3 {
|
||||||
padding-inline: calc(var(--spacing) * 3);
|
padding-inline: calc(var(--spacing) * 3);
|
||||||
}
|
}
|
||||||
.px-4 {
|
.px-4 {
|
||||||
padding-inline: calc(var(--spacing) * 4);
|
padding-inline: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
|
.py-1 {
|
||||||
|
padding-block: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.py-1\.5 {
|
.py-1\.5 {
|
||||||
padding-block: calc(var(--spacing) * 1.5);
|
padding-block: calc(var(--spacing) * 1.5);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,10 +34,11 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
|||||||
let model = try TrunkRoomModel(
|
let model = try TrunkRoomModel(
|
||||||
trunkID: trunk.requireID(),
|
trunkID: trunk.requireID(),
|
||||||
roomID: room.requireID(),
|
roomID: room.requireID(),
|
||||||
registers: registers
|
registers: registers,
|
||||||
|
type: request.type
|
||||||
)
|
)
|
||||||
try await model.save(on: database)
|
try await model.save(on: database)
|
||||||
try roomProxies.append(model.toDTO())
|
try await roomProxies.append(model.toDTO(on: database))
|
||||||
}
|
}
|
||||||
|
|
||||||
return try .init(
|
return try .init(
|
||||||
@@ -54,16 +55,33 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
|||||||
try await model.delete(on: database)
|
try await model.delete(on: database)
|
||||||
},
|
},
|
||||||
fetch: { projectID in
|
fetch: { projectID in
|
||||||
try await TrunkModel.query(on: database)
|
let models = try await TrunkModel.query(on: database)
|
||||||
.with(\.$rooms)
|
|
||||||
.with(\.$project)
|
.with(\.$project)
|
||||||
|
.with(\.$rooms)
|
||||||
.filter(\.$project.$id == projectID)
|
.filter(\.$project.$id == projectID)
|
||||||
.all()
|
.all()
|
||||||
.map { try $0.toDTO() }
|
|
||||||
|
return try await withThrowingTaskGroup(of: DuctSizing.TrunkSize.self) { group in
|
||||||
|
for model in models {
|
||||||
|
group.addTask {
|
||||||
|
try await model.toDTO(on: database)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return try await group.reduce(into: [DuctSizing.TrunkSize]()) {
|
||||||
|
$0.append($1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return try await models.map {
|
||||||
|
// try await $0.toDTO(on: database)
|
||||||
|
// }
|
||||||
},
|
},
|
||||||
get: { id in
|
get: { id in
|
||||||
try await TrunkModel.find(id, on: database)
|
guard let model = try await TrunkModel.find(id, on: database) else {
|
||||||
.map { try $0.toDTO() }
|
return nil
|
||||||
|
}
|
||||||
|
return try await model.toDTO(on: database)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -110,12 +128,14 @@ extension DuctSizing.TrunkSize {
|
|||||||
try await database.schema(TrunkRoomModel.schema)
|
try await database.schema(TrunkRoomModel.schema)
|
||||||
.id()
|
.id()
|
||||||
.field("registers", .array(of: .int), .required)
|
.field("registers", .array(of: .int), .required)
|
||||||
|
.field("type", .string, .required)
|
||||||
.field(
|
.field(
|
||||||
"trunkID", .uuid, .required, .references(TrunkModel.schema, "id", onDelete: .cascade)
|
"trunkID", .uuid, .required, .references(TrunkModel.schema, "id", onDelete: .cascade)
|
||||||
)
|
)
|
||||||
.field(
|
.field(
|
||||||
"roomID", .uuid, .required, .references(RoomModel.schema, "id", onDelete: .cascade)
|
"roomID", .uuid, .required, .references(RoomModel.schema, "id", onDelete: .cascade)
|
||||||
)
|
)
|
||||||
|
.unique(on: "trunkID", "roomID", "type")
|
||||||
.create()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,22 +163,30 @@ final class TrunkRoomModel: Model, @unchecked Sendable {
|
|||||||
@Field(key: "registers")
|
@Field(key: "registers")
|
||||||
var registers: [Int]
|
var registers: [Int]
|
||||||
|
|
||||||
|
@Field(key: "type")
|
||||||
|
var type: String
|
||||||
|
|
||||||
init() {}
|
init() {}
|
||||||
|
|
||||||
init(
|
init(
|
||||||
id: UUID? = nil,
|
id: UUID? = nil,
|
||||||
trunkID: TrunkModel.IDValue,
|
trunkID: TrunkModel.IDValue,
|
||||||
roomID: RoomModel.IDValue,
|
roomID: RoomModel.IDValue,
|
||||||
registers: [Int]
|
registers: [Int],
|
||||||
|
type: DuctSizing.TrunkSize.TrunkType
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
$trunk.id = trunkID
|
$trunk.id = trunkID
|
||||||
$room.id = roomID
|
$room.id = roomID
|
||||||
self.registers = registers
|
self.registers = registers
|
||||||
|
self.type = type.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func toDTO() throws -> DuctSizing.TrunkSize.RoomProxy {
|
func toDTO(on database: any Database) async throws -> DuctSizing.TrunkSize.RoomProxy {
|
||||||
.init(
|
guard let room = try await RoomModel.find($room.id, on: database) else {
|
||||||
|
throw NotFoundError()
|
||||||
|
}
|
||||||
|
return .init(
|
||||||
room: try room.toDTO(),
|
room: try room.toDTO(),
|
||||||
registers: registers
|
registers: registers
|
||||||
)
|
)
|
||||||
@@ -199,12 +227,25 @@ final class TrunkModel: Model, @unchecked Sendable {
|
|||||||
self.type = type.rawValue
|
self.type = type.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func toDTO() throws -> DuctSizing.TrunkSize {
|
func toDTO(on database: any Database) async throws -> DuctSizing.TrunkSize {
|
||||||
try .init(
|
let rooms = try await withThrowingTaskGroup(of: DuctSizing.TrunkSize.RoomProxy.self) { group in
|
||||||
|
for room in self.rooms {
|
||||||
|
group.addTask {
|
||||||
|
try await room.toDTO(on: database)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return try await group.reduce(into: [DuctSizing.TrunkSize.RoomProxy]()) {
|
||||||
|
$0.append($1)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return try .init(
|
||||||
id: requireID(),
|
id: requireID(),
|
||||||
projectID: $project.id,
|
projectID: $project.id,
|
||||||
type: .init(rawValue: type)!,
|
type: .init(rawValue: type)!,
|
||||||
rooms: rooms.map { try $0.toDTO() },
|
rooms: rooms,
|
||||||
height: height
|
height: height
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,6 +14,28 @@ extension Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension DuctSizing.TrunkSize.RoomProxy {
|
||||||
|
|
||||||
|
var totalHeatingLoad: Double {
|
||||||
|
room.heatingLoadPerRegister * Double(registers.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func totalCoolingSensible(projectSHR: Double) -> Double {
|
||||||
|
room.coolingSensiblePerRegister(projectSHR: projectSHR) * Double(registers.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DuctSizing.TrunkSize {
|
||||||
|
|
||||||
|
var totalHeatingLoad: Double {
|
||||||
|
rooms.reduce(into: 0) { $0 += $1.totalHeatingLoad }
|
||||||
|
}
|
||||||
|
|
||||||
|
func totalCoolingSensible(projectSHR: Double) -> Double {
|
||||||
|
rooms.reduce(into: 0) { $0 += $1.totalCoolingSensible(projectSHR: projectSHR) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension ComponentPressureLosses {
|
extension ComponentPressureLosses {
|
||||||
var totalLosses: Double { values.reduce(0) { $0 + $1 } }
|
var totalLosses: Double { values.reduce(0) { $0 + $1 } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,29 @@ public struct ManualDClient: Sendable {
|
|||||||
@Sendable (EquivalentRectangularDuctRequest) async throws -> EquivalentRectangularDuctResponse
|
@Sendable (EquivalentRectangularDuctRequest) async throws -> EquivalentRectangularDuctResponse
|
||||||
|
|
||||||
public func calculateSizes(
|
public func calculateSizes(
|
||||||
|
rooms: [Room],
|
||||||
|
trunks: [DuctSizing.TrunkSize],
|
||||||
|
equipmentInfo: EquipmentInfo,
|
||||||
|
maxSupplyLength: EffectiveLength,
|
||||||
|
maxReturnLength: EffectiveLength,
|
||||||
|
designFrictionRate: Double,
|
||||||
|
projectSHR: Double,
|
||||||
|
logger: Logger? = nil
|
||||||
|
) async throws -> (rooms: [DuctSizing.RoomContainer], trunks: [DuctSizing.TrunkContainer]) {
|
||||||
|
try await (
|
||||||
|
calculateSizes(
|
||||||
|
rooms: rooms, equipmentInfo: equipmentInfo,
|
||||||
|
maxSupplyLength: maxSupplyLength, maxReturnLength: maxReturnLength,
|
||||||
|
designFrictionRate: designFrictionRate, projectSHR: projectSHR
|
||||||
|
),
|
||||||
|
calculateSizes(
|
||||||
|
rooms: rooms, trunks: trunks, equipmentInfo: equipmentInfo,
|
||||||
|
maxSupplyLength: maxSupplyLength, maxReturnLength: maxReturnLength,
|
||||||
|
designFrictionRate: designFrictionRate, projectSHR: projectSHR)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateSizes(
|
||||||
rooms: [Room],
|
rooms: [Room],
|
||||||
equipmentInfo: EquipmentInfo,
|
equipmentInfo: EquipmentInfo,
|
||||||
maxSupplyLength: EffectiveLength,
|
maxSupplyLength: EffectiveLength,
|
||||||
@@ -56,6 +79,7 @@ public struct ManualDClient: Sendable {
|
|||||||
registerID: "SR-\(registerIDCount)",
|
registerID: "SR-\(registerIDCount)",
|
||||||
roomID: room.id,
|
roomID: room.id,
|
||||||
roomName: "\(room.name)-\(n)",
|
roomName: "\(room.name)-\(n)",
|
||||||
|
roomRegister: n,
|
||||||
heatingLoad: heatingLoad,
|
heatingLoad: heatingLoad,
|
||||||
coolingLoad: coolingLoad,
|
coolingLoad: coolingLoad,
|
||||||
heatingCFM: heatingCFM,
|
heatingCFM: heatingCFM,
|
||||||
@@ -76,6 +100,56 @@ public struct ManualDClient: Sendable {
|
|||||||
return retval
|
return retval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calculateSizes(
|
||||||
|
rooms: [Room],
|
||||||
|
trunks: [DuctSizing.TrunkSize],
|
||||||
|
equipmentInfo: EquipmentInfo,
|
||||||
|
maxSupplyLength: EffectiveLength,
|
||||||
|
maxReturnLength: EffectiveLength,
|
||||||
|
designFrictionRate: Double,
|
||||||
|
projectSHR: Double,
|
||||||
|
logger: Logger? = nil
|
||||||
|
) async throws -> [DuctSizing.TrunkContainer] {
|
||||||
|
|
||||||
|
var retval = [DuctSizing.TrunkContainer]()
|
||||||
|
let totalHeatingLoad = rooms.totalHeatingLoad
|
||||||
|
let totalCoolingSensible = rooms.totalCoolingSensible(shr: projectSHR)
|
||||||
|
|
||||||
|
for trunk in trunks {
|
||||||
|
let heatingLoad = trunk.totalHeatingLoad
|
||||||
|
let coolingLoad = trunk.totalCoolingSensible(projectSHR: projectSHR)
|
||||||
|
let heatingPercent = heatingLoad / totalHeatingLoad
|
||||||
|
let coolingPercent = coolingLoad / totalCoolingSensible
|
||||||
|
let heatingCFM = heatingPercent * Double(equipmentInfo.heatingCFM)
|
||||||
|
let coolingCFM = coolingPercent * Double(equipmentInfo.coolingCFM)
|
||||||
|
let designCFM = DuctSizing.DesignCFM(heating: heatingCFM, cooling: coolingCFM)
|
||||||
|
let sizes = try await self.ductSize(
|
||||||
|
.init(designCFM: Int(designCFM.value), frictionRate: designFrictionRate)
|
||||||
|
)
|
||||||
|
var width: Int? = nil
|
||||||
|
if let height = trunk.height {
|
||||||
|
let rectangularSize = try await self.equivalentRectangularDuct(
|
||||||
|
.init(round: sizes.finalSize, height: height)
|
||||||
|
)
|
||||||
|
width = rectangularSize.width
|
||||||
|
}
|
||||||
|
|
||||||
|
retval.append(
|
||||||
|
.init(
|
||||||
|
trunk: trunk,
|
||||||
|
ductSize: .init(
|
||||||
|
designCFM: designCFM,
|
||||||
|
roundSize: sizes.ductulatorSize,
|
||||||
|
finalSize: sizes.finalSize,
|
||||||
|
velocity: sizes.velocity,
|
||||||
|
flexSize: sizes.flexSize)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ManualDClient: TestDependencyKey {
|
extension ManualDClient: TestDependencyKey {
|
||||||
|
|||||||
@@ -21,11 +21,41 @@ public enum DuctSizing {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct SizeContainer: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
public let designCFM: DesignCFM
|
||||||
|
public let roundSize: Double
|
||||||
|
public let finalSize: Int
|
||||||
|
public let velocity: Int
|
||||||
|
public let flexSize: Int
|
||||||
|
public let height: Int?
|
||||||
|
public let width: Int?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
designCFM: DuctSizing.DesignCFM,
|
||||||
|
roundSize: Double,
|
||||||
|
finalSize: Int,
|
||||||
|
velocity: Int,
|
||||||
|
flexSize: Int,
|
||||||
|
height: Int? = nil,
|
||||||
|
width: Int? = nil
|
||||||
|
) {
|
||||||
|
self.designCFM = designCFM
|
||||||
|
self.roundSize = roundSize
|
||||||
|
self.finalSize = finalSize
|
||||||
|
self.velocity = velocity
|
||||||
|
self.flexSize = flexSize
|
||||||
|
self.height = height
|
||||||
|
self.width = width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct RoomContainer: Codable, Equatable, Sendable {
|
public struct RoomContainer: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
public let registerID: String
|
public let registerID: String
|
||||||
public let roomID: Room.ID
|
public let roomID: Room.ID
|
||||||
public let roomName: String
|
public let roomName: String
|
||||||
|
public let roomRegister: Int
|
||||||
public let heatingLoad: Double
|
public let heatingLoad: Double
|
||||||
public let coolingLoad: Double
|
public let coolingLoad: Double
|
||||||
public let heatingCFM: Double
|
public let heatingCFM: Double
|
||||||
@@ -42,6 +72,7 @@ public enum DuctSizing {
|
|||||||
registerID: String,
|
registerID: String,
|
||||||
roomID: Room.ID,
|
roomID: Room.ID,
|
||||||
roomName: String,
|
roomName: String,
|
||||||
|
roomRegister: Int,
|
||||||
heatingLoad: Double,
|
heatingLoad: Double,
|
||||||
coolingLoad: Double,
|
coolingLoad: Double,
|
||||||
heatingCFM: Double,
|
heatingCFM: Double,
|
||||||
@@ -57,6 +88,7 @@ public enum DuctSizing {
|
|||||||
self.registerID = registerID
|
self.registerID = registerID
|
||||||
self.roomID = roomID
|
self.roomID = roomID
|
||||||
self.roomName = roomName
|
self.roomName = roomName
|
||||||
|
self.roomRegister = roomRegister
|
||||||
self.heatingLoad = heatingLoad
|
self.heatingLoad = heatingLoad
|
||||||
self.coolingLoad = coolingLoad
|
self.coolingLoad = coolingLoad
|
||||||
self.heatingCFM = heatingCFM
|
self.heatingCFM = heatingCFM
|
||||||
@@ -94,6 +126,21 @@ public enum DuctSizing {
|
|||||||
|
|
||||||
extension DuctSizing {
|
extension DuctSizing {
|
||||||
|
|
||||||
|
public struct TrunkContainer: Codable, Equatable, Identifiable, Sendable {
|
||||||
|
public var id: TrunkSize.ID { trunk.id }
|
||||||
|
|
||||||
|
public let trunk: TrunkSize
|
||||||
|
public let ductSize: SizeContainer
|
||||||
|
|
||||||
|
public init(
|
||||||
|
trunk: TrunkSize,
|
||||||
|
ductSize: SizeContainer
|
||||||
|
) {
|
||||||
|
self.trunk = trunk
|
||||||
|
self.ductSize = ductSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct TrunkSize: Codable, Equatable, Identifiable, Sendable {
|
public struct TrunkSize: Codable, Equatable, Identifiable, Sendable {
|
||||||
|
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
@@ -145,16 +192,18 @@ extension DuctSizing.TrunkSize {
|
|||||||
|
|
||||||
public var id: Room.ID { room.id }
|
public var id: Room.ID { room.id }
|
||||||
public let room: Room
|
public let room: Room
|
||||||
public let registers: [Int]?
|
public let registers: [Int]
|
||||||
|
|
||||||
public init(room: Room, registers: [Int]? = nil) {
|
public init(room: Room, registers: [Int]) {
|
||||||
self.room = room
|
self.room = room
|
||||||
self.registers = registers
|
self.registers = registers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TrunkType: String, Codable, Equatable, Sendable {
|
public enum TrunkType: String, CaseIterable, Codable, Equatable, Sendable {
|
||||||
case `return`
|
case `return`
|
||||||
case supply
|
case supply
|
||||||
|
|
||||||
|
public static let allCases = [Self.supply, .return]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -609,7 +609,9 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
case index
|
case index
|
||||||
case deleteRectangularSize(Room.ID, DuctSizing.RectangularDuct.ID)
|
case deleteRectangularSize(Room.ID, DuctSizing.RectangularDuct.ID)
|
||||||
case roomRectangularForm(Room.ID, RoomRectangularForm)
|
case roomRectangularForm(Room.ID, RoomRectangularForm)
|
||||||
|
case trunk(TrunkRoute)
|
||||||
|
|
||||||
|
public static let roomPath = "room"
|
||||||
static let rootPath = "duct-sizing"
|
static let rootPath = "duct-sizing"
|
||||||
|
|
||||||
static let router = OneOf {
|
static let router = OneOf {
|
||||||
@@ -620,7 +622,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Route(.case(Self.deleteRectangularSize)) {
|
Route(.case(Self.deleteRectangularSize)) {
|
||||||
Path {
|
Path {
|
||||||
rootPath
|
rootPath
|
||||||
"room"
|
roomPath
|
||||||
Room.ID.parser()
|
Room.ID.parser()
|
||||||
}
|
}
|
||||||
Method.delete
|
Method.delete
|
||||||
@@ -631,7 +633,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Route(.case(Self.roomRectangularForm)) {
|
Route(.case(Self.roomRectangularForm)) {
|
||||||
Path {
|
Path {
|
||||||
rootPath
|
rootPath
|
||||||
"room"
|
roomPath
|
||||||
Room.ID.parser()
|
Room.ID.parser()
|
||||||
}
|
}
|
||||||
Method.post
|
Method.post
|
||||||
@@ -646,6 +648,67 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
.map(.memberwise(RoomRectangularForm.init))
|
.map(.memberwise(RoomRectangularForm.init))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Route(.case(Self.trunk)) {
|
||||||
|
Path { rootPath }
|
||||||
|
TrunkRoute.router
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TrunkRoute: Equatable, Sendable {
|
||||||
|
case delete(DuctSizing.TrunkSize.ID)
|
||||||
|
case submit(TrunkSizeForm)
|
||||||
|
case update(DuctSizing.TrunkSize.ID, TrunkSizeForm)
|
||||||
|
|
||||||
|
public static let rootPath = "trunk"
|
||||||
|
|
||||||
|
static let router = OneOf {
|
||||||
|
Route(.case(Self.delete)) {
|
||||||
|
Path {
|
||||||
|
rootPath
|
||||||
|
DuctSizing.TrunkSize.ID.parser()
|
||||||
|
}
|
||||||
|
Method.delete
|
||||||
|
}
|
||||||
|
Route(.case(Self.submit)) {
|
||||||
|
Path {
|
||||||
|
rootPath
|
||||||
|
}
|
||||||
|
Method.post
|
||||||
|
Body {
|
||||||
|
FormData {
|
||||||
|
Field("projectID") { Project.ID.parser() }
|
||||||
|
Field("type") { DuctSizing.TrunkSize.TrunkType.parser() }
|
||||||
|
Optionally {
|
||||||
|
Field("height") { Int.parser() }
|
||||||
|
}
|
||||||
|
Many {
|
||||||
|
Field("rooms", .string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map(.memberwise(TrunkSizeForm.init))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Route(.case(Self.update)) {
|
||||||
|
Path {
|
||||||
|
rootPath
|
||||||
|
DuctSizing.TrunkSize.ID.parser()
|
||||||
|
}
|
||||||
|
Method.patch
|
||||||
|
Body {
|
||||||
|
FormData {
|
||||||
|
Field("projectID") { Project.ID.parser() }
|
||||||
|
Field("type") { DuctSizing.TrunkSize.TrunkType.parser() }
|
||||||
|
Optionally {
|
||||||
|
Field("height") { Int.parser() }
|
||||||
|
}
|
||||||
|
Many {
|
||||||
|
Field("rooms", .string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map(.memberwise(TrunkSizeForm.init))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct RoomRectangularForm: Equatable, Sendable {
|
public struct RoomRectangularForm: Equatable, Sendable {
|
||||||
@@ -653,6 +716,13 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
public let register: Int
|
public let register: Int
|
||||||
public let height: Int
|
public let height: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct TrunkSizeForm: Equatable, Sendable {
|
||||||
|
public let projectID: Project.ID
|
||||||
|
public let type: DuctSizing.TrunkSize.TrunkType
|
||||||
|
public let height: Int?
|
||||||
|
public let rooms: [String]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,14 @@ extension DatabaseClient.Projects {
|
|||||||
|
|
||||||
extension DatabaseClient {
|
extension DatabaseClient {
|
||||||
|
|
||||||
func calculateDuctSizes(projectID: Project.ID) async throws -> [DuctSizing.RoomContainer] {
|
func calculateDuctSizes(
|
||||||
|
projectID: Project.ID
|
||||||
|
) async throws -> (rooms: [DuctSizing.RoomContainer], trunks: [DuctSizing.TrunkContainer]) {
|
||||||
@Dependency(\.manualD) var manualD
|
@Dependency(\.manualD) var manualD
|
||||||
|
|
||||||
return try await manualD.calculate(
|
return try await manualD.calculate(
|
||||||
rooms: rooms.fetch(projectID),
|
rooms: rooms.fetch(projectID),
|
||||||
|
trunks: trunkSizes.fetch(projectID),
|
||||||
designFrictionRateResult: designFrictionRate(projectID: projectID),
|
designFrictionRateResult: designFrictionRate(projectID: projectID),
|
||||||
projectSHR: projects.getSensibleHeatRatio(projectID)
|
projectSHR: projects.getSensibleHeatRatio(projectID)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,20 +6,22 @@ extension ManualDClient {
|
|||||||
|
|
||||||
func calculate(
|
func calculate(
|
||||||
rooms: [Room],
|
rooms: [Room],
|
||||||
|
trunks: [DuctSizing.TrunkSize],
|
||||||
designFrictionRateResult: (EquipmentInfo, EffectiveLength.MaxContainer, Double)?,
|
designFrictionRateResult: (EquipmentInfo, EffectiveLength.MaxContainer, Double)?,
|
||||||
projectSHR: Double?,
|
projectSHR: Double?,
|
||||||
logger: Logger? = nil
|
logger: Logger? = nil
|
||||||
) async throws -> [DuctSizing.RoomContainer] {
|
) async throws -> (rooms: [DuctSizing.RoomContainer], trunks: [DuctSizing.TrunkContainer]) {
|
||||||
guard let designFrictionRateResult else { return [] }
|
guard let designFrictionRateResult else { return ([], []) }
|
||||||
let equipmentInfo = designFrictionRateResult.0
|
let equipmentInfo = designFrictionRateResult.0
|
||||||
let effectiveLengths = designFrictionRateResult.1
|
let effectiveLengths = designFrictionRateResult.1
|
||||||
let designFrictionRate = designFrictionRateResult.2
|
let designFrictionRate = designFrictionRateResult.2
|
||||||
|
|
||||||
guard let maxSupply = effectiveLengths.supply else { return [] }
|
guard let maxSupply = effectiveLengths.supply else { return ([], []) }
|
||||||
guard let maxReturn = effectiveLengths.return else { return [] }
|
guard let maxReturn = effectiveLengths.return else { return ([], []) }
|
||||||
|
|
||||||
let ductRooms = try await self.calculateSizes(
|
let ductRooms = try await self.calculateSizes(
|
||||||
rooms: rooms,
|
rooms: rooms,
|
||||||
|
trunks: trunks,
|
||||||
equipmentInfo: equipmentInfo,
|
equipmentInfo: equipmentInfo,
|
||||||
maxSupplyLength: maxSupply,
|
maxSupplyLength: maxSupply,
|
||||||
maxReturnLength: maxReturn,
|
maxReturnLength: maxReturn,
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import Foundation
|
||||||
|
import Logging
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
extension SiteRoute.View.ProjectRoute.DuctSizingRoute.TrunkSizeForm {
|
||||||
|
|
||||||
|
func toCreate(logger: Logger? = nil) throws -> DuctSizing.TrunkSize.Create {
|
||||||
|
try .init(
|
||||||
|
projectID: projectID,
|
||||||
|
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 {
|
||||||
|
let split = room.split(separator: "_")
|
||||||
|
guard let idString = split.first,
|
||||||
|
let id = UUID(uuidString: String(idString))
|
||||||
|
else {
|
||||||
|
logger?.error("Could not parse id from: \(room)")
|
||||||
|
throw RoomError()
|
||||||
|
}
|
||||||
|
guard let registerString = split.last,
|
||||||
|
let register = Int(registerString)
|
||||||
|
else {
|
||||||
|
logger?.error("Could not register number from: \(room)")
|
||||||
|
throw RoomError()
|
||||||
|
}
|
||||||
|
if var currRegisters = retval[id] {
|
||||||
|
currRegisters.append(register)
|
||||||
|
retval[id] = currRegisters
|
||||||
|
} else {
|
||||||
|
retval[id] = [register]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return retval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RoomError: Error {}
|
||||||
@@ -542,6 +542,7 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute {
|
|||||||
return await ResultView {
|
return await ResultView {
|
||||||
let room = try await database.rooms.deleteRectangularSize(roomID, rectangularSizeID)
|
let room = try await database.rooms.deleteRectangularSize(roomID, rectangularSizeID)
|
||||||
return try await database.calculateDuctSizes(projectID: projectID)
|
return try await database.calculateDuctSizes(projectID: projectID)
|
||||||
|
.rooms
|
||||||
.filter({ $0.roomID == room.id })
|
.filter({ $0.roomID == room.id })
|
||||||
.first!
|
.first!
|
||||||
} onSuccess: { container in
|
} onSuccess: { container in
|
||||||
@@ -559,11 +560,30 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
return try await database.calculateDuctSizes(projectID: projectID)
|
return try await database.calculateDuctSizes(projectID: projectID)
|
||||||
|
.rooms
|
||||||
.filter({ $0.roomID == room.id })
|
.filter({ $0.roomID == room.id })
|
||||||
.first!
|
.first!
|
||||||
} onSuccess: { container in
|
} onSuccess: { container in
|
||||||
DuctSizingView.RoomRow(projectID: projectID, room: container)
|
DuctSizingView.RoomRow(projectID: projectID, room: container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .trunk(let route):
|
||||||
|
switch route {
|
||||||
|
case .delete(let id):
|
||||||
|
return await ResultView {
|
||||||
|
try await database.trunkSizes.delete(id)
|
||||||
|
}
|
||||||
|
case .submit(let form):
|
||||||
|
return await view(on: request, projectID: projectID) {
|
||||||
|
_ = try await database.trunkSizes.create(
|
||||||
|
form.toCreate(logger: request.logger)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .update(let id, let form):
|
||||||
|
// FIX:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,9 +601,9 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute {
|
|||||||
try await database.projects.getCompletedSteps(projectID),
|
try await database.projects.getCompletedSteps(projectID),
|
||||||
try await database.calculateDuctSizes(projectID: projectID)
|
try await database.calculateDuctSizes(projectID: projectID)
|
||||||
)
|
)
|
||||||
} onSuccess: { (steps, rooms) in
|
} onSuccess: { (steps, ducts) in
|
||||||
ProjectView(projectID: projectID, activeTab: .ductSizing, completedSteps: steps) {
|
ProjectView(projectID: projectID, activeTab: .ductSizing, completedSteps: steps) {
|
||||||
DuctSizingView(rooms: rooms)
|
DuctSizingView(rooms: ducts.rooms, trunks: ducts.trunks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,22 @@ import ElementaryHTMX
|
|||||||
import ManualDCore
|
import ManualDCore
|
||||||
import Styleguide
|
import Styleguide
|
||||||
|
|
||||||
// TODO: Add error text if prior steps are not completed.
|
// TODO: Add trunk size table.
|
||||||
|
|
||||||
struct DuctSizingView: HTML, Sendable {
|
struct DuctSizingView: HTML, Sendable {
|
||||||
|
|
||||||
@Environment(ProjectViewValue.$projectID) var projectID
|
@Environment(ProjectViewValue.$projectID) var projectID
|
||||||
|
|
||||||
// let projectID: Project.ID
|
|
||||||
let rooms: [DuctSizing.RoomContainer]
|
let rooms: [DuctSizing.RoomContainer]
|
||||||
|
let trunks: [DuctSizing.TrunkContainer]
|
||||||
|
|
||||||
|
var supplyTrunks: [DuctSizing.TrunkContainer] {
|
||||||
|
trunks.filter { $0.trunk.type == .supply }
|
||||||
|
}
|
||||||
|
|
||||||
|
var returnTrunks: [DuctSizing.TrunkContainer] {
|
||||||
|
trunks.filter { $0.trunk.type == .return }
|
||||||
|
}
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
div(.class("space-y-4")) {
|
div(.class("space-y-4")) {
|
||||||
@@ -22,6 +30,31 @@ struct DuctSizingView: HTML, Sendable {
|
|||||||
} else {
|
} else {
|
||||||
RoomsTable(projectID: projectID, rooms: rooms)
|
RoomsTable(projectID: projectID, 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,8 +123,8 @@ struct DuctSizingView: HTML, Sendable {
|
|||||||
.attributes(.class("badge-secondary"))
|
.attributes(.class("badge-secondary"))
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
Number(room.flexSize)
|
Badge(number: room.flexSize)
|
||||||
.attributes(.class("badge badge-outline badge-primary text-xl font-bold"))
|
.attributes(.class("badge-primary"))
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
if let width = room.rectangularWidth {
|
if let width = room.rectangularWidth {
|
||||||
@@ -141,6 +174,122 @@ struct DuctSizingView: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TrunkTable: HTML, Sendable {
|
||||||
|
let trunks: [DuctSizing.TrunkContainer]
|
||||||
|
let rooms: [DuctSizing.RoomContainer]
|
||||||
|
|
||||||
|
var body: some HTML {
|
||||||
|
div(.class("overflow-x-auto")) {
|
||||||
|
table(.class("table table-zebra text-lg")) {
|
||||||
|
thead {
|
||||||
|
tr(.class("text-lg")) {
|
||||||
|
th { "Associated Supplies" }
|
||||||
|
th { "Dsn CFM" }
|
||||||
|
th { "Round Size" }
|
||||||
|
th { "Velocity" }
|
||||||
|
th { "Final Size" }
|
||||||
|
th { "Flex Size" }
|
||||||
|
th { "Width" }
|
||||||
|
th { "Height" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 =
|
||||||
|
rooms
|
||||||
|
.first(where: { $0.roomID == room.id && $0.roomRegister == register })
|
||||||
|
{
|
||||||
|
array.append(room.registerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sorted()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DuctSizing.DesignCFM {
|
extension DuctSizing.DesignCFM {
|
||||||
|
|||||||
77
Sources/ViewController/Views/DuctSizing/TrunkSizeForm.swift
Normal file
77
Sources/ViewController/Views/DuctSizing/TrunkSizeForm.swift
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import Elementary
|
||||||
|
import ElementaryHTMX
|
||||||
|
import ManualDCore
|
||||||
|
import Styleguide
|
||||||
|
|
||||||
|
struct TrunkSizeForm: HTML, Sendable {
|
||||||
|
|
||||||
|
static func id() -> String {
|
||||||
|
"trunkSizeForm"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Environment(ProjectViewValue.$projectID) var projectID
|
||||||
|
|
||||||
|
let rooms: [DuctSizing.RoomContainer]
|
||||||
|
let dismiss: Bool
|
||||||
|
|
||||||
|
var route: String {
|
||||||
|
SiteRoute.View.router
|
||||||
|
.path(for: .project(.detail(projectID, .ductSizing(.index))))
|
||||||
|
.appendingPath(SiteRoute.View.ProjectRoute.DuctSizingRoute.TrunkRoute.rootPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some HTML {
|
||||||
|
ModalForm(id: Self.id(), dismiss: dismiss) {
|
||||||
|
h1(.class("text-lg font-bold mb-4")) { "Trunk Size" }
|
||||||
|
form(
|
||||||
|
.class("space-y-4"),
|
||||||
|
.hx.post(route),
|
||||||
|
.hx.target("body"),
|
||||||
|
.hx.swap(.outerHTML)
|
||||||
|
) {
|
||||||
|
|
||||||
|
input(.class("hidden"), .name("projectID"), .value(projectID))
|
||||||
|
|
||||||
|
div(.class("grid grid-cols-1 md:grid-cols-2 gap-4")) {
|
||||||
|
label(.class("select w-full")) {
|
||||||
|
span(.class("label")) { "Type" }
|
||||||
|
select(.name("type")) {
|
||||||
|
for type in DuctSizing.TrunkSize.TrunkType.allCases {
|
||||||
|
option(.value(type.rawValue)) { type.rawValue.capitalized }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LabeledInput(
|
||||||
|
"Height",
|
||||||
|
.type(.text),
|
||||||
|
.name("height"),
|
||||||
|
.placeholder("8 (Optional)"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add room select here.
|
||||||
|
div(.class("grid grid-cols-5 gap-6")) {
|
||||||
|
h2(.class("label font-bold col-span-5")) { "Associated Supply Runs" }
|
||||||
|
for room in rooms {
|
||||||
|
div(.class("flex justify-center items-center col-span-1")) {
|
||||||
|
div(.class("space-y-1")) {
|
||||||
|
p(.class("label block")) { room.registerID }
|
||||||
|
input(
|
||||||
|
.class("checkbox"),
|
||||||
|
.type(.checkbox),
|
||||||
|
.name("rooms"),
|
||||||
|
.value("\(room.roomID)_\(room.roomRegister)")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SubmitButton()
|
||||||
|
.attributes(.class("btn-block"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ struct EquipmentInfoForm: HTML, Sendable {
|
|||||||
equipmentInfo != nil
|
equipmentInfo != nil
|
||||||
? .hx.patch(route)
|
? .hx.patch(route)
|
||||||
: .hx.post(route),
|
: .hx.post(route),
|
||||||
.hx.target("#equipmentInfo"),
|
.hx.target("body"),
|
||||||
.hx.swap(.outerHTML)
|
.hx.swap(.outerHTML)
|
||||||
) {
|
) {
|
||||||
input(.class("hidden"), .name("projectID"), .value("\(projectID)"))
|
input(.class("hidden"), .name("projectID"), .value("\(projectID)"))
|
||||||
|
|||||||
Reference in New Issue
Block a user