feat: Adds update route to equipment info, reorganizes views.
This commit is contained in:
@@ -67,6 +67,7 @@ let package = Package(
|
|||||||
name: "ManualDCore",
|
name: "ManualDCore",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
.product(name: "Fluent", package: "fluent"),
|
||||||
.product(name: "URLRouting", package: "swift-url-routing"),
|
.product(name: "URLRouting", package: "swift-url-routing"),
|
||||||
.product(name: "CasePaths", package: "swift-case-paths"),
|
.product(name: "CasePaths", package: "swift-case-paths"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ extension DatabaseClient {
|
|||||||
public var delete: @Sendable (EquipmentInfo.ID) async throws -> Void
|
public var delete: @Sendable (EquipmentInfo.ID) async throws -> Void
|
||||||
public var fetch: @Sendable (Project.ID) async throws -> EquipmentInfo?
|
public var fetch: @Sendable (Project.ID) async throws -> EquipmentInfo?
|
||||||
public var get: @Sendable (EquipmentInfo.ID) async throws -> EquipmentInfo?
|
public var get: @Sendable (EquipmentInfo.ID) async throws -> EquipmentInfo?
|
||||||
|
public var update: @Sendable (EquipmentInfo.Update) async throws -> EquipmentInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,12 +39,21 @@ extension DatabaseClient.Equipment {
|
|||||||
.filter("projectID", .equal, projectId)
|
.filter("projectID", .equal, projectId)
|
||||||
.first()
|
.first()
|
||||||
else {
|
else {
|
||||||
throw NotFoundError()
|
return nil
|
||||||
}
|
}
|
||||||
return try model.toDTO()
|
return try model.toDTO()
|
||||||
},
|
},
|
||||||
get: { id in
|
get: { id in
|
||||||
try await EquipmentModel.find(id, on: database).map { try $0.toDTO() }
|
try await EquipmentModel.find(id, on: database).map { try $0.toDTO() }
|
||||||
|
},
|
||||||
|
update: { request in
|
||||||
|
guard let model = try await EquipmentModel.find(request.id, on: database) else {
|
||||||
|
throw NotFoundError()
|
||||||
|
}
|
||||||
|
guard request.hasUpdates else { return try model.toDTO() }
|
||||||
|
try model.applyUpdates(request)
|
||||||
|
try await model.save(on: database)
|
||||||
|
return try model.toDTO()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -77,6 +87,30 @@ extension EquipmentInfo.Create {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension EquipmentInfo.Update {
|
||||||
|
var hasUpdates: Bool {
|
||||||
|
staticPressure != nil || heatingCFM != nil || coolingCFM != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate() throws(ValidationError) {
|
||||||
|
if let staticPressure {
|
||||||
|
guard staticPressure >= 0 else {
|
||||||
|
throw ValidationError("Equipment info static pressure should be greater than 0.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let heatingCFM {
|
||||||
|
guard heatingCFM >= 0 else {
|
||||||
|
throw ValidationError("Equipment info heating CFM should be greater than 0.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let coolingCFM {
|
||||||
|
guard coolingCFM >= 0 else {
|
||||||
|
throw ValidationError("Equipment info heating CFM should be greater than 0.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension EquipmentInfo {
|
extension EquipmentInfo {
|
||||||
|
|
||||||
struct Migrate: AsyncMigration {
|
struct Migrate: AsyncMigration {
|
||||||
@@ -159,4 +193,17 @@ final class EquipmentModel: Model, @unchecked Sendable {
|
|||||||
updatedAt: updatedAt!
|
updatedAt: updatedAt!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyUpdates(_ updates: EquipmentInfo.Update) throws {
|
||||||
|
try updates.validate()
|
||||||
|
if let staticPressure = updates.staticPressure {
|
||||||
|
self.staticPressure = staticPressure
|
||||||
|
}
|
||||||
|
if let heatingCFM = updates.heatingCFM {
|
||||||
|
self.heatingCFM = heatingCFM
|
||||||
|
}
|
||||||
|
if let coolingCFM = updates.coolingCFM {
|
||||||
|
self.coolingCFM = coolingCFM
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ extension ComponentPressureLoss {
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.value = value
|
self.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return's commonly used default component pressure losses.
|
||||||
|
public static func `default`(projectID: Project.ID) -> [Self] {
|
||||||
|
[
|
||||||
|
.init(projectID: projectID, name: "supply-outlet", value: 0.03),
|
||||||
|
.init(projectID: projectID, name: "return-grille", value: 0.03),
|
||||||
|
.init(projectID: projectID, name: "balancing-damper", value: 0.03),
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,25 @@ extension EquipmentInfo {
|
|||||||
self.coolingCFM = coolingCFM
|
self.coolingCFM = coolingCFM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
|
public let id: EquipmentInfo.ID
|
||||||
|
public let staticPressure: Double?
|
||||||
|
public let heatingCFM: Int?
|
||||||
|
public let coolingCFM: Int?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
id: EquipmentInfo.ID,
|
||||||
|
staticPressure: Double? = nil,
|
||||||
|
heatingCFM: Int? = nil,
|
||||||
|
coolingCFM: Int? = nil
|
||||||
|
) {
|
||||||
|
self.id = id
|
||||||
|
self.staticPressure = staticPressure
|
||||||
|
self.heatingCFM = heatingCFM
|
||||||
|
self.coolingCFM = coolingCFM
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import CasePathsCore
|
import CasePathsCore
|
||||||
|
import FluentKit
|
||||||
import Foundation
|
import Foundation
|
||||||
@preconcurrency import URLRouting
|
@preconcurrency import URLRouting
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import CasePathsCore
|
import CasePathsCore
|
||||||
|
import FluentKit
|
||||||
import Foundation
|
import Foundation
|
||||||
@preconcurrency import URLRouting
|
@preconcurrency import URLRouting
|
||||||
|
|
||||||
@@ -10,9 +11,7 @@ extension SiteRoute {
|
|||||||
case login(LoginRoute)
|
case login(LoginRoute)
|
||||||
case signup(SignupRoute)
|
case signup(SignupRoute)
|
||||||
case project(ProjectRoute)
|
case project(ProjectRoute)
|
||||||
// case frictionRate(FrictionRateRoute)
|
|
||||||
case effectiveLength(EffectiveLengthRoute)
|
case effectiveLength(EffectiveLengthRoute)
|
||||||
// case user(UserRoute)
|
|
||||||
|
|
||||||
public static let router = OneOf {
|
public static let router = OneOf {
|
||||||
Route(.case(Self.login)) {
|
Route(.case(Self.login)) {
|
||||||
@@ -24,15 +23,9 @@ extension SiteRoute {
|
|||||||
Route(.case(Self.project)) {
|
Route(.case(Self.project)) {
|
||||||
SiteRoute.View.ProjectRoute.router
|
SiteRoute.View.ProjectRoute.router
|
||||||
}
|
}
|
||||||
// Route(.case(Self.frictionRate)) {
|
|
||||||
// SiteRoute.View.FrictionRateRoute.router
|
|
||||||
// }
|
|
||||||
Route(.case(Self.effectiveLength)) {
|
Route(.case(Self.effectiveLength)) {
|
||||||
SiteRoute.View.EffectiveLengthRoute.router
|
SiteRoute.View.EffectiveLengthRoute.router
|
||||||
}
|
}
|
||||||
// Route(.case(Self.user)) {
|
|
||||||
// SiteRoute.View.UserRoute.router
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +36,11 @@ extension SiteRoute.View {
|
|||||||
case detail(Project.ID, DetailRoute)
|
case detail(Project.ID, DetailRoute)
|
||||||
case form(dismiss: Bool = false)
|
case form(dismiss: Bool = false)
|
||||||
case index
|
case index
|
||||||
case page(page: Int = 1, limit: Int = 25)
|
case page(PageRequest)
|
||||||
|
|
||||||
|
public static func page(page: Int, per limit: Int) -> Self {
|
||||||
|
.page(.init(page: page, per: limit))
|
||||||
|
}
|
||||||
|
|
||||||
static let rootPath = "projects"
|
static let rootPath = "projects"
|
||||||
|
|
||||||
@@ -83,7 +80,7 @@ extension SiteRoute.View {
|
|||||||
Path { rootPath }
|
Path { rootPath }
|
||||||
Method.get
|
Method.get
|
||||||
}
|
}
|
||||||
Route(.case(Self.page(page:limit:))) {
|
Route(.case(Self.page)) {
|
||||||
Path {
|
Path {
|
||||||
rootPath
|
rootPath
|
||||||
"page"
|
"page"
|
||||||
@@ -91,8 +88,9 @@ extension SiteRoute.View {
|
|||||||
Method.get
|
Method.get
|
||||||
Query {
|
Query {
|
||||||
Field("page", default: 1) { Int.parser() }
|
Field("page", default: 1) { Int.parser() }
|
||||||
Field("limit", default: 25) { Int.parser() }
|
Field("per", default: 25) { Int.parser() }
|
||||||
}
|
}
|
||||||
|
.map(.memberwise(PageRequest.init))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,6 +100,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
|
|
||||||
public enum DetailRoute: Equatable, Sendable {
|
public enum DetailRoute: Equatable, Sendable {
|
||||||
case index
|
case index
|
||||||
|
case equipment(EquipmentInfoRoute)
|
||||||
case frictionRate(FrictionRateRoute)
|
case frictionRate(FrictionRateRoute)
|
||||||
case rooms(RoomRoute)
|
case rooms(RoomRoute)
|
||||||
|
|
||||||
@@ -109,6 +108,9 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Route(.case(Self.index)) {
|
Route(.case(Self.index)) {
|
||||||
Method.get
|
Method.get
|
||||||
}
|
}
|
||||||
|
Route(.case(Self.equipment)) {
|
||||||
|
EquipmentInfoRoute.router
|
||||||
|
}
|
||||||
Route(.case(Self.frictionRate)) {
|
Route(.case(Self.frictionRate)) {
|
||||||
FrictionRateRoute.router
|
FrictionRateRoute.router
|
||||||
}
|
}
|
||||||
@@ -160,6 +162,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
|
|
||||||
public enum FrictionRateRoute: Equatable, Sendable {
|
public enum FrictionRateRoute: Equatable, Sendable {
|
||||||
case index
|
case index
|
||||||
|
// TODO: Remove form or move equipment / component losses routes here.
|
||||||
case form(FormType, dismiss: Bool = false)
|
case form(FormType, dismiss: Bool = false)
|
||||||
|
|
||||||
static let rootPath = "friction-rate"
|
static let rootPath = "friction-rate"
|
||||||
@@ -187,6 +190,64 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
case componentPressureLoss
|
case componentPressureLoss
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum EquipmentInfoRoute: Equatable, Sendable {
|
||||||
|
case index
|
||||||
|
case form(dismiss: Bool)
|
||||||
|
case submit(EquipmentInfo.Create)
|
||||||
|
case update(EquipmentInfo.Update)
|
||||||
|
|
||||||
|
static let rootPath = "equipment"
|
||||||
|
|
||||||
|
public static let router = OneOf {
|
||||||
|
Route(.case(Self.index)) {
|
||||||
|
Path { rootPath }
|
||||||
|
Method.get
|
||||||
|
}
|
||||||
|
Route(.case(Self.form)) {
|
||||||
|
Path {
|
||||||
|
rootPath
|
||||||
|
"create"
|
||||||
|
}
|
||||||
|
Method.get
|
||||||
|
Query {
|
||||||
|
Field("dismiss", default: true) { Bool.parser() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Route(.case(Self.submit)) {
|
||||||
|
Path { rootPath }
|
||||||
|
Method.post
|
||||||
|
Body {
|
||||||
|
FormData {
|
||||||
|
Field("projectID") { Project.ID.parser() }
|
||||||
|
Field("staticPressure") { Double.parser() }
|
||||||
|
Field("heatingCFM") { Int.parser() }
|
||||||
|
Field("coolingCFM") { Int.parser() }
|
||||||
|
}
|
||||||
|
.map(.memberwise(EquipmentInfo.Create.init))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Route(.case(Self.update)) {
|
||||||
|
Path { rootPath }
|
||||||
|
Method.patch
|
||||||
|
Body {
|
||||||
|
FormData {
|
||||||
|
Field("id") { EquipmentInfo.ID.parser() }
|
||||||
|
Optionally {
|
||||||
|
Field("staticPressure", default: nil) { Double.parser() }
|
||||||
|
}
|
||||||
|
Optionally {
|
||||||
|
Field("heatingCFM", default: nil) { Int.parser() }
|
||||||
|
}
|
||||||
|
Optionally {
|
||||||
|
Field("coolingCFM", default: nil) { Int.parser() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map(.memberwise(EquipmentInfo.Update.init))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SiteRoute.View {
|
extension SiteRoute.View {
|
||||||
@@ -313,3 +374,9 @@ extension SiteRoute.View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension PageRequest: @retroactive Equatable {
|
||||||
|
public static func == (lhs: FluentKit.PageRequest, rhs: FluentKit.PageRequest) -> Bool {
|
||||||
|
lhs.page == rhs.page && lhs.per == rhs.per
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ public struct CancelButton: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct EditButton: HTML, Sendable {
|
public struct EditButton: HTML, Sendable {
|
||||||
let title: String
|
let title: String?
|
||||||
let type: HTMLAttribute<HTMLTag.button>.ButtonType
|
let type: HTMLAttribute<HTMLTag.button>.ButtonType
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
title: String = "Edit",
|
title: String? = nil,
|
||||||
type: HTMLAttribute<HTMLTag.button>.ButtonType = .button
|
type: HTMLAttribute<HTMLTag.button>.ButtonType = .button
|
||||||
) {
|
) {
|
||||||
self.title = title
|
self.title = title
|
||||||
@@ -68,13 +68,15 @@ public struct EditButton: HTML, Sendable {
|
|||||||
button(
|
button(
|
||||||
.class(
|
.class(
|
||||||
"""
|
"""
|
||||||
text-white font-bold text-xl bg-blue-500 hover:bg-blue-600 px-4 py-2 rounded-lg shadow-lg
|
btn btn-primary
|
||||||
"""
|
"""
|
||||||
),
|
),
|
||||||
.type(type)
|
.type(type)
|
||||||
) {
|
) {
|
||||||
div(.class("flex")) {
|
div(.class("flex")) {
|
||||||
span(.class("pe-2")) { title }
|
if let title {
|
||||||
|
span(.class("pe-2")) { title }
|
||||||
|
}
|
||||||
SVG(.squarePen)
|
SVG(.squarePen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,6 +88,9 @@ public struct PlusButton: HTML, Sendable {
|
|||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public var body: some HTML<HTMLTag.button> {
|
public var body: some HTML<HTMLTag.button> {
|
||||||
button(.type(.button)) { SVG(.circlePlus) }
|
button(
|
||||||
|
.type(.button),
|
||||||
|
.class("btn btn-primary")
|
||||||
|
) { SVG(.circlePlus) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Fluent
|
|||||||
import ManualDCore
|
import ManualDCore
|
||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
|
// FIX: Remove these, not used currently.
|
||||||
extension DatabaseClient.Projects {
|
extension DatabaseClient.Projects {
|
||||||
|
|
||||||
func fetchPage(
|
func fetchPage(
|
||||||
@@ -21,6 +22,16 @@ extension DatabaseClient.Projects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension DatabaseClient.ComponentLoss {
|
||||||
|
|
||||||
|
func createDefaults(projectID: Project.ID) async throws {
|
||||||
|
let defaults = ComponentPressureLoss.Create.default(projectID: projectID)
|
||||||
|
for loss in defaults {
|
||||||
|
_ = try await create(loss)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension PageRequest {
|
extension PageRequest {
|
||||||
static func next<T>(_ currentPage: Page<T>) -> Self {
|
static func next<T>(_ currentPage: Page<T>) -> Self {
|
||||||
.init(page: currentPage.metadata.page + 1, per: currentPage.metadata.per)
|
.init(page: currentPage.metadata.page + 1, per: currentPage.metadata.per)
|
||||||
|
|||||||
@@ -81,9 +81,8 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
return request.view {
|
return request.view {
|
||||||
ProjectsTable(userID: user.id, projects: projects)
|
ProjectsTable(userID: user.id, projects: projects)
|
||||||
}
|
}
|
||||||
case .page(let page, let limit):
|
case .page(let page):
|
||||||
let projects = try await database.projects.fetchPage(
|
let projects = try await database.projects.fetch(user.id, page)
|
||||||
userID: user.id, page: page, limit: limit)
|
|
||||||
return ProjectsTable(userID: user.id, projects: projects)
|
return ProjectsTable(userID: user.id, projects: projects)
|
||||||
|
|
||||||
case .form(let dismiss):
|
case .form(let dismiss):
|
||||||
@@ -91,6 +90,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
|
|
||||||
case .create(let form):
|
case .create(let form):
|
||||||
let project = try await database.projects.create(user.id, form)
|
let project = try await database.projects.create(user.id, form)
|
||||||
|
try await database.componentLoss.createDefaults(projectID: project.id)
|
||||||
return request.view {
|
return request.view {
|
||||||
ProjectView(projectID: project.id, activeTab: .projects) {
|
ProjectView(projectID: project.id, activeTab: .projects) {
|
||||||
ProjectDetail(project: project)
|
ProjectDetail(project: project)
|
||||||
@@ -106,6 +106,8 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
ProjectDetail(project: project)
|
ProjectDetail(project: project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case .equipment(let route):
|
||||||
|
return try await route.renderView(on: request, projectID: projectID)
|
||||||
case .frictionRate(let route):
|
case .frictionRate(let route):
|
||||||
return try await route.renderView(on: request, projectID: projectID)
|
return try await route.renderView(on: request, projectID: projectID)
|
||||||
|
|
||||||
@@ -121,6 +123,30 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SiteRoute.View.ProjectRoute.EquipmentInfoRoute {
|
||||||
|
func renderView(
|
||||||
|
on request: ViewController.Request,
|
||||||
|
projectID: Project.ID
|
||||||
|
) async throws -> AnySendableHTML {
|
||||||
|
@Dependency(\.database) var database
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case .index:
|
||||||
|
let equipment = try await database.equipment.fetch(projectID)
|
||||||
|
return EquipmentInfoView(equipmentInfo: equipment, projectID: projectID)
|
||||||
|
case .form(let dismiss):
|
||||||
|
let equipment = try await database.equipment.fetch(projectID)
|
||||||
|
return EquipmentInfoForm(dismiss: dismiss, projectID: projectID, equipmentInfo: equipment)
|
||||||
|
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)
|
||||||
|
return EquipmentInfoView(equipmentInfo: equipment, projectID: projectID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension SiteRoute.View.ProjectRoute.RoomRoute {
|
extension SiteRoute.View.ProjectRoute.RoomRoute {
|
||||||
func renderView(
|
func renderView(
|
||||||
on request: ViewController.Request,
|
on request: ViewController.Request,
|
||||||
@@ -162,18 +188,24 @@ extension SiteRoute.View.ProjectRoute.FrictionRateRoute {
|
|||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
case .index:
|
case .index:
|
||||||
|
let equipment = try await database.equipment.fetch(projectID)
|
||||||
let componentLosses = try await database.componentLoss.fetch(projectID)
|
let componentLosses = try await database.componentLoss.fetch(projectID)
|
||||||
|
|
||||||
return request.view {
|
return request.view {
|
||||||
ProjectView(projectID: projectID, activeTab: .frictionRate) {
|
ProjectView(projectID: projectID, activeTab: .frictionRate) {
|
||||||
FrictionRateView(componentLosses: componentLosses, projectID: projectID)
|
FrictionRateView(
|
||||||
|
equipmentInfo: equipment,
|
||||||
|
componentLosses: componentLosses,
|
||||||
|
projectID: projectID
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .form(let type, let dismiss):
|
case .form(let type, let dismiss):
|
||||||
// FIX: Forms need to reference existing items.
|
// FIX: Forms need to reference existing items.
|
||||||
switch type {
|
switch type {
|
||||||
case .equipmentInfo:
|
case .equipmentInfo:
|
||||||
return EquipmentForm(dismiss: dismiss, projectID: projectID)
|
return div { "REMOVE ME!" }
|
||||||
|
// return EquipmentForm(dismiss: dismiss, projectID: projectID)
|
||||||
case .componentPressureLoss:
|
case .componentPressureLoss:
|
||||||
return ComponentLossForm(dismiss: dismiss, projectID: projectID)
|
return ComponentLossForm(dismiss: dismiss, projectID: projectID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
import Styleguide
|
||||||
|
|
||||||
|
// TODO: Have form hold onto equipment info model to edit.
|
||||||
|
struct EquipmentInfoForm: HTML, Sendable {
|
||||||
|
|
||||||
|
let dismiss: Bool
|
||||||
|
let projectID: Project.ID
|
||||||
|
let equipmentInfo: EquipmentInfo?
|
||||||
|
|
||||||
|
var staticPressure: String {
|
||||||
|
guard let staticPressure = equipmentInfo?.staticPressure else {
|
||||||
|
return "0.5"
|
||||||
|
}
|
||||||
|
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 body: some HTML {
|
||||||
|
ModalForm(id: "equipmentForm", dismiss: dismiss) {
|
||||||
|
h1(.class("text-3xl font-bold pb-6 ps-2")) { "Equipment Info" }
|
||||||
|
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.target("#equipmentInfo"),
|
||||||
|
.hx.swap(.outerHTML)
|
||||||
|
) {
|
||||||
|
input(.class("hidden"), .name("projectID"), .value("\(projectID)"))
|
||||||
|
|
||||||
|
if let equipmentInfo {
|
||||||
|
input(.class("hidden"), .name("id"), .value("\(equipmentInfo.id)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
label(.for("staticPressure")) { "Static Pressure" }
|
||||||
|
Input(id: "staticPressure", placeholder: "Static pressure")
|
||||||
|
.attributes(
|
||||||
|
.type(.number), .value(staticPressure), .min("0"), .max("1.0"), .step("0.1")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
label(.for("heatingCFM")) { "Heating CFM" }
|
||||||
|
Input(id: "heatingCFM", placeholder: "CFM")
|
||||||
|
.attributes(.type(.number), .min("0"), .value(heatingCFM))
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
label(.for("coolingCFM")) { "Cooling CFM" }
|
||||||
|
Input(id: "coolingCFM", placeholder: "CFM")
|
||||||
|
.attributes(.type(.number), .min("0"), .value(coolingCFM))
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
div {}
|
||||||
|
div(.class("space-x-4")) {
|
||||||
|
CancelButton()
|
||||||
|
.attributes(
|
||||||
|
.hx.get(
|
||||||
|
route: .project(
|
||||||
|
.detail(projectID, .equipment(.form(dismiss: true)))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
.hx.target("#equipmentForm"),
|
||||||
|
.hx.swap(.outerHTML)
|
||||||
|
)
|
||||||
|
SubmitButton(title: "Save")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
import Styleguide
|
||||||
|
|
||||||
|
struct EquipmentInfoView: HTML, Sendable {
|
||||||
|
let equipmentInfo: EquipmentInfo?
|
||||||
|
var projectID: Project.ID
|
||||||
|
|
||||||
|
var body: some HTML {
|
||||||
|
div(
|
||||||
|
.class("space-y-4 border border-gray-200 rounded-lg shadow-lg p-4"),
|
||||||
|
.id("equipmentInfo")
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row {
|
||||||
|
h1(.class("text-2xl font-bold")) { "Equipment Info" }
|
||||||
|
|
||||||
|
if equipmentInfo != nil {
|
||||||
|
EditButton()
|
||||||
|
.attributes(
|
||||||
|
.hx.get(route: .project(.detail(projectID, .equipment(.form(dismiss: false))))),
|
||||||
|
.hx.target("#equipmentForm"),
|
||||||
|
.hx.swap(.outerHTML)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let equipmentInfo {
|
||||||
|
|
||||||
|
Row {
|
||||||
|
Label { "Static Pressure" }
|
||||||
|
Number(equipmentInfo.staticPressure)
|
||||||
|
}
|
||||||
|
.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: nil)
|
||||||
|
} else {
|
||||||
|
EquipmentInfoForm(dismiss: false, projectID: projectID, equipmentInfo: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import Elementary
|
|
||||||
import ManualDCore
|
|
||||||
import Styleguide
|
|
||||||
|
|
||||||
// TODO: Have form hold onto equipment info model to edit.
|
|
||||||
struct EquipmentForm: HTML, Sendable {
|
|
||||||
|
|
||||||
let dismiss: Bool
|
|
||||||
let projectID: Project.ID
|
|
||||||
|
|
||||||
var body: some HTML {
|
|
||||||
ModalForm(id: "equipmentForm", dismiss: dismiss) {
|
|
||||||
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")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
label(.for("heatingCFM")) { "Heating CFM" }
|
|
||||||
Input(id: "heatingCFM", placeholder: "CFM")
|
|
||||||
.attributes(.type(.number), .min("0"))
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
label(.for("coolingCFM")) { "Cooling CFM" }
|
|
||||||
Input(id: "coolingCFM", placeholder: "CFM")
|
|
||||||
.attributes(.type(.number), .min("0"))
|
|
||||||
}
|
|
||||||
Row {
|
|
||||||
div {}
|
|
||||||
div(.class("space-x-4")) {
|
|
||||||
CancelButton()
|
|
||||||
.attributes(
|
|
||||||
.hx.get(
|
|
||||||
route: .project(
|
|
||||||
.detail(projectID, .frictionRate(.form(.equipmentInfo, dismiss: true)))
|
|
||||||
)
|
|
||||||
),
|
|
||||||
.hx.target("#equipmentForm"),
|
|
||||||
.hx.swap(.outerHTML)
|
|
||||||
)
|
|
||||||
SubmitButton(title: "Save")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import Elementary
|
|
||||||
import ManualDCore
|
|
||||||
import Styleguide
|
|
||||||
|
|
||||||
struct EquipmentInfoView: HTML, Sendable {
|
|
||||||
let equipmentInfo: EquipmentInfo
|
|
||||||
var projectID: Project.ID { equipmentInfo.projectID }
|
|
||||||
|
|
||||||
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" }
|
|
||||||
Number(equipmentInfo.staticPressure)
|
|
||||||
}
|
|
||||||
.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"))
|
|
||||||
|
|
||||||
Row {
|
|
||||||
div {}
|
|
||||||
EditButton()
|
|
||||||
.attributes(
|
|
||||||
.hx.get(route: .project(.detail(projectID, .frictionRate(.form(.equipmentInfo))))),
|
|
||||||
.hx.target("#equipmentForm"),
|
|
||||||
.hx.swap(.outerHTML)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div(.id("equipmentForm")) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,13 +4,14 @@ import Styleguide
|
|||||||
|
|
||||||
struct FrictionRateView: HTML, Sendable {
|
struct FrictionRateView: HTML, Sendable {
|
||||||
|
|
||||||
|
let equipmentInfo: EquipmentInfo?
|
||||||
let componentLosses: [ComponentPressureLoss]
|
let componentLosses: [ComponentPressureLoss]
|
||||||
let projectID: Project.ID
|
let projectID: Project.ID
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
div(.class("p-4 space-y-6")) {
|
div(.class("p-4 space-y-6")) {
|
||||||
h1(.class("text-4xl font-bold pb-6")) { "Friction Rate" }
|
h1(.class("text-4xl font-bold pb-6")) { "Friction Rate" }
|
||||||
EquipmentInfoView(equipmentInfo: EquipmentInfo.mock)
|
EquipmentInfoView(equipmentInfo: equipmentInfo, projectID: projectID)
|
||||||
ComponentPressureLossesView(
|
ComponentPressureLossesView(
|
||||||
componentPressureLosses: componentLosses, projectID: projectID
|
componentPressureLosses: componentLosses, projectID: projectID
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ extension ProjectsTable {
|
|||||||
// if there are more pages left.
|
// if there are more pages left.
|
||||||
if projects.metadata.pageCount > projects.metadata.page {
|
if projects.metadata.pageCount > projects.metadata.page {
|
||||||
tr(
|
tr(
|
||||||
.hx.get(route: .project(.page(page: projects.metadata.page + 1, limit: 25))),
|
.hx.get(route: .project(.page(.next(projects)))),
|
||||||
.hx.trigger(.event(.revealed)),
|
.hx.trigger(.event(.revealed)),
|
||||||
.hx.swap(.outerHTML),
|
.hx.swap(.outerHTML),
|
||||||
.hx.target("this"),
|
.hx.target("this"),
|
||||||
|
|||||||
Reference in New Issue
Block a user