WIP: Begin cleaning up duct sizing routes.
This commit is contained in:
@@ -34,10 +34,11 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
||||
let model = try TrunkRoomModel(
|
||||
trunkID: trunk.requireID(),
|
||||
roomID: room.requireID(),
|
||||
registers: registers
|
||||
registers: registers,
|
||||
type: request.type
|
||||
)
|
||||
try await model.save(on: database)
|
||||
try roomProxies.append(model.toDTO())
|
||||
try await roomProxies.append(model.toDTO(on: database))
|
||||
}
|
||||
|
||||
return try .init(
|
||||
@@ -54,16 +55,33 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
||||
try await model.delete(on: database)
|
||||
},
|
||||
fetch: { projectID in
|
||||
try await TrunkModel.query(on: database)
|
||||
.with(\.$rooms)
|
||||
let models = try await TrunkModel.query(on: database)
|
||||
.with(\.$project)
|
||||
.with(\.$rooms)
|
||||
.filter(\.$project.$id == projectID)
|
||||
.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
|
||||
try await TrunkModel.find(id, on: database)
|
||||
.map { try $0.toDTO() }
|
||||
guard let model = try await TrunkModel.find(id, on: database) else {
|
||||
return nil
|
||||
}
|
||||
return try await model.toDTO(on: database)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -110,12 +128,14 @@ extension DuctSizing.TrunkSize {
|
||||
try await database.schema(TrunkRoomModel.schema)
|
||||
.id()
|
||||
.field("registers", .array(of: .int), .required)
|
||||
.field("type", .string, .required)
|
||||
.field(
|
||||
"trunkID", .uuid, .required, .references(TrunkModel.schema, "id", onDelete: .cascade)
|
||||
)
|
||||
.field(
|
||||
"roomID", .uuid, .required, .references(RoomModel.schema, "id", onDelete: .cascade)
|
||||
)
|
||||
.unique(on: "trunkID", "roomID", "type")
|
||||
.create()
|
||||
}
|
||||
|
||||
@@ -143,22 +163,30 @@ final class TrunkRoomModel: Model, @unchecked Sendable {
|
||||
@Field(key: "registers")
|
||||
var registers: [Int]
|
||||
|
||||
@Field(key: "type")
|
||||
var type: String
|
||||
|
||||
init() {}
|
||||
|
||||
init(
|
||||
id: UUID? = nil,
|
||||
trunkID: TrunkModel.IDValue,
|
||||
roomID: RoomModel.IDValue,
|
||||
registers: [Int]
|
||||
registers: [Int],
|
||||
type: DuctSizing.TrunkSize.TrunkType
|
||||
) {
|
||||
self.id = id
|
||||
$trunk.id = trunkID
|
||||
$room.id = roomID
|
||||
self.registers = registers
|
||||
self.type = type.rawValue
|
||||
}
|
||||
|
||||
func toDTO() throws -> DuctSizing.TrunkSize.RoomProxy {
|
||||
.init(
|
||||
func toDTO(on database: any Database) async throws -> DuctSizing.TrunkSize.RoomProxy {
|
||||
guard let room = try await RoomModel.find($room.id, on: database) else {
|
||||
throw NotFoundError()
|
||||
}
|
||||
return .init(
|
||||
room: try room.toDTO(),
|
||||
registers: registers
|
||||
)
|
||||
@@ -199,12 +227,25 @@ final class TrunkModel: Model, @unchecked Sendable {
|
||||
self.type = type.rawValue
|
||||
}
|
||||
|
||||
func toDTO() throws -> DuctSizing.TrunkSize {
|
||||
try .init(
|
||||
func toDTO(on database: any Database) async throws -> DuctSizing.TrunkSize {
|
||||
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(),
|
||||
projectID: $project.id,
|
||||
type: .init(rawValue: type)!,
|
||||
rooms: rooms.map { try $0.toDTO() },
|
||||
rooms: rooms,
|
||||
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 {
|
||||
var totalLosses: Double { values.reduce(0) { $0 + $1 } }
|
||||
}
|
||||
|
||||
@@ -12,6 +12,29 @@ public struct ManualDClient: Sendable {
|
||||
@Sendable (EquivalentRectangularDuctRequest) async throws -> EquivalentRectangularDuctResponse
|
||||
|
||||
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],
|
||||
equipmentInfo: EquipmentInfo,
|
||||
maxSupplyLength: EffectiveLength,
|
||||
@@ -56,6 +79,7 @@ public struct ManualDClient: Sendable {
|
||||
registerID: "SR-\(registerIDCount)",
|
||||
roomID: room.id,
|
||||
roomName: "\(room.name)-\(n)",
|
||||
roomRegister: n,
|
||||
heatingLoad: heatingLoad,
|
||||
coolingLoad: coolingLoad,
|
||||
heatingCFM: heatingCFM,
|
||||
@@ -76,6 +100,56 @@ public struct ManualDClient: Sendable {
|
||||
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 {
|
||||
|
||||
@@ -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 let registerID: String
|
||||
public let roomID: Room.ID
|
||||
public let roomName: String
|
||||
public let roomRegister: Int
|
||||
public let heatingLoad: Double
|
||||
public let coolingLoad: Double
|
||||
public let heatingCFM: Double
|
||||
@@ -42,6 +72,7 @@ public enum DuctSizing {
|
||||
registerID: String,
|
||||
roomID: Room.ID,
|
||||
roomName: String,
|
||||
roomRegister: Int,
|
||||
heatingLoad: Double,
|
||||
coolingLoad: Double,
|
||||
heatingCFM: Double,
|
||||
@@ -57,6 +88,7 @@ public enum DuctSizing {
|
||||
self.registerID = registerID
|
||||
self.roomID = roomID
|
||||
self.roomName = roomName
|
||||
self.roomRegister = roomRegister
|
||||
self.heatingLoad = heatingLoad
|
||||
self.coolingLoad = coolingLoad
|
||||
self.heatingCFM = heatingCFM
|
||||
@@ -94,6 +126,21 @@ public enum 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 let id: UUID
|
||||
@@ -145,16 +192,18 @@ extension DuctSizing.TrunkSize {
|
||||
|
||||
public var id: Room.ID { room.id }
|
||||
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.registers = registers
|
||||
}
|
||||
}
|
||||
|
||||
public enum TrunkType: String, Codable, Equatable, Sendable {
|
||||
public enum TrunkType: String, CaseIterable, Codable, Equatable, Sendable {
|
||||
case `return`
|
||||
case supply
|
||||
|
||||
public static let allCases = [Self.supply, .return]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,7 +609,9 @@ extension SiteRoute.View.ProjectRoute {
|
||||
case index
|
||||
case deleteRectangularSize(Room.ID, DuctSizing.RectangularDuct.ID)
|
||||
case roomRectangularForm(Room.ID, RoomRectangularForm)
|
||||
case trunk(TrunkRoute)
|
||||
|
||||
public static let roomPath = "room"
|
||||
static let rootPath = "duct-sizing"
|
||||
|
||||
static let router = OneOf {
|
||||
@@ -620,7 +622,7 @@ extension SiteRoute.View.ProjectRoute {
|
||||
Route(.case(Self.deleteRectangularSize)) {
|
||||
Path {
|
||||
rootPath
|
||||
"room"
|
||||
roomPath
|
||||
Room.ID.parser()
|
||||
}
|
||||
Method.delete
|
||||
@@ -631,7 +633,7 @@ extension SiteRoute.View.ProjectRoute {
|
||||
Route(.case(Self.roomRectangularForm)) {
|
||||
Path {
|
||||
rootPath
|
||||
"room"
|
||||
roomPath
|
||||
Room.ID.parser()
|
||||
}
|
||||
Method.post
|
||||
@@ -646,6 +648,67 @@ extension SiteRoute.View.ProjectRoute {
|
||||
.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 {
|
||||
@@ -653,6 +716,13 @@ extension SiteRoute.View.ProjectRoute {
|
||||
public let register: 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 {
|
||||
|
||||
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
|
||||
|
||||
return try await manualD.calculate(
|
||||
rooms: rooms.fetch(projectID),
|
||||
trunks: trunkSizes.fetch(projectID),
|
||||
designFrictionRateResult: designFrictionRate(projectID: projectID),
|
||||
projectSHR: projects.getSensibleHeatRatio(projectID)
|
||||
)
|
||||
|
||||
@@ -6,20 +6,22 @@ extension ManualDClient {
|
||||
|
||||
func calculate(
|
||||
rooms: [Room],
|
||||
trunks: [DuctSizing.TrunkSize],
|
||||
designFrictionRateResult: (EquipmentInfo, EffectiveLength.MaxContainer, Double)?,
|
||||
projectSHR: Double?,
|
||||
logger: Logger? = nil
|
||||
) async throws -> [DuctSizing.RoomContainer] {
|
||||
guard let designFrictionRateResult else { return [] }
|
||||
) async throws -> (rooms: [DuctSizing.RoomContainer], trunks: [DuctSizing.TrunkContainer]) {
|
||||
guard let designFrictionRateResult else { return ([], []) }
|
||||
let equipmentInfo = designFrictionRateResult.0
|
||||
let effectiveLengths = designFrictionRateResult.1
|
||||
let designFrictionRate = designFrictionRateResult.2
|
||||
|
||||
guard let maxSupply = effectiveLengths.supply else { return [] }
|
||||
guard let maxReturn = effectiveLengths.return else { return [] }
|
||||
guard let maxSupply = effectiveLengths.supply else { return ([], []) }
|
||||
guard let maxReturn = effectiveLengths.return else { return ([], []) }
|
||||
|
||||
let ductRooms = try await self.calculateSizes(
|
||||
rooms: rooms,
|
||||
trunks: trunks,
|
||||
equipmentInfo: equipmentInfo,
|
||||
maxSupplyLength: maxSupply,
|
||||
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 {
|
||||
let room = try await database.rooms.deleteRectangularSize(roomID, rectangularSizeID)
|
||||
return try await database.calculateDuctSizes(projectID: projectID)
|
||||
.rooms
|
||||
.filter({ $0.roomID == room.id })
|
||||
.first!
|
||||
} onSuccess: { container in
|
||||
@@ -559,11 +560,30 @@ extension SiteRoute.View.ProjectRoute.DuctSizingRoute {
|
||||
)
|
||||
)
|
||||
return try await database.calculateDuctSizes(projectID: projectID)
|
||||
.rooms
|
||||
.filter({ $0.roomID == room.id })
|
||||
.first!
|
||||
} onSuccess: { container in
|
||||
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.calculateDuctSizes(projectID: projectID)
|
||||
)
|
||||
} onSuccess: { (steps, rooms) in
|
||||
} onSuccess: { (steps, ducts) in
|
||||
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 Styleguide
|
||||
|
||||
// TODO: Add error text if prior steps are not completed.
|
||||
// TODO: Add trunk size table.
|
||||
|
||||
struct DuctSizingView: HTML, Sendable {
|
||||
|
||||
@Environment(ProjectViewValue.$projectID) var projectID
|
||||
|
||||
// let projectID: Project.ID
|
||||
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 {
|
||||
div(.class("space-y-4")) {
|
||||
@@ -22,6 +30,31 @@ struct DuctSizingView: HTML, Sendable {
|
||||
} else {
|
||||
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"))
|
||||
}
|
||||
td {
|
||||
Number(room.flexSize)
|
||||
.attributes(.class("badge badge-outline badge-primary text-xl font-bold"))
|
||||
Badge(number: room.flexSize)
|
||||
.attributes(.class("badge-primary"))
|
||||
}
|
||||
td {
|
||||
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 {
|
||||
|
||||
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
|
||||
? .hx.patch(route)
|
||||
: .hx.post(route),
|
||||
.hx.target("#equipmentInfo"),
|
||||
.hx.target("body"),
|
||||
.hx.swap(.outerHTML)
|
||||
) {
|
||||
input(.class("hidden"), .name("projectID"), .value("\(projectID)"))
|
||||
|
||||
Reference in New Issue
Block a user