feat: Initial duct sizing view and calculations, need to add supply and return trunk sizing.
This commit is contained in:
@@ -105,6 +105,7 @@ let package = Package(
|
|||||||
name: "ViewController",
|
name: "ViewController",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.target(name: "DatabaseClient"),
|
.target(name: "DatabaseClient"),
|
||||||
|
.target(name: "ManualDClient"),
|
||||||
.target(name: "ManualDCore"),
|
.target(name: "ManualDCore"),
|
||||||
.target(name: "Styleguide"),
|
.target(name: "Styleguide"),
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
|||||||
@@ -2206,6 +2206,9 @@
|
|||||||
.collapse {
|
.collapse {
|
||||||
visibility: collapse;
|
visibility: collapse;
|
||||||
}
|
}
|
||||||
|
.invisible {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
.visible {
|
.visible {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
@@ -5360,6 +5363,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.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);
|
||||||
}
|
}
|
||||||
@@ -7805,12 +7811,18 @@
|
|||||||
.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);
|
||||||
}
|
}
|
||||||
.py-2 {
|
.py-2 {
|
||||||
padding-block: calc(var(--spacing) * 2);
|
padding-block: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
|
.py-4 {
|
||||||
|
padding-block: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
.ps-2 {
|
.ps-2 {
|
||||||
padding-inline-start: calc(var(--spacing) * 2);
|
padding-inline-start: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -9444,6 +9456,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.lg\:visible {
|
||||||
|
@media (width >= 64rem) {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.lg\:block {
|
||||||
|
@media (width >= 64rem) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.lg\:inline-block {
|
||||||
|
@media (width >= 64rem) {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.lg\:table-cell {
|
||||||
|
@media (width >= 64rem) {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
.lg\:grid-cols-2 {
|
.lg\:grid-cols-2 {
|
||||||
@media (width >= 64rem) {
|
@media (width >= 64rem) {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
@@ -9454,6 +9486,16 @@
|
|||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.xl\:visible {
|
||||||
|
@media (width >= 80rem) {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.xl\:table-cell {
|
||||||
|
@media (width >= 80rem) {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
.xl\:grid-cols-2 {
|
.xl\:grid-cols-2 {
|
||||||
@media (width >= 80rem) {
|
@media (width >= 80rem) {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
@@ -9464,6 +9506,11 @@
|
|||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.\32 xl\:table-cell {
|
||||||
|
@media (width >= 96rem) {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
.dark\:text-white {
|
.dark\:text-white {
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
color: var(--color-white);
|
color: var(--color-white);
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
|
extension Room {
|
||||||
|
|
||||||
|
var heatingLoadPerRegister: Double {
|
||||||
|
heatingLoad / Double(registerCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func coolingSensiblePerRegister(projectSHR: Double) -> Double {
|
||||||
|
let sensible = coolingSensible ?? (coolingTotal * projectSHR)
|
||||||
|
return sensible / Double(registerCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension ComponentPressureLosses {
|
extension ComponentPressureLosses {
|
||||||
var totalLosses: Double { values.reduce(0) { $0 + $1 } }
|
var totalLosses: Double { values.reduce(0) { $0 + $1 } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import DependenciesMacros
|
import DependenciesMacros
|
||||||
|
import Logging
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
@@ -9,6 +10,57 @@ public struct ManualDClient: Sendable {
|
|||||||
public var totalEffectiveLength: @Sendable (TotalEffectiveLengthRequest) async throws -> Int
|
public var totalEffectiveLength: @Sendable (TotalEffectiveLengthRequest) async throws -> Int
|
||||||
public var equivalentRectangularDuct:
|
public var equivalentRectangularDuct:
|
||||||
@Sendable (EquivalentRectangularDuctRequest) async throws -> EquivalentRectangularDuctResponse
|
@Sendable (EquivalentRectangularDuctRequest) async throws -> EquivalentRectangularDuctResponse
|
||||||
|
|
||||||
|
public func calculateSizes(
|
||||||
|
rooms: [Room],
|
||||||
|
equipmentInfo: EquipmentInfo,
|
||||||
|
maxSupplyLength: EffectiveLength,
|
||||||
|
maxReturnLength: EffectiveLength,
|
||||||
|
designFrictionRate: Double,
|
||||||
|
projectSHR: Double,
|
||||||
|
logger: Logger? = nil
|
||||||
|
) async throws -> [DuctSizing.RoomContainer] {
|
||||||
|
var registerIDCount = 1
|
||||||
|
var retval: [DuctSizing.RoomContainer] = []
|
||||||
|
let totalHeatingLoad = rooms.totalHeatingLoad
|
||||||
|
let totalCoolingSensible = rooms.totalCoolingSensible(shr: projectSHR)
|
||||||
|
|
||||||
|
for room in rooms {
|
||||||
|
let heatingLoad = room.heatingLoadPerRegister
|
||||||
|
let coolingLoad = room.coolingSensiblePerRegister(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)
|
||||||
|
)
|
||||||
|
|
||||||
|
for n in 1...room.registerCount {
|
||||||
|
retval.append(
|
||||||
|
.init(
|
||||||
|
registerID: "SR-\(registerIDCount)",
|
||||||
|
roomID: room.id,
|
||||||
|
roomName: "\(room.name)-\(n)",
|
||||||
|
heatingLoad: heatingLoad,
|
||||||
|
coolingLoad: coolingLoad,
|
||||||
|
heatingCFM: heatingCFM,
|
||||||
|
coolingCFM: coolingCFM,
|
||||||
|
designCFM: designCFM,
|
||||||
|
roundSize: sizes.ductulatorSize,
|
||||||
|
finalSize: sizes.finalSize,
|
||||||
|
velocity: sizes.velocity,
|
||||||
|
flexSize: sizes.flexSize
|
||||||
|
)
|
||||||
|
)
|
||||||
|
registerIDCount += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ManualDClient: TestDependencyKey {
|
extension ManualDClient: TestDependencyKey {
|
||||||
|
|||||||
69
Sources/ManualDCore/DuctSizing.swift
Normal file
69
Sources/ManualDCore/DuctSizing.swift
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum DuctSizing {
|
||||||
|
|
||||||
|
public struct RoomContainer: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
public let registerID: String
|
||||||
|
public let roomID: Room.ID
|
||||||
|
public let roomName: String
|
||||||
|
public let heatingLoad: Double
|
||||||
|
public let coolingLoad: Double
|
||||||
|
public let heatingCFM: Double
|
||||||
|
public let coolingCFM: Double
|
||||||
|
public let designCFM: DesignCFM
|
||||||
|
public let roundSize: Double
|
||||||
|
public let finalSize: Int
|
||||||
|
public let velocity: Int
|
||||||
|
public let flexSize: Int
|
||||||
|
|
||||||
|
public init(
|
||||||
|
registerID: String,
|
||||||
|
roomID: Room.ID,
|
||||||
|
roomName: String,
|
||||||
|
heatingLoad: Double,
|
||||||
|
coolingLoad: Double,
|
||||||
|
heatingCFM: Double,
|
||||||
|
coolingCFM: Double,
|
||||||
|
designCFM: DesignCFM,
|
||||||
|
roundSize: Double,
|
||||||
|
finalSize: Int,
|
||||||
|
velocity: Int,
|
||||||
|
flexSize: Int
|
||||||
|
) {
|
||||||
|
self.registerID = registerID
|
||||||
|
self.roomID = roomID
|
||||||
|
self.roomName = roomName
|
||||||
|
self.heatingLoad = heatingLoad
|
||||||
|
self.coolingLoad = coolingLoad
|
||||||
|
self.heatingCFM = heatingCFM
|
||||||
|
self.coolingCFM = coolingCFM
|
||||||
|
self.designCFM = designCFM
|
||||||
|
self.roundSize = roundSize
|
||||||
|
self.finalSize = finalSize
|
||||||
|
self.velocity = velocity
|
||||||
|
self.flexSize = flexSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DesignCFM: Codable, Equatable, Sendable {
|
||||||
|
case heating(Double)
|
||||||
|
case cooling(Double)
|
||||||
|
|
||||||
|
public init(heating: Double, cooling: Double) {
|
||||||
|
if heating >= cooling {
|
||||||
|
self = .heating(heating)
|
||||||
|
} else {
|
||||||
|
self = .cooling(cooling)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var value: Double {
|
||||||
|
switch self {
|
||||||
|
case .heating(let value): return value
|
||||||
|
case .cooling(let value): return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,6 +85,24 @@ extension Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Array where Element == Room {
|
||||||
|
|
||||||
|
public var totalHeatingLoad: Double {
|
||||||
|
reduce(into: 0) { $0 += $1.heatingLoad }
|
||||||
|
}
|
||||||
|
|
||||||
|
public var totalCoolingLoad: Double {
|
||||||
|
reduce(into: 0) { $0 += $1.coolingTotal }
|
||||||
|
}
|
||||||
|
|
||||||
|
public func totalCoolingSensible(shr: Double) -> Double {
|
||||||
|
reduce(into: 0) {
|
||||||
|
let sensible = $1.coolingSensible ?? ($1.coolingTotal * shr)
|
||||||
|
$0 += sensible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
||||||
extension Room {
|
extension Room {
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
public enum DetailRoute: Equatable, Sendable {
|
public enum DetailRoute: Equatable, Sendable {
|
||||||
case index(tab: Tab = .default)
|
case index(tab: Tab = .default)
|
||||||
case componentLoss(ComponentLossRoute)
|
case componentLoss(ComponentLossRoute)
|
||||||
|
case ductSizing(DuctSizingRoute)
|
||||||
case equipment(EquipmentInfoRoute)
|
case equipment(EquipmentInfoRoute)
|
||||||
case equivalentLength(EquivalentLengthRoute)
|
case equivalentLength(EquivalentLengthRoute)
|
||||||
case frictionRate(FrictionRateRoute)
|
case frictionRate(FrictionRateRoute)
|
||||||
@@ -169,6 +170,9 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Route(.case(Self.componentLoss)) {
|
Route(.case(Self.componentLoss)) {
|
||||||
ComponentLossRoute.router
|
ComponentLossRoute.router
|
||||||
}
|
}
|
||||||
|
Route(.case(Self.ductSizing)) {
|
||||||
|
DuctSizingRoute.router
|
||||||
|
}
|
||||||
Route(.case(Self.equipment)) {
|
Route(.case(Self.equipment)) {
|
||||||
EquipmentInfoRoute.router
|
EquipmentInfoRoute.router
|
||||||
}
|
}
|
||||||
@@ -670,6 +674,19 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum DuctSizingRoute: Equatable, Sendable {
|
||||||
|
case index
|
||||||
|
|
||||||
|
static let rootPath = "duct-sizing"
|
||||||
|
|
||||||
|
static let router = OneOf {
|
||||||
|
Route(.case(Self.index)) {
|
||||||
|
Path { rootPath }
|
||||||
|
Method.get
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SiteRoute.View {
|
extension SiteRoute.View {
|
||||||
|
|||||||
@@ -22,6 +22,30 @@ extension DatabaseClient.Projects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension DatabaseClient {
|
||||||
|
|
||||||
|
func designFrictionRate(
|
||||||
|
projectID: Project.ID
|
||||||
|
) async throws -> (EquipmentInfo, EffectiveLength.MaxContainer, Double)? {
|
||||||
|
guard let equipmentInfo = try await equipment.fetch(projectID) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let equivalentLengths = try await effectiveLength.fetchMax(projectID)
|
||||||
|
guard let tel = equivalentLengths.total else { return nil }
|
||||||
|
|
||||||
|
let componentLosses = try await componentLoss.fetch(projectID)
|
||||||
|
guard componentLosses.count > 0 else { return nil }
|
||||||
|
|
||||||
|
let availableStaticPressure =
|
||||||
|
equipmentInfo.staticPressure - componentLosses.totalComponentPressureLoss
|
||||||
|
|
||||||
|
let designFrictionRate = (availableStaticPressure * 100) / tel
|
||||||
|
|
||||||
|
return (equipmentInfo, equivalentLengths, designFrictionRate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension DatabaseClient.ComponentLoss {
|
extension DatabaseClient.ComponentLoss {
|
||||||
|
|
||||||
func createDefaults(projectID: Project.ID) async throws {
|
func createDefaults(projectID: Project.ID) async throws {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import Logging
|
||||||
|
import ManualDClient
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
extension ManualDClient {
|
||||||
|
|
||||||
|
func calculate(
|
||||||
|
rooms: [Room],
|
||||||
|
designFrictionRateResult: (EquipmentInfo, EffectiveLength.MaxContainer, Double)?,
|
||||||
|
projectSHR: Double?,
|
||||||
|
logger: Logger? = nil
|
||||||
|
) async throws -> [DuctSizing.RoomContainer] {
|
||||||
|
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 [] }
|
||||||
|
|
||||||
|
let ductRooms = try await self.calculateSizes(
|
||||||
|
rooms: rooms,
|
||||||
|
equipmentInfo: equipmentInfo,
|
||||||
|
maxSupplyLength: maxSupply,
|
||||||
|
maxReturnLength: maxReturn,
|
||||||
|
designFrictionRate: designFrictionRate,
|
||||||
|
projectSHR: projectSHR ?? 1.0,
|
||||||
|
logger: logger
|
||||||
|
)
|
||||||
|
|
||||||
|
logger?.debug("Rooms: \(ductRooms)")
|
||||||
|
|
||||||
|
return ductRooms
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -117,6 +117,8 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
case .componentLoss(let route):
|
case .componentLoss(let route):
|
||||||
return try await route.renderView(on: request, projectID: projectID)
|
return try await route.renderView(on: request, projectID: projectID)
|
||||||
|
case .ductSizing(let route):
|
||||||
|
return try await route.renderView(on: request, projectID: projectID)
|
||||||
case .equipment(let route):
|
case .equipment(let route):
|
||||||
return try await route.renderView(on: request, projectID: projectID)
|
return try await route.renderView(on: request, projectID: projectID)
|
||||||
case .equivalentLength(let route):
|
case .equivalentLength(let route):
|
||||||
@@ -335,6 +337,20 @@ extension SiteRoute.View.ProjectRoute.EquivalentLengthRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SiteRoute.View.ProjectRoute.DuctSizingRoute {
|
||||||
|
|
||||||
|
func renderView(on request: ViewController.Request, projectID: Project.ID) async throws
|
||||||
|
-> AnySendableHTML
|
||||||
|
{
|
||||||
|
switch self {
|
||||||
|
case .index:
|
||||||
|
return request.view {
|
||||||
|
ProjectView(projectID: projectID, activeTab: .ductSizing, logger: request.logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func _render<C: HTML>(
|
private func _render<C: HTML>(
|
||||||
isHtmxRequest: Bool,
|
isHtmxRequest: Bool,
|
||||||
active activeTab: SiteRoute.View.ProjectRoute.DetailRoute.Tab = .rooms,
|
active activeTab: SiteRoute.View.ProjectRoute.DetailRoute.Tab = .rooms,
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ struct ComponentLossForm: HTML, Sendable {
|
|||||||
for: .project(.detail(projectID, .componentLoss(.index)))
|
for: .project(.detail(projectID, .componentLoss(.index)))
|
||||||
)
|
)
|
||||||
.appendingPath(componentLoss?.id)
|
.appendingPath(componentLoss?.id)
|
||||||
// if let componentLoss {
|
|
||||||
// return baseRoute.appending("/\(componentLoss.id)")
|
|
||||||
// }
|
|
||||||
// return baseRoute
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
|
|||||||
81
Sources/ViewController/Views/DuctSizing/DuctSizingView.swift
Normal file
81
Sources/ViewController/Views/DuctSizing/DuctSizingView.swift
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import Elementary
|
||||||
|
import ElementaryHTMX
|
||||||
|
import ManualDCore
|
||||||
|
import Styleguide
|
||||||
|
|
||||||
|
// TODO: Add error text if prior steps are not completed.
|
||||||
|
|
||||||
|
struct DuctSizingView: HTML, Sendable {
|
||||||
|
|
||||||
|
let rooms: [DuctSizing.RoomContainer]
|
||||||
|
|
||||||
|
var body: some HTML {
|
||||||
|
div {
|
||||||
|
h1(.class("text-2xl py-4")) { "Duct Sizes" }
|
||||||
|
|
||||||
|
div(.class("overflow-x-auto")) {
|
||||||
|
table(.class("table table-zebra")) {
|
||||||
|
thead {
|
||||||
|
tr(.class("text-xl text-gray-400 font-bold")) {
|
||||||
|
th { "ID" }
|
||||||
|
th { "Name" }
|
||||||
|
th { "H-BTU" }
|
||||||
|
th { "C-BTU" }
|
||||||
|
th(.class("hidden 2xl:table-cell")) { "Htg CFM" }
|
||||||
|
th(.class("hidden 2xl:table-cell")) { "Clg CFM" }
|
||||||
|
th { "Dsn CFM" }
|
||||||
|
th(.class("hidden xl:table-cell")) { "Round Size" }
|
||||||
|
th { "Velocity" }
|
||||||
|
th { "Final Size" }
|
||||||
|
th { "Flex Size" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for room in rooms {
|
||||||
|
RoomRow(room: room)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RoomRow: HTML, Sendable {
|
||||||
|
let room: DuctSizing.RoomContainer
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.tr> {
|
||||||
|
tr(.class("text-lg")) {
|
||||||
|
td { room.registerID }
|
||||||
|
td { room.roomName }
|
||||||
|
td { Number(room.heatingLoad, digits: 0) }
|
||||||
|
td { Number(room.coolingLoad, digits: 0) }
|
||||||
|
td(.class("hidden 2xl:table-cell")) { Number(room.heatingCFM, digits: 0) }
|
||||||
|
td(.class("hidden 2xl:table-cell")) { Number(room.coolingCFM, digits: 0) }
|
||||||
|
td {
|
||||||
|
Number(room.designCFM.value, digits: 0)
|
||||||
|
.attributes(
|
||||||
|
.class("badge badge-outline badge-\(room.designCFM.color) text-xl font-bold"))
|
||||||
|
}
|
||||||
|
td(.class("hidden xl:table-cell")) { Number(room.roundSize, digits: 0) }
|
||||||
|
td { Number(room.velocity) }
|
||||||
|
td {
|
||||||
|
Number(room.finalSize)
|
||||||
|
.attributes(.class("badge badge-outline badge-secondary text-xl font-bold"))
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
Number(room.flexSize)
|
||||||
|
.attributes(.class("badge badge-outline badge-primary text-xl font-bold"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DuctSizing.DesignCFM {
|
||||||
|
var color: String {
|
||||||
|
switch self {
|
||||||
|
case .heating: return "error"
|
||||||
|
case .cooling: return "info"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,21 +2,27 @@ import DatabaseClient
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import Elementary
|
import Elementary
|
||||||
import ElementaryHTMX
|
import ElementaryHTMX
|
||||||
|
import Logging
|
||||||
|
import ManualDClient
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
import Styleguide
|
import Styleguide
|
||||||
|
|
||||||
struct ProjectView: HTML, Sendable {
|
struct ProjectView: HTML, Sendable {
|
||||||
@Dependency(\.database) var database
|
@Dependency(\.database) var database
|
||||||
|
@Dependency(\.manualD) var manualD
|
||||||
|
|
||||||
let projectID: Project.ID
|
let projectID: Project.ID
|
||||||
let activeTab: SiteRoute.View.ProjectRoute.DetailRoute.Tab
|
let activeTab: SiteRoute.View.ProjectRoute.DetailRoute.Tab
|
||||||
|
let logger: Logger?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
projectID: Project.ID,
|
projectID: Project.ID,
|
||||||
activeTab: SiteRoute.View.ProjectRoute.DetailRoute.Tab
|
activeTab: SiteRoute.View.ProjectRoute.DetailRoute.Tab,
|
||||||
|
logger: Logger? = nil
|
||||||
) {
|
) {
|
||||||
self.projectID = projectID
|
self.projectID = projectID
|
||||||
self.activeTab = activeTab
|
self.activeTab = activeTab
|
||||||
|
self.logger = logger
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
@@ -61,7 +67,15 @@ struct ProjectView: HTML, Sendable {
|
|||||||
projectID: projectID
|
projectID: projectID
|
||||||
)
|
)
|
||||||
case .ductSizing:
|
case .ductSizing:
|
||||||
div { "FIX ME!" }
|
try await DuctSizingView(
|
||||||
|
rooms: manualD.calculate(
|
||||||
|
rooms: database.rooms.fetch(projectID),
|
||||||
|
designFrictionRateResult: database.designFrictionRate(projectID: projectID),
|
||||||
|
projectSHR: database.projects.getSensibleHeatRatio(projectID),
|
||||||
|
logger: logger
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// div { "FIX ME!" }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +192,11 @@ extension ProjectView {
|
|||||||
}
|
}
|
||||||
li(.class("w-full")) {
|
li(.class("w-full")) {
|
||||||
row(
|
row(
|
||||||
title: "Duct Sizes", icon: .wind, href: "#", isComplete: false, hideIsComplete: true
|
title: "Duct Sizes",
|
||||||
|
icon: .wind,
|
||||||
|
route: .project(.detail(projectID, .ductSizing(.index))),
|
||||||
|
isComplete: false,
|
||||||
|
hideIsComplete: true
|
||||||
)
|
)
|
||||||
.attributes(.class("btn-active"), when: active == .ductSizing)
|
.attributes(.class("btn-active"), when: active == .ductSizing)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,21 @@ import Styleguide
|
|||||||
// TODO: Need to hold the project ID in hidden input field.
|
// TODO: Need to hold the project ID in hidden input field.
|
||||||
struct RoomForm: HTML, Sendable {
|
struct RoomForm: HTML, Sendable {
|
||||||
|
|
||||||
static let id = "roomForm"
|
static func id(_ room: Room? = nil) -> String {
|
||||||
|
let baseId = "roomForm"
|
||||||
|
guard let room else { return baseId }
|
||||||
|
return baseId.appending("_\(room.id.idString)")
|
||||||
|
}
|
||||||
|
|
||||||
let id: String
|
|
||||||
let dismiss: Bool
|
let dismiss: Bool
|
||||||
let projectID: Project.ID
|
let projectID: Project.ID
|
||||||
let room: Room?
|
let room: Room?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
id: String = Self.id,
|
|
||||||
dismiss: Bool,
|
dismiss: Bool,
|
||||||
projectID: Project.ID,
|
projectID: Project.ID,
|
||||||
room: Room? = nil
|
room: Room? = nil
|
||||||
) {
|
) {
|
||||||
self.id = id
|
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
self.projectID = projectID
|
self.projectID = projectID
|
||||||
self.room = room
|
self.room = room
|
||||||
@@ -35,7 +36,7 @@ struct RoomForm: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
ModalForm(id: id, dismiss: dismiss) {
|
ModalForm(id: Self.id(room), dismiss: dismiss) {
|
||||||
h1(.class("text-3xl font-bold pb-6")) { "Room" }
|
h1(.class("text-3xl font-bold pb-6")) { "Room" }
|
||||||
form(
|
form(
|
||||||
.class("modal-backdrop"),
|
.class("modal-backdrop"),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ struct RoomsView: HTML, Sendable {
|
|||||||
) {
|
) {
|
||||||
div(.class("flex me-4")) {
|
div(.class("flex me-4")) {
|
||||||
PlusButton()
|
PlusButton()
|
||||||
.attributes(.showModal(id: RoomForm.id))
|
.attributes(.showModal(id: RoomForm.id()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,12 +134,11 @@ struct RoomsView: HTML, Sendable {
|
|||||||
EditButton()
|
EditButton()
|
||||||
.attributes(
|
.attributes(
|
||||||
.class("join-item"),
|
.class("join-item"),
|
||||||
.showModal(id: "roomForm_\(room.name)")
|
.showModal(id: RoomForm.id(room))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RoomForm(
|
RoomForm(
|
||||||
id: "roomForm_\(room.name)",
|
|
||||||
dismiss: true,
|
dismiss: true,
|
||||||
projectID: room.projectID,
|
projectID: room.projectID,
|
||||||
room: room
|
room: room
|
||||||
|
|||||||
Reference in New Issue
Block a user