WIP: Working on friction rate worksheet views.

This commit is contained in:
2025-12-31 21:56:43 -05:00
parent 591875cf13
commit 24c87602e9
12 changed files with 326 additions and 11 deletions

View File

@@ -59,4 +59,51 @@ public typealias ComponentPressureLosses = [String: Double]
]
}
}
extension ComponentPressureLoss {
public static var mock: [Self] {
[
.init(
id: UUID(0),
projectID: UUID(0),
name: "evaporator-coil",
value: 0.2,
createdAt: Date(),
updatedAt: Date()
),
.init(
id: UUID(1),
projectID: UUID(0),
name: "filter",
value: 0.1,
createdAt: Date(),
updatedAt: Date()
),
.init(
id: UUID(2),
projectID: UUID(0),
name: "supply-outlet",
value: 0.03,
createdAt: Date(),
updatedAt: Date()
),
.init(
id: UUID(3),
projectID: UUID(0),
name: "return-grille",
value: 0.03,
createdAt: Date(),
updatedAt: Date()
),
.init(
id: UUID(4),
projectID: UUID(0),
name: "balance-damper",
value: 0.03,
createdAt: Date(),
updatedAt: Date()
),
]
}
}
#endif

View File

@@ -1,3 +1,4 @@
import Dependencies
import Foundation
public struct EquipmentInfo: Codable, Equatable, Identifiable, Sendable {
@@ -50,3 +51,16 @@ extension EquipmentInfo {
}
}
}
#if DEBUG
extension EquipmentInfo {
public static let mock = Self(
id: UUID(0),
projectID: UUID(0),
heatingCFM: 1000,
coolingCFM: 1000,
createdAt: Date(),
updatedAt: Date()
)
}
#endif

View File

@@ -89,7 +89,7 @@ extension SiteRoute.View {
extension SiteRoute.View {
public enum FrictionRateRoute: Equatable, Sendable {
case index
case form
case form(FormType, dismiss: Bool = false)
static let rootPath = "friction-rate"
@@ -104,7 +104,18 @@ extension SiteRoute.View {
"create"
}
Method.get
Query {
Field("type") { FormType.parser() }
Field("dismiss", default: false) { Bool.parser() }
}
}
}
}
}
extension SiteRoute.View.FrictionRateRoute {
public enum FormType: String, CaseIterable, Codable, Equatable, Sendable {
case equipmentInfo
case componentPressureLoss
}
}

View File

@@ -51,3 +51,32 @@ public struct CancelButton: HTML, Sendable {
}
}
}
public struct EditButton: HTML, Sendable {
let title: String
let type: HTMLAttribute<HTMLTag.button>.ButtonType
public init(
title: String = "Edit",
type: HTMLAttribute<HTMLTag.button>.ButtonType = .button
) {
self.title = title
self.type = type
}
public var body: some HTML<HTMLTag.button> {
button(
.class(
"""
text-white font-bold text-xl bg-blue-500 hover:bg-blue-600 px-4 py-2 rounded-lg shadow-lg
"""
),
.type(type)
) {
div(.class("flex")) {
span(.class("pe-2")) { title }
SVG(.squarePen)
}
}
}
}

View File

@@ -0,0 +1,20 @@
import Elementary
public struct Label: HTML, Sendable {
let title: String
public init(_ title: String) {
self.title = title
}
public init(_ title: @escaping () -> String) {
self.title = title()
}
public var body: some HTML<HTMLTag.span> {
span(.class("text-xl text-gray-400 font-bold")) {
title
}
}
}

View File

@@ -16,6 +16,7 @@ public struct SVG: HTML, Sendable {
extension SVG {
public enum Key: Sendable {
case close
case squarePen
var svg: String {
switch self {
@@ -23,6 +24,10 @@ extension SVG {
return """
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
"""
case .squarePen:
return """
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
"""
}
}
}

View File

@@ -58,11 +58,28 @@ extension SiteRoute.View.FrictionRateRoute {
return MainPage {
FrictionRateView()
}
// FIX:
default:
return MainPage {
FrictionRateView()
case .form(let type, let dismiss):
guard !dismiss else {
return div(.id(type.id)) {}
}
// FIX: Forms need to reference existing items.
switch type {
case .equipmentInfo:
return EquipmentForm()
case .componentPressureLoss:
return ComponentLossForm()
}
}
}
}
extension SiteRoute.View.FrictionRateRoute.FormType {
var id: String {
switch self {
case .equipmentInfo:
return "equipmentForm"
case .componentPressureLoss:
return "componentLossForm"
}
}
}

View File

@@ -0,0 +1,45 @@
import Elementary
import ElementaryHTMX
import ManualDCore
import Styleguide
struct ComponentLossForm: HTML, Sendable {
var body: some HTML {
div(
.id("componentLossForm"),
.class(
"""
fixed top-40 left-[25vw] w-1/2 z-50 text-gray-800
bg-gray-200 border border-gray-400
rounded-lg shadow-lg mx-10
"""
)
) {
h1(.class("text-2xl font-bold")) { "Component Loss" }
form(.class("space-y-4 p-4")) {
div {
label(.for("name")) { "Name" }
Input(id: "name", placeholder: "Name")
.attributes(.type(.text), .required, .autofocus)
}
div {
label(.for("value")) { "Value" }
Input(id: "name", placeholder: "Pressure loss")
.attributes(.type(.number), .min("0"), .max("1"), .step("0.1"), .required)
}
Row {
div {}
div {
CancelButton()
.attributes(
.hx.get(route: .frictionRate(.form(.componentPressureLoss, dismiss: true))),
.hx.target("#componentLossForm"),
.hx.swap(.outerHTML)
)
SubmitButton()
}
}
}
}
}
}

View File

@@ -0,0 +1,62 @@
import Elementary
import ElementaryHTMX
import ManualDCore
import Styleguide
struct ComponentPressureLossTable: HTML, Sendable {
let componentPressureLosses: [ComponentPressureLoss]
var body: some HTML {
div(.id("cplTable")) {
h1(.class("text-2xl font-bold pb-4")) { "Component Pressure Losses" }
table(
.class(
"w-full border-collapse border border-gray-200 table-fixed"
)
) {
thead { tableHeader }
tbody(.id("cplTableBody")) {
Rows(componentPressureLosses: componentPressureLosses)
}
}
}
div(.id("componentLossForm")) {}
}
private var tableHeader: some HTML<HTMLTag.tr> {
tr {
th(.class("border border-gray-200 text-xl font-bold")) { "Name" }
th(.class("border border-gray-200 text-xl font-bold")) { "Pressure Loss" }
th(.class("border border-gray-200 text-xl font-bold")) {
Row {
div {}
button(
.class("px-2"),
.hx.get(route: .frictionRate(.form(.componentPressureLoss))),
.hx.target("#componentLossForm"),
.hx.swap(.outerHTML)
) {
Icon(.circlePlus)
}
}
}
}
}
private struct Rows: HTML, Sendable {
let componentPressureLosses: [ComponentPressureLoss]
var body: some HTML {
for cpl in componentPressureLosses {
tr {
td(.class("border border-gray-200 p-2")) { cpl.name }
td(.class("border border-gray-200 p-2")) { "\(cpl.value)" }
td(.class("border border-gray-200 p-2")) {
// FIX: Add edit button
}
}
}
}
}
}

View File

@@ -2,16 +2,28 @@ import Elementary
import ManualDCore
import Styleguide
// TODO: Have form hold onto equipment info model to edit.
struct EquipmentForm: HTML, Sendable {
var body: some HTML {
div(.id("equipmentForm")) {
h1(.class("text-3xl font-bold pb-6")) { "Equipment Info" }
form {
div(
.id("equipmentForm"),
.class(
"""
fixed top-40 left-[25vw] w-1/2 z-50 text-gray-800
bg-gray-200 border border-gray-400
rounded-lg shadow-lg mx-10
"""
)
) {
h1(.class("text-3xl font-bold pb-6 ps-2")) { "Equipment Info" }
form(.class("space-y-4 p-4")) {
div {
label(.for("staticPressure")) { "Static Pressure" }
Input(id: "staticPressure", placeholder: "Static pressure")
.attributes(.type(.number), .value("0.5"), .min("0"), .max("1.0"), .step("0.1"))
.attributes(
.type(.number), .value("0.5"), .min("0"), .max("1.0"), .step("0.1")
)
}
div {
label(.for("heatingCFM")) { "Heating CFM" }
@@ -26,6 +38,12 @@ struct EquipmentForm: HTML, Sendable {
Row {
div {}
div(.class("space-x-4")) {
CancelButton()
.attributes(
.hx.get(route: .frictionRate(.form(.equipmentInfo, dismiss: true))),
.hx.target("#equipmentForm"),
.hx.swap(.outerHTML)
)
SubmitButton(title: "Save")
}
}

View File

@@ -0,0 +1,45 @@
import Elementary
import ManualDCore
import Styleguide
struct EquipmentInfoView: HTML, Sendable {
let equipmentInfo: EquipmentInfo
var body: some HTML {
div(.class("space-y-4 border border-gray-200 rounded-lg shadow-lg p-4")) {
Row {
h1(.class("text-2xl font-bold")) { "Equipment Info" }
}
Row {
Label { "Static Pressure" }
span { "\(equipmentInfo.staticPressure)" }
}
.attributes(.class("border-b border-gray-200"))
Row {
Label { "Heating CFM" }
span { "\(equipmentInfo.heatingCFM)" }
}
.attributes(.class("border-b border-gray-200"))
Row {
Label { "Cooling CFM" }
span { "\(equipmentInfo.coolingCFM)" }
}
.attributes(.class("border-b border-gray-200"))
Row {
div {}
EditButton()
.attributes(
.hx.get(route: .frictionRate(.form(.equipmentInfo))),
.hx.target("#equipmentForm"),
.hx.swap(.outerHTML)
)
}
}
div(.id("equipmentForm")) {}
}
}

View File

@@ -5,8 +5,10 @@ import Styleguide
struct FrictionRateView: HTML, Sendable {
var body: some HTML {
div {
EquipmentForm()
div(.class("w-full flex-1 p-4 space-y-6")) {
h1(.class("text-4xl font-bold pb-6")) { "Friction Rate" }
EquipmentInfoView(equipmentInfo: EquipmentInfo.mock)
ComponentPressureLossTable(componentPressureLosses: ComponentPressureLoss.mock)
}
}
}