feat: Updates form routes and database routes to use id's in the url path.
This commit is contained in:
@@ -50,11 +50,7 @@ extension EffectiveLength.Update {
|
||||
form: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepThree,
|
||||
projectID: Project.ID
|
||||
) throws {
|
||||
guard let id = form.id else {
|
||||
throw ValidationError("Id not found.")
|
||||
}
|
||||
self.init(
|
||||
id: id,
|
||||
name: form.name,
|
||||
type: form.type,
|
||||
straightLengths: form.straightLengths,
|
||||
|
||||
20
Sources/ViewController/Extensions/String+appendingPath.swift
Normal file
20
Sources/ViewController/Extensions/String+appendingPath.swift
Normal file
@@ -0,0 +1,20 @@
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
|
||||
func appendingPath(_ string: String) -> Self {
|
||||
guard string.starts(with: "/") else {
|
||||
return self.appending("/\(string)")
|
||||
}
|
||||
return self.appending(string)
|
||||
}
|
||||
|
||||
func appendingPath(_ id: UUID?) -> Self {
|
||||
guard let id else { return self }
|
||||
return appendingPath(id.uuidString)
|
||||
}
|
||||
|
||||
func appendingPath(_ id: UUID) -> Self {
|
||||
return appendingPath(id.uuidString)
|
||||
}
|
||||
}
|
||||
7
Sources/ViewController/Extensions/UUID+idString.swift
Normal file
7
Sources/ViewController/Extensions/UUID+idString.swift
Normal file
@@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
extension UUID {
|
||||
var idString: String {
|
||||
uuidString.replacing("-", with: "")
|
||||
}
|
||||
}
|
||||
@@ -105,8 +105,8 @@ extension SiteRoute.View.ProjectRoute {
|
||||
try await database.projects.delete(id)
|
||||
return EmptyHTML()
|
||||
|
||||
case .update(let form):
|
||||
let project = try await database.projects.update(form)
|
||||
case .update(let id, let form):
|
||||
let project = try await database.projects.update(id, form)
|
||||
return ProjectView(projectID: project.id, activeTab: .project)
|
||||
|
||||
case .detail(let projectID, let route):
|
||||
@@ -115,6 +115,8 @@ extension SiteRoute.View.ProjectRoute {
|
||||
return request.view {
|
||||
ProjectView(projectID: projectID, activeTab: tab)
|
||||
}
|
||||
case .componentLoss(let route):
|
||||
return try await route.renderView(on: request, projectID: projectID)
|
||||
case .equipment(let route):
|
||||
return try await route.renderView(on: request, projectID: projectID)
|
||||
case .equivalentLength(let route):
|
||||
@@ -147,8 +149,8 @@ extension SiteRoute.View.ProjectRoute.EquipmentInfoRoute {
|
||||
case .submit(let form):
|
||||
let equipment = try await database.equipment.create(form)
|
||||
return EquipmentInfoView(equipmentInfo: equipment, projectID: projectID)
|
||||
case .update(let updates):
|
||||
let equipment = try await database.equipment.update(updates)
|
||||
case .update(let id, let updates):
|
||||
let equipment = try await database.equipment.update(id, updates)
|
||||
return EquipmentInfoView(equipmentInfo: equipment, projectID: projectID)
|
||||
}
|
||||
}
|
||||
@@ -187,13 +189,14 @@ extension SiteRoute.View.ProjectRoute.RoomRoute {
|
||||
ProjectView(projectID: projectID, activeTab: .rooms)
|
||||
}
|
||||
|
||||
case .update(let form):
|
||||
let _ = try await database.rooms.update(form)
|
||||
case .update(let id, let form):
|
||||
let _ = try await database.rooms.update(id, form)
|
||||
return ProjectView(projectID: projectID, activeTab: .rooms)
|
||||
|
||||
case .updateSensibleHeatRatio(let form):
|
||||
let _ = try await database.projects.update(
|
||||
.init(id: form.projectID, sensibleHeatRatio: form.sensibleHeatRatio)
|
||||
form.projectID,
|
||||
.init(sensibleHeatRatio: form.sensibleHeatRatio)
|
||||
)
|
||||
return request.view {
|
||||
ProjectView(projectID: projectID, activeTab: .rooms)
|
||||
@@ -210,8 +213,8 @@ extension SiteRoute.View.ProjectRoute.FrictionRateRoute {
|
||||
|
||||
switch self {
|
||||
case .index:
|
||||
let equipment = try await database.equipment.fetch(projectID)
|
||||
let componentLosses = try await database.componentLoss.fetch(projectID)
|
||||
// let equipment = try await database.equipment.fetch(projectID)
|
||||
// let componentLosses = try await database.componentLoss.fetch(projectID)
|
||||
|
||||
return request.view {
|
||||
ProjectView(projectID: projectID, activeTab: .frictionRate)
|
||||
@@ -224,12 +227,36 @@ extension SiteRoute.View.ProjectRoute.FrictionRateRoute {
|
||||
return div { "REMOVE ME!" }
|
||||
// return EquipmentForm(dismiss: dismiss, projectID: projectID)
|
||||
case .componentPressureLoss:
|
||||
return ComponentLossForm(dismiss: dismiss, projectID: projectID)
|
||||
return ComponentLossForm(dismiss: dismiss, projectID: projectID, componentLoss: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SiteRoute.View.ProjectRoute.ComponentLossRoute {
|
||||
|
||||
func renderView(
|
||||
on request: ViewController.Request,
|
||||
projectID: Project.ID
|
||||
) async throws -> AnySendableHTML {
|
||||
@Dependency(\.database) var database
|
||||
|
||||
switch self {
|
||||
case .index:
|
||||
return EmptyHTML()
|
||||
case .delete(let id):
|
||||
_ = try await database.componentLoss.delete(id)
|
||||
return EmptyHTML()
|
||||
case .submit(let form):
|
||||
_ = try await database.componentLoss.create(form)
|
||||
return ProjectView(projectID: projectID, activeTab: .frictionRate)
|
||||
case .update(let id, let form):
|
||||
_ = try await database.componentLoss.update(id, form)
|
||||
return ProjectView(projectID: projectID, activeTab: .frictionRate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SiteRoute.View.ProjectRoute.FrictionRateRoute.FormType {
|
||||
var id: String {
|
||||
switch self {
|
||||
@@ -271,8 +298,8 @@ extension SiteRoute.View.ProjectRoute.EquivalentLengthRoute {
|
||||
return GroupField(style: style ?? .supply)
|
||||
}
|
||||
|
||||
case .update(let form):
|
||||
_ = try await database.effectiveLength.update(.init(form: form, projectID: projectID))
|
||||
case .update(let id, let form):
|
||||
_ = try await database.effectiveLength.update(id, .init(form: form, projectID: projectID))
|
||||
return ProjectView(projectID: projectID, activeTab: .equivalentLength)
|
||||
|
||||
case .submit(let step):
|
||||
|
||||
@@ -4,38 +4,62 @@ import ManualDCore
|
||||
import Styleguide
|
||||
|
||||
struct ComponentLossForm: HTML, Sendable {
|
||||
|
||||
static func id(_ componentLoss: ComponentPressureLoss? = nil) -> String {
|
||||
let base = "componentLossForm"
|
||||
guard let componentLoss else { return base }
|
||||
return "\(base)_\(componentLoss.id.idString)"
|
||||
}
|
||||
|
||||
let dismiss: Bool
|
||||
let projectID: Project.ID
|
||||
let componentLoss: ComponentPressureLoss?
|
||||
|
||||
var route: String {
|
||||
SiteRoute.View.router.path(
|
||||
for: .project(.detail(projectID, .componentLoss(.index)))
|
||||
)
|
||||
.appendingPath(componentLoss?.id)
|
||||
// if let componentLoss {
|
||||
// return baseRoute.appending("/\(componentLoss.id)")
|
||||
// }
|
||||
// return baseRoute
|
||||
}
|
||||
|
||||
var body: some HTML {
|
||||
ModalForm(id: "componentLossForm", dismiss: dismiss) {
|
||||
ModalForm(id: Self.id(componentLoss), dismiss: dismiss) {
|
||||
h1(.class("text-2xl font-bold")) { "Component Loss" }
|
||||
form(.class("space-y-4 p-4")) {
|
||||
form(
|
||||
.class("space-y-4 p-4"),
|
||||
componentLoss == nil
|
||||
? .hx.post(route)
|
||||
: .hx.patch(route),
|
||||
.hx.target("body"),
|
||||
.hx.swap(.outerHTML)
|
||||
) {
|
||||
|
||||
if let componentLoss {
|
||||
input(.class("hidden"), .name("id"), .value("\(componentLoss.id)"))
|
||||
}
|
||||
|
||||
input(.class("hidden"), .name("projectID"), .value("\(projectID)"))
|
||||
|
||||
div {
|
||||
label(.for("name")) { "Name" }
|
||||
Input(id: "name", placeholder: "Name")
|
||||
.attributes(.type(.text), .required, .autofocus)
|
||||
.attributes(.type(.text), .required, .autofocus, .value(componentLoss?.name))
|
||||
}
|
||||
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: .project(
|
||||
.detail(projectID, .frictionRate(.form(.componentPressureLoss, dismiss: true))))
|
||||
),
|
||||
.hx.target("#componentLossForm"),
|
||||
.hx.swap(.outerHTML)
|
||||
)
|
||||
SubmitButton()
|
||||
}
|
||||
Input(id: "value", placeholder: "Pressure loss")
|
||||
.attributes(
|
||||
.type(.number), .min("0.03"), .max("1.0"), .step("0.1"), .required,
|
||||
.value(componentLoss?.value)
|
||||
)
|
||||
}
|
||||
|
||||
SubmitButton()
|
||||
.attributes(.class("btn-block"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,33 +23,71 @@ struct ComponentPressureLossesView: HTML, Sendable {
|
||||
)
|
||||
) {
|
||||
Row {
|
||||
h1(.class("text-2xl font-bold")) { "Component Pressure Losses" }
|
||||
div(.class("flex space-x-4 items-center")) {
|
||||
h1(.class("text-2xl font-bold")) { "Component Pressure Losses" }
|
||||
div(.class("flex text-primary space-x-2 items-baseline")) {
|
||||
Number(total)
|
||||
.attributes(.class("text-xl font-bold badge badge-outline badge-primary"))
|
||||
span(.class("text-sm italic")) { "Total" }
|
||||
}
|
||||
}
|
||||
PlusButton()
|
||||
.attributes(
|
||||
.hx.get(
|
||||
route: .project(
|
||||
.detail(projectID, .frictionRate(.form(.componentPressureLoss, dismiss: false))))
|
||||
),
|
||||
.hx.target("#componentLossForm"),
|
||||
.hx.swap(.outerHTML)
|
||||
.showModal(id: ComponentLossForm.id())
|
||||
)
|
||||
}
|
||||
|
||||
for row in componentPressureLosses {
|
||||
Row {
|
||||
Label { row.name }
|
||||
Number(row.value)
|
||||
table(.class("table table-zebra")) {
|
||||
thead {
|
||||
tr(.class("text-xl font-bold")) {
|
||||
th { "Name" }
|
||||
th { "Value" }
|
||||
th {}
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
for row in componentPressureLosses {
|
||||
TableRow(row: row)
|
||||
}
|
||||
}
|
||||
.attributes(.class("border-b border-gray-200"))
|
||||
}
|
||||
|
||||
Row {
|
||||
Label { "Total" }
|
||||
Number(total)
|
||||
.attributes(.class("text-xl font-bold"))
|
||||
}
|
||||
}
|
||||
ComponentLossForm(dismiss: true, projectID: projectID)
|
||||
ComponentLossForm(dismiss: true, projectID: projectID, componentLoss: nil)
|
||||
}
|
||||
|
||||
struct TableRow: HTML, Sendable {
|
||||
let row: ComponentPressureLoss
|
||||
|
||||
var body: some HTML<HTMLTag.tr> {
|
||||
tr(.class("text-lg")) {
|
||||
td { row.name }
|
||||
td { Number(row.value) }
|
||||
td {
|
||||
div(.class("flex join items-end justify-end mx-auto")) {
|
||||
TrashButton()
|
||||
.attributes(
|
||||
.class("join-item"),
|
||||
.hx.delete(
|
||||
route: .project(
|
||||
.detail(row.projectID, .componentLoss(.delete(row.id)))
|
||||
)
|
||||
),
|
||||
.hx.target("body"),
|
||||
.hx.swap(.outerHTML),
|
||||
.hx.confirm("Are your sure?")
|
||||
|
||||
)
|
||||
EditButton()
|
||||
.attributes(
|
||||
.class("join-item"),
|
||||
.showModal(id: ComponentLossForm.id(row))
|
||||
)
|
||||
}
|
||||
|
||||
ComponentLossForm(dismiss: true, projectID: row.projectID, componentLoss: row)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,14 +157,14 @@ struct EffectiveLengthForm: HTML, Sendable {
|
||||
let stepTwo: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepTwo
|
||||
|
||||
var route: String {
|
||||
if effectiveLength != nil {
|
||||
return SiteRoute.View.router.path(
|
||||
for: .project(.detail(projectID, .equivalentLength(.index))))
|
||||
let baseRoute = SiteRoute.View.router.path(
|
||||
for: .project(.detail(projectID, .equivalentLength(.index)))
|
||||
)
|
||||
|
||||
if let effectiveLength {
|
||||
return baseRoute.appendingPath(effectiveLength.id)
|
||||
} else {
|
||||
let baseRoute = SiteRoute.View.router.path(
|
||||
for: .project(.detail(projectID, .equivalentLength(.index)))
|
||||
)
|
||||
return "\(baseRoute)/stepThree"
|
||||
return baseRoute.appendingPath("stepThree")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,20 +126,28 @@ struct EffectiveLengthsView: HTML, Sendable {
|
||||
}
|
||||
|
||||
div(.class("card-actions justify-end pt-6 space-y-4 mt-auto")) {
|
||||
// TODO: Delete.
|
||||
TrashButton()
|
||||
.attributes(
|
||||
.hx.delete(
|
||||
route: .project(
|
||||
.detail(
|
||||
effectiveLength.projectID, .equivalentLength(.delete(id: effectiveLength.id)))
|
||||
)),
|
||||
.hx.confirm("Are you sure?"),
|
||||
.hx.target("#\(id)"),
|
||||
.hx.swap(.outerHTML)
|
||||
)
|
||||
EditButton()
|
||||
.attributes(.showModal(id: EffectiveLengthForm.id(effectiveLength)))
|
||||
div(.class("join")) {
|
||||
TrashButton()
|
||||
.attributes(
|
||||
.class("join-item"),
|
||||
.hx.delete(
|
||||
route: .project(
|
||||
.detail(
|
||||
effectiveLength.projectID,
|
||||
.equivalentLength(.delete(id: effectiveLength.id))
|
||||
)
|
||||
)
|
||||
),
|
||||
.hx.confirm("Are you sure?"),
|
||||
.hx.target("#\(id)"),
|
||||
.hx.swap(.outerHTML)
|
||||
)
|
||||
EditButton()
|
||||
.attributes(
|
||||
.class("join-item"),
|
||||
.showModal(id: EffectiveLengthForm.id(effectiveLength))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
EffectiveLengthForm(effectiveLength: effectiveLength)
|
||||
|
||||
@@ -18,18 +18,11 @@ struct EquipmentInfoForm: HTML, Sendable {
|
||||
return "\(staticPressure)"
|
||||
}
|
||||
|
||||
var heatingCFM: String {
|
||||
guard let heatingCFM = equipmentInfo?.heatingCFM else {
|
||||
return ""
|
||||
}
|
||||
return "\(heatingCFM)"
|
||||
}
|
||||
|
||||
var coolingCFM: String {
|
||||
guard let heatingCFM = equipmentInfo?.heatingCFM else {
|
||||
return ""
|
||||
}
|
||||
return "\(heatingCFM)"
|
||||
var route: String {
|
||||
SiteRoute.View.router.path(
|
||||
for: .project(.detail(projectID, .equipment(.index)))
|
||||
)
|
||||
.appendingPath(equipmentInfo?.id)
|
||||
}
|
||||
|
||||
var body: some HTML {
|
||||
@@ -38,8 +31,8 @@ struct EquipmentInfoForm: HTML, Sendable {
|
||||
form(
|
||||
.class("space-y-4 p-4"),
|
||||
equipmentInfo != nil
|
||||
? .hx.patch(route: .project(.detail(projectID, .equipment(.index))))
|
||||
: .hx.post(route: .project(.detail(projectID, .equipment(.index)))),
|
||||
? .hx.patch(route)
|
||||
: .hx.post(route),
|
||||
.hx.target("#equipmentInfo"),
|
||||
.hx.swap(.outerHTML)
|
||||
) {
|
||||
@@ -59,12 +52,12 @@ struct EquipmentInfoForm: HTML, Sendable {
|
||||
div {
|
||||
label(.for("heatingCFM")) { "Heating CFM" }
|
||||
Input(id: "heatingCFM", placeholder: "CFM")
|
||||
.attributes(.type(.number), .min("0"), .value(heatingCFM))
|
||||
.attributes(.type(.number), .min("0"), .value(equipmentInfo?.heatingCFM))
|
||||
}
|
||||
div {
|
||||
label(.for("coolingCFM")) { "Cooling CFM" }
|
||||
Input(id: "coolingCFM", placeholder: "CFM")
|
||||
.attributes(.type(.number), .min("0"), .value(coolingCFM))
|
||||
.attributes(.type(.number), .min("0"), .value(equipmentInfo?.coolingCFM))
|
||||
}
|
||||
div {
|
||||
SubmitButton(title: "Save")
|
||||
|
||||
@@ -23,24 +23,28 @@ struct EquipmentInfoView: HTML, Sendable {
|
||||
|
||||
if let equipmentInfo {
|
||||
|
||||
Row {
|
||||
Label { "Static Pressure" }
|
||||
Number(equipmentInfo.staticPressure)
|
||||
table(.class("table table-zebra")) {
|
||||
thead {
|
||||
tr {
|
||||
th { Label("Name") }
|
||||
th { Label("Value") }
|
||||
}
|
||||
}
|
||||
tbody(.class("text-lg")) {
|
||||
tr {
|
||||
td { "Static Pressure" }
|
||||
td { Number(equipmentInfo.staticPressure) }
|
||||
}
|
||||
tr {
|
||||
td { "Heating CFM" }
|
||||
td { Number(equipmentInfo.heatingCFM) }
|
||||
}
|
||||
tr {
|
||||
td { "Cooling CFM" }
|
||||
td { Number(equipmentInfo.coolingCFM) }
|
||||
}
|
||||
}
|
||||
}
|
||||
.attributes(.class("border-b border-gray-200"))
|
||||
|
||||
Row {
|
||||
Label { "Heating CFM" }
|
||||
Number(equipmentInfo.heatingCFM)
|
||||
}
|
||||
.attributes(.class("border-b border-gray-200"))
|
||||
|
||||
Row {
|
||||
Label { "Cooling CFM" }
|
||||
Number(equipmentInfo.coolingCFM)
|
||||
}
|
||||
.attributes(.class("border-b border-gray-200"))
|
||||
|
||||
}
|
||||
EquipmentInfoForm(
|
||||
dismiss: true, projectID: projectID, equipmentInfo: equipmentInfo
|
||||
|
||||
@@ -6,15 +6,95 @@ struct FrictionRateView: HTML, Sendable {
|
||||
|
||||
let equipmentInfo: EquipmentInfo?
|
||||
let componentLosses: [ComponentPressureLoss]
|
||||
let equivalentLengths: EffectiveLength.MaxContainer
|
||||
let projectID: Project.ID
|
||||
|
||||
var availableStaticPressure: Double? {
|
||||
guard let staticPressure = equipmentInfo?.staticPressure else {
|
||||
return nil
|
||||
}
|
||||
return staticPressure - componentLosses.totalComponentPressureLoss
|
||||
}
|
||||
|
||||
var frictionRateDesignValue: Double? {
|
||||
guard let availableStaticPressure, let tel = equivalentLengths.total else {
|
||||
return nil
|
||||
}
|
||||
return (((availableStaticPressure * 100) / tel) * 100) / 100
|
||||
}
|
||||
|
||||
var badgeColor: String {
|
||||
let base = "badge-primary"
|
||||
guard let frictionRateDesignValue else { return base }
|
||||
if frictionRateDesignValue >= 0.18 || frictionRateDesignValue <= 0.02 {
|
||||
return "badge-error"
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
var showHighErrors: Bool {
|
||||
guard let frictionRateDesignValue else { return false }
|
||||
return frictionRateDesignValue >= 0.18
|
||||
}
|
||||
|
||||
var showLowErrors: Bool {
|
||||
guard let frictionRateDesignValue else { return false }
|
||||
return frictionRateDesignValue <= 0.02
|
||||
}
|
||||
|
||||
var body: some HTML {
|
||||
div(.class("p-4 space-y-6")) {
|
||||
h1(.class("text-4xl font-bold pb-6")) { "Friction Rate" }
|
||||
EquipmentInfoView(equipmentInfo: equipmentInfo, projectID: projectID)
|
||||
ComponentPressureLossesView(
|
||||
componentPressureLosses: componentLosses, projectID: projectID
|
||||
)
|
||||
div(.class("flex space-x-4")) {
|
||||
Label("Available Static Pressure")
|
||||
if let availableStaticPressure {
|
||||
Number(availableStaticPressure, digits: 2)
|
||||
.attributes(.class("badge badge-lg badge-outline font-bold ms-4"))
|
||||
}
|
||||
}
|
||||
div(.class("flex space-x-4")) {
|
||||
if let frictionRateDesignValue {
|
||||
Label("Friction Rate Design Value")
|
||||
Number(frictionRateDesignValue, digits: 2)
|
||||
.attributes(.class("badge badge-lg badge-outline \(badgeColor) font-bold"))
|
||||
}
|
||||
}
|
||||
|
||||
div(.class("text-error italic")) {
|
||||
p {
|
||||
"No component pressures losses"
|
||||
}
|
||||
.attributes(.class("hidden"), when: componentLosses.totalComponentPressureLoss > 0)
|
||||
|
||||
p {
|
||||
"Calculated friction rate is below 0.02. The fan may not deliver the required CFM."
|
||||
br()
|
||||
" * Increase the blower speed"
|
||||
br()
|
||||
" * Increase the blower size"
|
||||
br()
|
||||
" * Decrease the Total Effective Length (TEL)"
|
||||
}
|
||||
.attributes(.class("hidden"), when: !showLowErrors)
|
||||
|
||||
p {
|
||||
"Calculated friction rate is above 0.18. The fan may deliver too many CFM."
|
||||
br()
|
||||
" * Decrease the blower speed"
|
||||
br()
|
||||
" * Decreae the blower size"
|
||||
br()
|
||||
" * Increase the Total Effective Length (TEL)"
|
||||
}
|
||||
.attributes(.class("hidden"), when: !showHighErrors)
|
||||
}
|
||||
|
||||
div(.class("grid grid-cols-1 lg:grid-cols-2 gap-4")) {
|
||||
EquipmentInfoView(equipmentInfo: equipmentInfo, projectID: projectID)
|
||||
ComponentPressureLossesView(
|
||||
componentPressureLosses: componentLosses, projectID: projectID
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,19 @@ struct ProjectForm: HTML, Sendable {
|
||||
self.project = project
|
||||
}
|
||||
|
||||
var route: String {
|
||||
SiteRoute.View.router.path(for: .project(.index))
|
||||
.appendingPath(project?.id)
|
||||
}
|
||||
|
||||
var body: some HTML {
|
||||
ModalForm(id: Self.id, dismiss: dismiss) {
|
||||
h1(.class("text-3xl font-bold pb-6 ps-2")) { "Project" }
|
||||
form(
|
||||
.class("space-y-4 p-4"),
|
||||
project == nil
|
||||
? .hx.post(route: .project(.index))
|
||||
: .hx.patch(route: .project(.index)),
|
||||
? .hx.post(route)
|
||||
: .hx.patch(route),
|
||||
.hx.target("body"),
|
||||
.hx.swap(.outerHTML)
|
||||
) {
|
||||
|
||||
@@ -5,8 +5,6 @@ import ElementaryHTMX
|
||||
import ManualDCore
|
||||
import Styleguide
|
||||
|
||||
// TODO: Make view async and load based on the active tab.
|
||||
|
||||
struct ProjectView: HTML, Sendable {
|
||||
@Dependency(\.database) var database
|
||||
|
||||
@@ -58,7 +56,10 @@ struct ProjectView: HTML, Sendable {
|
||||
case .frictionRate:
|
||||
try await FrictionRateView(
|
||||
equipmentInfo: database.equipment.fetch(projectID),
|
||||
componentLosses: database.componentLoss.fetch(projectID), projectID: projectID)
|
||||
componentLosses: database.componentLoss.fetch(projectID),
|
||||
equivalentLengths: database.effectiveLength.fetchMax(projectID),
|
||||
projectID: projectID
|
||||
)
|
||||
case .ductSizing:
|
||||
div { "FIX ME!" }
|
||||
|
||||
@@ -75,135 +76,135 @@ struct ProjectView: HTML, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Update to use DaisyUI drawer.
|
||||
struct Sidebar: HTML {
|
||||
extension ProjectView {
|
||||
|
||||
let active: SiteRoute.View.ProjectRoute.DetailRoute.Tab
|
||||
let projectID: Project.ID
|
||||
let completedSteps: Project.CompletedSteps
|
||||
struct Sidebar: HTML {
|
||||
|
||||
var body: some HTML {
|
||||
let active: SiteRoute.View.ProjectRoute.DetailRoute.Tab
|
||||
let projectID: Project.ID
|
||||
let completedSteps: Project.CompletedSteps
|
||||
|
||||
div(.class("drawer-side is-drawer-close:overflow-visible")) {
|
||||
label(
|
||||
.for("my-drawer-1"), .init(name: "aria-label", value: "close sidebar"),
|
||||
.class("drawer-overlay")
|
||||
) {}
|
||||
var body: some HTML {
|
||||
|
||||
div(
|
||||
.class(
|
||||
"""
|
||||
flex min-h-full flex-col items-start bg-base-200
|
||||
is-drawer-close:min-w-[80px] is-drawer-open:min-w-[340px]
|
||||
"""
|
||||
)
|
||||
) {
|
||||
div(.class("drawer-side is-drawer-close:overflow-visible")) {
|
||||
label(
|
||||
.for("my-drawer-1"), .init(name: "aria-label", value: "close sidebar"),
|
||||
.class("drawer-overlay")
|
||||
) {}
|
||||
|
||||
ul(.class("w-full")) {
|
||||
div(
|
||||
.class(
|
||||
"""
|
||||
flex min-h-full flex-col items-start bg-base-200
|
||||
is-drawer-close:min-w-[80px] is-drawer-open:min-w-[340px]
|
||||
"""
|
||||
)
|
||||
) {
|
||||
|
||||
li(.class("w-full")) {
|
||||
div(
|
||||
.class("w-full is-drawer-close:tooltip is-drawer-close:tooltip-right"),
|
||||
.data("tip", value: "All Projects")
|
||||
) {
|
||||
a(
|
||||
.class(
|
||||
"""
|
||||
flex btn btn-secondary btn-square btn-block
|
||||
is-drawer-close:items-center
|
||||
"""
|
||||
),
|
||||
.hx.get(route: .project(.index)),
|
||||
.hx.target("body"),
|
||||
.hx.pushURL(true),
|
||||
.hx.swap(.outerHTML),
|
||||
ul(.class("w-full")) {
|
||||
|
||||
li(.class("w-full")) {
|
||||
div(
|
||||
.class("w-full is-drawer-close:tooltip is-drawer-close:tooltip-right"),
|
||||
.data("tip", value: "All Projects")
|
||||
) {
|
||||
div(.class("flex is-drawer-open:space-x-4")) {
|
||||
span { "<" }
|
||||
span(.class("is-drawer-close:hidden")) { "All Projects" }
|
||||
a(
|
||||
.class(
|
||||
"""
|
||||
flex btn btn-secondary btn-square btn-block
|
||||
is-drawer-close:items-center
|
||||
"""
|
||||
),
|
||||
.hx.get(route: .project(.index)),
|
||||
.hx.target("body"),
|
||||
.hx.pushURL(true),
|
||||
.hx.swap(.outerHTML),
|
||||
) {
|
||||
div(.class("flex is-drawer-open:space-x-4")) {
|
||||
span { "<" }
|
||||
span(.class("is-drawer-close:hidden")) { "All Projects" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIX: Move to user profile / settings page.
|
||||
li(.class("w-full is-drawer-close:hidden")) {
|
||||
div(.class("flex justify-between p-4")) {
|
||||
Label("Theme")
|
||||
input(.type(.checkbox), .class("toggle theme-controller"), .value("light"))
|
||||
// FIX: Move to user profile / settings page.
|
||||
li(.class("w-full is-drawer-close:hidden")) {
|
||||
div(.class("flex justify-between p-4")) {
|
||||
Label("Theme")
|
||||
input(.type(.checkbox), .class("toggle theme-controller"), .value("light"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li(.class("w-full")) {
|
||||
row(
|
||||
title: "Project",
|
||||
icon: .mapPin,
|
||||
route: .project(.detail(projectID, .index(tab: .project))),
|
||||
isComplete: true
|
||||
)
|
||||
.attributes(.class("btn-active"), when: active == .project)
|
||||
}
|
||||
li(.class("w-full")) {
|
||||
row(
|
||||
title: "Project",
|
||||
icon: .mapPin,
|
||||
route: .project(.detail(projectID, .index(tab: .project))),
|
||||
isComplete: true
|
||||
)
|
||||
.attributes(.class("btn-active"), when: active == .project)
|
||||
}
|
||||
|
||||
li(.class("w-full")) {
|
||||
row(
|
||||
title: "Rooms",
|
||||
icon: .doorClosed,
|
||||
route: .project(.detail(projectID, .rooms(.index))),
|
||||
isComplete: completedSteps.rooms
|
||||
)
|
||||
.attributes(.class("btn-active"), when: active == .rooms)
|
||||
}
|
||||
li(.class("w-full")) {
|
||||
row(
|
||||
title: "Rooms",
|
||||
icon: .doorClosed,
|
||||
route: .project(.detail(projectID, .rooms(.index))),
|
||||
isComplete: completedSteps.rooms
|
||||
)
|
||||
.attributes(.class("btn-active"), when: active == .rooms)
|
||||
}
|
||||
|
||||
li(.class("w-full")) {
|
||||
row(
|
||||
title: "Equivalent Lengths",
|
||||
icon: .rulerDimensionLine,
|
||||
route: .project(.detail(projectID, .equivalentLength(.index))),
|
||||
isComplete: completedSteps.equivalentLength
|
||||
)
|
||||
.attributes(.class("btn-active"), when: active == .equivalentLength)
|
||||
li(.class("w-full")) {
|
||||
row(
|
||||
title: "Equivalent Lengths",
|
||||
icon: .rulerDimensionLine,
|
||||
route: .project(.detail(projectID, .equivalentLength(.index))),
|
||||
isComplete: completedSteps.equivalentLength
|
||||
)
|
||||
.attributes(.class("btn-active"), when: active == .equivalentLength)
|
||||
|
||||
}
|
||||
li(.class("w-full")) {
|
||||
row(
|
||||
title: "Friction Rate",
|
||||
icon: .squareFunction,
|
||||
route: .project(.detail(projectID, .frictionRate(.index))),
|
||||
isComplete: completedSteps.frictionRate
|
||||
)
|
||||
.attributes(.class("btn-active"), when: active == .frictionRate)
|
||||
}
|
||||
li(.class("w-full")) {
|
||||
row(
|
||||
title: "Friction Rate",
|
||||
icon: .squareFunction,
|
||||
route: .project(.detail(projectID, .frictionRate(.index))),
|
||||
isComplete: completedSteps.frictionRate
|
||||
)
|
||||
.attributes(.class("btn-active"), when: active == .frictionRate)
|
||||
|
||||
}
|
||||
li(.class("w-full")) {
|
||||
row(
|
||||
title: "Duct Sizes", icon: .wind, href: "#", isComplete: false, hideIsComplete: true
|
||||
)
|
||||
.attributes(.class("btn-active"), when: active == .ductSizing)
|
||||
}
|
||||
li(.class("w-full")) {
|
||||
row(
|
||||
title: "Duct Sizes", icon: .wind, href: "#", isComplete: false, hideIsComplete: true
|
||||
)
|
||||
.attributes(.class("btn-active"), when: active == .ductSizing)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use SiteRoute.View routes as href.
|
||||
private func row(
|
||||
title: String,
|
||||
icon: SVG.Key,
|
||||
href: String,
|
||||
isComplete: Bool,
|
||||
hideIsComplete: Bool = false
|
||||
) -> some HTML<HTMLTag.div> {
|
||||
div(
|
||||
.class(
|
||||
"w-full is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
||||
),
|
||||
.data("tip", value: title)
|
||||
) {
|
||||
// TODO: Use SiteRoute.View routes as href.
|
||||
private func row(
|
||||
title: String,
|
||||
icon: SVG.Key,
|
||||
href: String,
|
||||
isComplete: Bool,
|
||||
hideIsComplete: Bool = false
|
||||
) -> some HTML<HTMLTag.a> {
|
||||
a(
|
||||
.class(
|
||||
"flex btn btn-soft btn-square btn-block is-drawer-open:justify-between is-drawer-close:items-center"
|
||||
"""
|
||||
flex w-full btn btn-soft btn-square btn-block
|
||||
is-drawer-open:justify-between is-drawer-close:items-center
|
||||
is-drawer-close:tooltip is-drawer-close:tooltip-right
|
||||
"""
|
||||
),
|
||||
.href(href)
|
||||
.href(href),
|
||||
.data("tip", value: title)
|
||||
) {
|
||||
div(.class("flex is-drawer-open:space-x-4")) {
|
||||
SVG(icon)
|
||||
@@ -226,18 +227,18 @@ struct Sidebar: HTML {
|
||||
.attributes(.class("is-drawer-close:text-green-400"), when: isComplete)
|
||||
.attributes(.class("is-drawer-close:text-error"), when: !isComplete && !hideIsComplete)
|
||||
}
|
||||
}
|
||||
|
||||
private func row(
|
||||
title: String,
|
||||
icon: SVG.Key,
|
||||
route: SiteRoute.View,
|
||||
isComplete: Bool,
|
||||
hideIsComplete: Bool = false
|
||||
) -> some HTML<HTMLTag.div> {
|
||||
row(
|
||||
title: title, icon: icon, href: SiteRoute.View.router.path(for: route),
|
||||
isComplete: isComplete, hideIsComplete: hideIsComplete
|
||||
)
|
||||
private func row(
|
||||
title: String,
|
||||
icon: SVG.Key,
|
||||
route: SiteRoute.View,
|
||||
isComplete: Bool,
|
||||
hideIsComplete: Bool = false
|
||||
) -> some HTML<HTMLTag.a> {
|
||||
row(
|
||||
title: title, icon: icon, href: SiteRoute.View.router.path(for: route),
|
||||
isComplete: isComplete, hideIsComplete: hideIsComplete
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,13 @@ struct RoomForm: HTML, Sendable {
|
||||
self.room = room
|
||||
}
|
||||
|
||||
var route: String {
|
||||
SiteRoute.View.router.path(
|
||||
for: .project(.detail(projectID, .rooms(.index)))
|
||||
)
|
||||
.appendingPath(room?.id)
|
||||
}
|
||||
|
||||
var body: some HTML {
|
||||
ModalForm(id: id, dismiss: dismiss) {
|
||||
h1(.class("text-3xl font-bold pb-6")) { "Room" }
|
||||
@@ -34,8 +41,8 @@ struct RoomForm: HTML, Sendable {
|
||||
.class("modal-backdrop"),
|
||||
.init(name: "method", value: "dialog"),
|
||||
room == nil
|
||||
? .hx.post(route: .project(.detail(projectID, .rooms(.index))))
|
||||
: .hx.patch(route: .project(.detail(projectID, .rooms(.index)))),
|
||||
? .hx.post(route)
|
||||
: .hx.patch(route),
|
||||
.hx.target("body"),
|
||||
.hx.swap(.outerHTML)
|
||||
) {
|
||||
|
||||
@@ -121,18 +121,22 @@ struct RoomsView: HTML, Sendable {
|
||||
Number(room.registerCount)
|
||||
}
|
||||
td {
|
||||
div(.class("flex justify-end space-x-6")) {
|
||||
TrashButton()
|
||||
.attributes(
|
||||
.hx.delete(
|
||||
route: .project(.detail(room.projectID, .rooms(.delete(id: room.id))))),
|
||||
.hx.target("closest tr"),
|
||||
.hx.confirm("Are you sure?")
|
||||
)
|
||||
EditButton()
|
||||
.attributes(
|
||||
.showModal(id: "roomForm_\(room.name)")
|
||||
)
|
||||
div(.class("flex justify-end")) {
|
||||
div(.class("join")) {
|
||||
TrashButton()
|
||||
.attributes(
|
||||
.class("join-item"),
|
||||
.hx.delete(
|
||||
route: .project(.detail(room.projectID, .rooms(.delete(id: room.id))))),
|
||||
.hx.target("closest tr"),
|
||||
.hx.confirm("Are you sure?")
|
||||
)
|
||||
EditButton()
|
||||
.attributes(
|
||||
.class("join-item"),
|
||||
.showModal(id: "roomForm_\(room.name)")
|
||||
)
|
||||
}
|
||||
}
|
||||
RoomForm(
|
||||
id: "roomForm_\(room.name)",
|
||||
|
||||
Reference in New Issue
Block a user