feat: Updates form routes and database routes to use id's in the url path.
This commit is contained in:
@@ -5271,8 +5271,8 @@
|
|||||||
.mx-2 {
|
.mx-2 {
|
||||||
margin-inline: calc(var(--spacing) * 2);
|
margin-inline: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.mx-4 {
|
.mx-auto {
|
||||||
margin-inline: calc(var(--spacing) * 4);
|
margin-inline: auto;
|
||||||
}
|
}
|
||||||
.file-input-ghost {
|
.file-input-ghost {
|
||||||
@layer daisyui.l1.l2 {
|
@layer daisyui.l1.l2 {
|
||||||
@@ -5569,6 +5569,15 @@
|
|||||||
border-width: var(--border, 1px) 0 var(--border, 1px) var(--border, 1px);
|
border-width: var(--border, 1px) 0 var(--border, 1px) var(--border, 1px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.ms-4 {
|
||||||
|
margin-inline-start: calc(var(--spacing) * 4);
|
||||||
|
}
|
||||||
|
.ms-6 {
|
||||||
|
margin-inline-start: calc(var(--spacing) * 6);
|
||||||
|
}
|
||||||
|
.ms-8 {
|
||||||
|
margin-inline-start: calc(var(--spacing) * 8);
|
||||||
|
}
|
||||||
.me-4 {
|
.me-4 {
|
||||||
margin-inline-end: calc(var(--spacing) * 4);
|
margin-inline-end: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
@@ -6745,6 +6754,9 @@
|
|||||||
.grid-cols-1 {
|
.grid-cols-1 {
|
||||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
.grid-cols-2 {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
.grid-cols-5 {
|
.grid-cols-5 {
|
||||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -6754,6 +6766,15 @@
|
|||||||
.flex-wrap {
|
.flex-wrap {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
.items-baseline {
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
.items-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.items-end {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
.items-start {
|
.items-start {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
@@ -7793,6 +7814,9 @@
|
|||||||
.ps-2 {
|
.ps-2 {
|
||||||
padding-inline-start: calc(var(--spacing) * 2);
|
padding-inline-start: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
|
.ps-8 {
|
||||||
|
padding-inline-start: calc(var(--spacing) * 8);
|
||||||
|
}
|
||||||
.file-input-xl {
|
.file-input-xl {
|
||||||
@layer daisyui.l1.l2 {
|
@layer daisyui.l1.l2 {
|
||||||
padding-inline-end: calc(0.25rem * 6);
|
padding-inline-end: calc(0.25rem * 6);
|
||||||
@@ -9357,9 +9381,9 @@
|
|||||||
outline-color: var(--color-indigo-600);
|
outline-color: var(--color-indigo-600);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.md\:hidden {
|
.md\:grid-cols-1 {
|
||||||
@media (width >= 48rem) {
|
@media (width >= 48rem) {
|
||||||
display: none;
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.md\:grid-cols-2 {
|
.md\:grid-cols-2 {
|
||||||
@@ -9420,9 +9444,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.lg\:hidden {
|
.lg\:grid-cols-2 {
|
||||||
@media (width >= 64rem) {
|
@media (width >= 64rem) {
|
||||||
display: none;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.lg\:grid-cols-3 {
|
.lg\:grid-cols-3 {
|
||||||
@@ -9430,6 +9454,16 @@
|
|||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.xl\:grid-cols-2 {
|
||||||
|
@media (width >= 80rem) {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.xl\:grid-cols-3 {
|
||||||
|
@media (width >= 80rem) {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
.dark\:text-white {
|
.dark\:text-white {
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
color: var(--color-white);
|
color: var(--color-white);
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ extension DatabaseClient {
|
|||||||
public var delete: @Sendable (ComponentPressureLoss.ID) async throws -> Void
|
public var delete: @Sendable (ComponentPressureLoss.ID) async throws -> Void
|
||||||
public var fetch: @Sendable (Project.ID) async throws -> [ComponentPressureLoss]
|
public var fetch: @Sendable (Project.ID) async throws -> [ComponentPressureLoss]
|
||||||
public var get: @Sendable (ComponentPressureLoss.ID) async throws -> ComponentPressureLoss?
|
public var get: @Sendable (ComponentPressureLoss.ID) async throws -> ComponentPressureLoss?
|
||||||
|
public var update:
|
||||||
|
@Sendable (ComponentPressureLoss.ID, ComponentPressureLoss.Update) async throws ->
|
||||||
|
ComponentPressureLoss
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +46,17 @@ extension DatabaseClient.ComponentLoss {
|
|||||||
},
|
},
|
||||||
get: { id in
|
get: { id in
|
||||||
try await ComponentLossModel.find(id, on: database).map { try $0.toDTO() }
|
try await ComponentLossModel.find(id, on: database).map { try $0.toDTO() }
|
||||||
|
},
|
||||||
|
update: { id, updates in
|
||||||
|
try updates.validate()
|
||||||
|
guard let model = try await ComponentLossModel.find(id, on: database) else {
|
||||||
|
throw NotFoundError()
|
||||||
|
}
|
||||||
|
model.applyUpdates(updates)
|
||||||
|
if model.hasChanges {
|
||||||
|
try await model.save(on: database)
|
||||||
|
}
|
||||||
|
return try model.toDTO()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -68,6 +82,24 @@ extension ComponentPressureLoss.Create {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ComponentPressureLoss.Update {
|
||||||
|
func validate() throws(ValidationError) {
|
||||||
|
if let name {
|
||||||
|
guard !name.isEmpty else {
|
||||||
|
throw ValidationError("Component loss name should not be empty.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let value {
|
||||||
|
guard value > 0 else {
|
||||||
|
throw ValidationError("Component loss value should be greater than 0.")
|
||||||
|
}
|
||||||
|
guard value < 1.0 else {
|
||||||
|
throw ValidationError("Component loss value should be less than 1.0.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension ComponentPressureLoss {
|
extension ComponentPressureLoss {
|
||||||
struct Migrate: AsyncMigration {
|
struct Migrate: AsyncMigration {
|
||||||
let name = "CreateComponentLoss"
|
let name = "CreateComponentLoss"
|
||||||
@@ -142,4 +174,13 @@ final class ComponentLossModel: Model, @unchecked Sendable {
|
|||||||
updatedAt: updatedAt!
|
updatedAt: updatedAt!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyUpdates(_ updates: ComponentPressureLoss.Update) {
|
||||||
|
if let name = updates.name, name != self.name {
|
||||||
|
self.name = name
|
||||||
|
}
|
||||||
|
if let value = updates.value, value != self.value {
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ extension DatabaseClient {
|
|||||||
public var fetch: @Sendable (Project.ID) async throws -> [EffectiveLength]
|
public var fetch: @Sendable (Project.ID) async throws -> [EffectiveLength]
|
||||||
public var fetchMax: @Sendable (Project.ID) async throws -> EffectiveLength.MaxContainer
|
public var fetchMax: @Sendable (Project.ID) async throws -> EffectiveLength.MaxContainer
|
||||||
public var get: @Sendable (EffectiveLength.ID) async throws -> EffectiveLength?
|
public var get: @Sendable (EffectiveLength.ID) async throws -> EffectiveLength?
|
||||||
public var update: @Sendable (EffectiveLength.Update) async throws -> EffectiveLength
|
public var update:
|
||||||
|
@Sendable (EffectiveLength.ID, EffectiveLength.Update) async throws -> EffectiveLength
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,11 +60,12 @@ extension DatabaseClient.EffectiveLengthClient: TestDependencyKey {
|
|||||||
get: { id in
|
get: { id in
|
||||||
try await EffectiveLengthModel.find(id, on: database).map { try $0.toDTO() }
|
try await EffectiveLengthModel.find(id, on: database).map { try $0.toDTO() }
|
||||||
},
|
},
|
||||||
update: { updates in
|
update: { id, updates in
|
||||||
guard let model = try await EffectiveLengthModel.find(updates.id, on: database) else {
|
guard let model = try await EffectiveLengthModel.find(id, on: database) else {
|
||||||
throw NotFoundError()
|
throw NotFoundError()
|
||||||
}
|
}
|
||||||
if try model.applyUpdates(updates) {
|
try model.applyUpdates(updates)
|
||||||
|
if model.hasChanges {
|
||||||
try await model.save(on: database)
|
try await model.save(on: database)
|
||||||
}
|
}
|
||||||
return try model.toDTO()
|
return try model.toDTO()
|
||||||
@@ -184,24 +186,18 @@ final class EffectiveLengthModel: Model, @unchecked Sendable {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyUpdates(_ updates: EffectiveLength.Update) throws -> Bool {
|
func applyUpdates(_ updates: EffectiveLength.Update) throws {
|
||||||
var hasUpdates = false
|
|
||||||
if let name = updates.name, name != self.name {
|
if let name = updates.name, name != self.name {
|
||||||
hasUpdates = true
|
|
||||||
self.name = name
|
self.name = name
|
||||||
}
|
}
|
||||||
if let type = updates.type, type.rawValue != self.type {
|
if let type = updates.type, type.rawValue != self.type {
|
||||||
hasUpdates = true
|
|
||||||
self.type = type.rawValue
|
self.type = type.rawValue
|
||||||
}
|
}
|
||||||
if let straightLengths = updates.straightLengths, straightLengths != self.straightLengths {
|
if let straightLengths = updates.straightLengths, straightLengths != self.straightLengths {
|
||||||
hasUpdates = true
|
|
||||||
self.straightLengths = straightLengths
|
self.straightLengths = straightLengths
|
||||||
}
|
}
|
||||||
if let groups = updates.groups {
|
if let groups = updates.groups {
|
||||||
hasUpdates = true
|
|
||||||
self.groups = try JSONEncoder().encode(groups)
|
self.groups = try JSONEncoder().encode(groups)
|
||||||
}
|
}
|
||||||
return hasUpdates
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ 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
|
public var update:
|
||||||
|
@Sendable (EquipmentInfo.ID, EquipmentInfo.Update) async throws -> EquipmentInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,13 +47,15 @@ extension DatabaseClient.Equipment {
|
|||||||
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
|
update: { id, updates in
|
||||||
guard let model = try await EquipmentModel.find(request.id, on: database) else {
|
guard let model = try await EquipmentModel.find(id, on: database) else {
|
||||||
throw NotFoundError()
|
throw NotFoundError()
|
||||||
}
|
}
|
||||||
guard request.hasUpdates else { return try model.toDTO() }
|
try updates.validate()
|
||||||
try model.applyUpdates(request)
|
model.applyUpdates(updates)
|
||||||
|
if model.hasChanges {
|
||||||
try await model.save(on: database)
|
try await model.save(on: database)
|
||||||
|
}
|
||||||
return try model.toDTO()
|
return try model.toDTO()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -196,8 +199,7 @@ final class EquipmentModel: Model, @unchecked Sendable {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyUpdates(_ updates: EquipmentInfo.Update) throws {
|
func applyUpdates(_ updates: EquipmentInfo.Update) {
|
||||||
try updates.validate()
|
|
||||||
if let staticPressure = updates.staticPressure {
|
if let staticPressure = updates.staticPressure {
|
||||||
self.staticPressure = staticPressure
|
self.staticPressure = staticPressure
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ extension DatabaseClient {
|
|||||||
public var getCompletedSteps: @Sendable (Project.ID) async throws -> Project.CompletedSteps
|
public var getCompletedSteps: @Sendable (Project.ID) async throws -> Project.CompletedSteps
|
||||||
public var getSensibleHeatRatio: @Sendable (Project.ID) async throws -> Double?
|
public var getSensibleHeatRatio: @Sendable (Project.ID) async throws -> Double?
|
||||||
public var fetch: @Sendable (User.ID, PageRequest) async throws -> Page<Project>
|
public var fetch: @Sendable (User.ID, PageRequest) async throws -> Page<Project>
|
||||||
public var update: @Sendable (Project.Update) async throws -> Project
|
public var update: @Sendable (Project.ID, Project.Update) async throws -> Project
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,12 +85,13 @@ extension DatabaseClient.Projects: TestDependencyKey {
|
|||||||
.paginate(request)
|
.paginate(request)
|
||||||
.map { try $0.toDTO() }
|
.map { try $0.toDTO() }
|
||||||
},
|
},
|
||||||
update: { updates in
|
update: { id, updates in
|
||||||
guard let model = try await ProjectModel.find(updates.id, on: database) else {
|
guard let model = try await ProjectModel.find(id, on: database) else {
|
||||||
throw NotFoundError()
|
throw NotFoundError()
|
||||||
}
|
}
|
||||||
try updates.validate()
|
try updates.validate()
|
||||||
if model.applyUpdates(updates) {
|
model.applyUpdates(updates)
|
||||||
|
if model.hasChanges {
|
||||||
try await model.save(on: database)
|
try await model.save(on: database)
|
||||||
}
|
}
|
||||||
return try model.toDTO()
|
return try model.toDTO()
|
||||||
@@ -283,34 +284,26 @@ final class ProjectModel: Model, @unchecked Sendable {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyUpdates(_ updates: Project.Update) -> Bool {
|
func applyUpdates(_ updates: Project.Update) {
|
||||||
var hasUpdates = false
|
|
||||||
if let name = updates.name, name != self.name {
|
if let name = updates.name, name != self.name {
|
||||||
hasUpdates = true
|
|
||||||
self.name = name
|
self.name = name
|
||||||
}
|
}
|
||||||
if let streetAddress = updates.streetAddress, streetAddress != self.streetAddress {
|
if let streetAddress = updates.streetAddress, streetAddress != self.streetAddress {
|
||||||
hasUpdates = true
|
|
||||||
self.streetAddress = streetAddress
|
self.streetAddress = streetAddress
|
||||||
}
|
}
|
||||||
if let city = updates.city, city != self.city {
|
if let city = updates.city, city != self.city {
|
||||||
hasUpdates = true
|
|
||||||
self.city = city
|
self.city = city
|
||||||
}
|
}
|
||||||
if let state = updates.state, state != self.state {
|
if let state = updates.state, state != self.state {
|
||||||
hasUpdates = true
|
|
||||||
self.state = state
|
self.state = state
|
||||||
}
|
}
|
||||||
if let zipCode = updates.zipCode, zipCode != self.zipCode {
|
if let zipCode = updates.zipCode, zipCode != self.zipCode {
|
||||||
hasUpdates = true
|
|
||||||
self.zipCode = zipCode
|
self.zipCode = zipCode
|
||||||
}
|
}
|
||||||
if let sensibleHeatRatio = updates.sensibleHeatRatio,
|
if let sensibleHeatRatio = updates.sensibleHeatRatio,
|
||||||
sensibleHeatRatio != self.sensibleHeatRatio
|
sensibleHeatRatio != self.sensibleHeatRatio
|
||||||
{
|
{
|
||||||
hasUpdates = true
|
|
||||||
self.sensibleHeatRatio = sensibleHeatRatio
|
self.sensibleHeatRatio = sensibleHeatRatio
|
||||||
}
|
}
|
||||||
return hasUpdates
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ extension DatabaseClient {
|
|||||||
public var delete: @Sendable (Room.ID) async throws -> Void
|
public var delete: @Sendable (Room.ID) async throws -> Void
|
||||||
public var get: @Sendable (Room.ID) async throws -> Room?
|
public var get: @Sendable (Room.ID) async throws -> Room?
|
||||||
public var fetch: @Sendable (Project.ID) async throws -> [Room]
|
public var fetch: @Sendable (Project.ID) async throws -> [Room]
|
||||||
public var update: @Sendable (Room.Update) async throws -> Room
|
public var update: @Sendable (Room.ID, Room.Update) async throws -> Room
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,13 +41,14 @@ extension DatabaseClient.Rooms: TestDependencyKey {
|
|||||||
.all()
|
.all()
|
||||||
.map { try $0.toDTO() }
|
.map { try $0.toDTO() }
|
||||||
},
|
},
|
||||||
update: { updates in
|
update: { id, updates in
|
||||||
guard let model = try await RoomModel.find(updates.id, on: database) else {
|
guard let model = try await RoomModel.find(id, on: database) else {
|
||||||
throw NotFoundError()
|
throw NotFoundError()
|
||||||
}
|
}
|
||||||
|
|
||||||
try updates.validate()
|
try updates.validate()
|
||||||
if model.applyUpdates(updates) {
|
model.applyUpdates(updates)
|
||||||
|
if model.hasChanges {
|
||||||
try await model.save(on: database)
|
try await model.save(on: database)
|
||||||
}
|
}
|
||||||
return try model.toDTO()
|
return try model.toDTO()
|
||||||
@@ -218,30 +219,24 @@ final class RoomModel: Model, @unchecked Sendable {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyUpdates(_ updates: Room.Update) -> Bool {
|
func applyUpdates(_ updates: Room.Update) {
|
||||||
var hasUpdates = false
|
|
||||||
|
|
||||||
if let name = updates.name, name != self.name {
|
if let name = updates.name, name != self.name {
|
||||||
hasUpdates = true
|
|
||||||
self.name = name
|
self.name = name
|
||||||
}
|
}
|
||||||
if let heatingLoad = updates.heatingLoad, heatingLoad != self.heatingLoad {
|
if let heatingLoad = updates.heatingLoad, heatingLoad != self.heatingLoad {
|
||||||
hasUpdates = true
|
|
||||||
self.heatingLoad = heatingLoad
|
self.heatingLoad = heatingLoad
|
||||||
}
|
}
|
||||||
if let coolingTotal = updates.coolingTotal, coolingTotal != self.coolingTotal {
|
if let coolingTotal = updates.coolingTotal, coolingTotal != self.coolingTotal {
|
||||||
hasUpdates = true
|
|
||||||
self.coolingTotal = coolingTotal
|
self.coolingTotal = coolingTotal
|
||||||
}
|
}
|
||||||
if let coolingSensible = updates.coolingSensible, coolingSensible != self.coolingSensible {
|
if let coolingSensible = updates.coolingSensible, coolingSensible != self.coolingSensible {
|
||||||
hasUpdates = true
|
|
||||||
self.coolingSensible = coolingSensible
|
self.coolingSensible = coolingSensible
|
||||||
}
|
}
|
||||||
if let registerCount = updates.registerCount, registerCount != self.registerCount {
|
if let registerCount = updates.registerCount, registerCount != self.registerCount {
|
||||||
hasUpdates = true
|
|
||||||
self.registerCount = registerCount
|
self.registerCount = registerCount
|
||||||
}
|
}
|
||||||
return hasUpdates
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,26 @@ extension ComponentPressureLoss {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
public let name: String?
|
||||||
|
public let value: Double?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
name: String? = nil,
|
||||||
|
value: Double? = nil
|
||||||
|
) {
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array where Element == ComponentPressureLoss {
|
||||||
|
public var totalComponentPressureLoss: Double {
|
||||||
|
reduce(into: 0) { $0 += $1.value }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public typealias ComponentPressureLosses = [String: Double]
|
public typealias ComponentPressureLosses = [String: Double]
|
||||||
|
|||||||
@@ -63,20 +63,17 @@ extension EffectiveLength {
|
|||||||
|
|
||||||
public struct Update: Codable, Equatable, Sendable {
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
public let id: EffectiveLength.ID
|
|
||||||
public let name: String?
|
public let name: String?
|
||||||
public let type: EffectiveLengthType?
|
public let type: EffectiveLengthType?
|
||||||
public let straightLengths: [Int]?
|
public let straightLengths: [Int]?
|
||||||
public let groups: [Group]?
|
public let groups: [Group]?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
id: EffectiveLength.ID,
|
|
||||||
name: String? = nil,
|
name: String? = nil,
|
||||||
type: EffectiveLength.EffectiveLengthType? = nil,
|
type: EffectiveLength.EffectiveLengthType? = nil,
|
||||||
straightLengths: [Int]? = nil,
|
straightLengths: [Int]? = nil,
|
||||||
groups: [EffectiveLength.Group]? = nil
|
groups: [EffectiveLength.Group]? = nil
|
||||||
) {
|
) {
|
||||||
self.id = id
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.type = type
|
self.type = type
|
||||||
self.straightLengths = straightLengths
|
self.straightLengths = straightLengths
|
||||||
@@ -113,6 +110,12 @@ extension EffectiveLength {
|
|||||||
public let supply: EffectiveLength?
|
public let supply: EffectiveLength?
|
||||||
public let `return`: EffectiveLength?
|
public let `return`: EffectiveLength?
|
||||||
|
|
||||||
|
public var total: Double? {
|
||||||
|
guard let supply else { return nil }
|
||||||
|
guard let `return` else { return nil }
|
||||||
|
return supply.totalEquivalentLength + `return`.totalEquivalentLength
|
||||||
|
}
|
||||||
|
|
||||||
public init(supply: EffectiveLength? = nil, return: EffectiveLength? = nil) {
|
public init(supply: EffectiveLength? = nil, return: EffectiveLength? = nil) {
|
||||||
self.supply = supply
|
self.supply = supply
|
||||||
self.return = `return`
|
self.return = `return`
|
||||||
|
|||||||
@@ -52,18 +52,15 @@ extension EquipmentInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct Update: Codable, Equatable, Sendable {
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
public let id: EquipmentInfo.ID
|
|
||||||
public let staticPressure: Double?
|
public let staticPressure: Double?
|
||||||
public let heatingCFM: Int?
|
public let heatingCFM: Int?
|
||||||
public let coolingCFM: Int?
|
public let coolingCFM: Int?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
id: EquipmentInfo.ID,
|
|
||||||
staticPressure: Double? = nil,
|
staticPressure: Double? = nil,
|
||||||
heatingCFM: Int? = nil,
|
heatingCFM: Int? = nil,
|
||||||
coolingCFM: Int? = nil
|
coolingCFM: Int? = nil
|
||||||
) {
|
) {
|
||||||
self.id = id
|
|
||||||
self.staticPressure = staticPressure
|
self.staticPressure = staticPressure
|
||||||
self.heatingCFM = heatingCFM
|
self.heatingCFM = heatingCFM
|
||||||
self.coolingCFM = coolingCFM
|
self.coolingCFM = coolingCFM
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ extension Project {
|
|||||||
|
|
||||||
public struct Update: Codable, Equatable, Sendable {
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
public let id: Project.ID
|
|
||||||
public let name: String?
|
public let name: String?
|
||||||
public let streetAddress: String?
|
public let streetAddress: String?
|
||||||
public let city: String?
|
public let city: String?
|
||||||
@@ -88,7 +87,6 @@ extension Project {
|
|||||||
public let sensibleHeatRatio: Double?
|
public let sensibleHeatRatio: Double?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
id: Project.ID,
|
|
||||||
name: String? = nil,
|
name: String? = nil,
|
||||||
streetAddress: String? = nil,
|
streetAddress: String? = nil,
|
||||||
city: String? = nil,
|
city: String? = nil,
|
||||||
@@ -96,7 +94,6 @@ extension Project {
|
|||||||
zipCode: String? = nil,
|
zipCode: String? = nil,
|
||||||
sensibleHeatRatio: Double? = nil
|
sensibleHeatRatio: Double? = nil
|
||||||
) {
|
) {
|
||||||
self.id = id
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.streetAddress = streetAddress
|
self.streetAddress = streetAddress
|
||||||
self.city = city
|
self.city = city
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ extension Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct Update: Codable, Equatable, Sendable {
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
public let id: Room.ID
|
|
||||||
public let name: String?
|
public let name: String?
|
||||||
public let heatingLoad: Double?
|
public let heatingLoad: Double?
|
||||||
public let coolingTotal: Double?
|
public let coolingTotal: Double?
|
||||||
@@ -71,14 +70,12 @@ extension Room {
|
|||||||
public let registerCount: Int?
|
public let registerCount: Int?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
id: Room.ID,
|
|
||||||
name: String? = nil,
|
name: String? = nil,
|
||||||
heatingLoad: Double? = nil,
|
heatingLoad: Double? = nil,
|
||||||
coolingTotal: Double? = nil,
|
coolingTotal: Double? = nil,
|
||||||
coolingSensible: Double? = nil,
|
coolingSensible: Double? = nil,
|
||||||
registerCount: Int? = nil
|
registerCount: Int? = nil
|
||||||
) {
|
) {
|
||||||
self.id = id
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.heatingLoad = heatingLoad
|
self.heatingLoad = heatingLoad
|
||||||
self.coolingTotal = coolingTotal
|
self.coolingTotal = coolingTotal
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ extension SiteRoute.View {
|
|||||||
case form(id: Project.ID? = nil, dismiss: Bool = false)
|
case form(id: Project.ID? = nil, dismiss: Bool = false)
|
||||||
case index
|
case index
|
||||||
case page(PageRequest)
|
case page(PageRequest)
|
||||||
case update(Project.Update)
|
case update(Project.ID, Project.Update)
|
||||||
|
|
||||||
public static func page(page: Int, per limit: Int) -> Self {
|
public static func page(page: Int, per limit: Int) -> Self {
|
||||||
.page(.init(page: page, per: limit))
|
.page(.init(page: page, per: limit))
|
||||||
@@ -112,11 +112,13 @@ extension SiteRoute.View {
|
|||||||
.map(.memberwise(PageRequest.init))
|
.map(.memberwise(PageRequest.init))
|
||||||
}
|
}
|
||||||
Route(.case(Self.update)) {
|
Route(.case(Self.update)) {
|
||||||
Path { rootPath }
|
Path {
|
||||||
|
rootPath
|
||||||
|
Project.ID.parser()
|
||||||
|
}
|
||||||
Method.patch
|
Method.patch
|
||||||
Body {
|
Body {
|
||||||
FormData {
|
FormData {
|
||||||
Field("id") { Project.ID.parser() }
|
|
||||||
Optionally {
|
Optionally {
|
||||||
Field("name", .string)
|
Field("name", .string)
|
||||||
}
|
}
|
||||||
@@ -149,6 +151,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 equipment(EquipmentInfoRoute)
|
case equipment(EquipmentInfoRoute)
|
||||||
case equivalentLength(EquivalentLengthRoute)
|
case equivalentLength(EquivalentLengthRoute)
|
||||||
case frictionRate(FrictionRateRoute)
|
case frictionRate(FrictionRateRoute)
|
||||||
@@ -163,6 +166,9 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Route(.case(Self.componentLoss)) {
|
||||||
|
ComponentLossRoute.router
|
||||||
|
}
|
||||||
Route(.case(Self.equipment)) {
|
Route(.case(Self.equipment)) {
|
||||||
EquipmentInfoRoute.router
|
EquipmentInfoRoute.router
|
||||||
}
|
}
|
||||||
@@ -193,7 +199,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
case form(id: Room.ID? = nil, dismiss: Bool = false)
|
case form(id: Room.ID? = nil, dismiss: Bool = false)
|
||||||
case index
|
case index
|
||||||
case submit(Room.Create)
|
case submit(Room.Create)
|
||||||
case update(Room.Update)
|
case update(Room.ID, Room.Update)
|
||||||
case updateSensibleHeatRatio(SHRUpdate)
|
case updateSensibleHeatRatio(SHRUpdate)
|
||||||
|
|
||||||
static let rootPath = "rooms"
|
static let rootPath = "rooms"
|
||||||
@@ -243,11 +249,13 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Route(.case(Self.update)) {
|
Route(.case(Self.update)) {
|
||||||
Path { rootPath }
|
Path {
|
||||||
|
rootPath
|
||||||
|
Room.ID.parser()
|
||||||
|
}
|
||||||
Method.patch
|
Method.patch
|
||||||
Body {
|
Body {
|
||||||
FormData {
|
FormData {
|
||||||
Field("id") { Room.ID.parser() }
|
|
||||||
Optionally {
|
Optionally {
|
||||||
Field("name", .string)
|
Field("name", .string)
|
||||||
}
|
}
|
||||||
@@ -291,6 +299,59 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ComponentLossRoute: Equatable, Sendable {
|
||||||
|
case index
|
||||||
|
case delete(ComponentPressureLoss.ID)
|
||||||
|
case submit(ComponentPressureLoss.Create)
|
||||||
|
case update(ComponentPressureLoss.ID, ComponentPressureLoss.Update)
|
||||||
|
|
||||||
|
static let rootPath = "component-loss"
|
||||||
|
|
||||||
|
static let router = OneOf {
|
||||||
|
Route(.case(Self.index)) {
|
||||||
|
Path { rootPath }
|
||||||
|
Method.get
|
||||||
|
}
|
||||||
|
Route(.case(Self.delete)) {
|
||||||
|
Path {
|
||||||
|
rootPath
|
||||||
|
ComponentPressureLoss.ID.parser()
|
||||||
|
}
|
||||||
|
Method.delete
|
||||||
|
}
|
||||||
|
Route(.case(Self.submit)) {
|
||||||
|
Path { rootPath }
|
||||||
|
Method.post
|
||||||
|
Body {
|
||||||
|
FormData {
|
||||||
|
Field("projectID") { Project.ID.parser() }
|
||||||
|
Field("name", .string)
|
||||||
|
Field("value") { Double.parser() }
|
||||||
|
}
|
||||||
|
.map(.memberwise(ComponentPressureLoss.Create.init))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Route(.case(Self.update)) {
|
||||||
|
Path {
|
||||||
|
rootPath
|
||||||
|
ComponentPressureLoss.ID.parser()
|
||||||
|
}
|
||||||
|
Method.patch
|
||||||
|
Body {
|
||||||
|
FormData {
|
||||||
|
Optionally {
|
||||||
|
Field("name", .string)
|
||||||
|
}
|
||||||
|
Optionally {
|
||||||
|
Field("value") { Double.parser() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map(.memberwise(ComponentPressureLoss.Update.init))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum FrictionRateRoute: Equatable, Sendable {
|
public enum FrictionRateRoute: Equatable, Sendable {
|
||||||
case index
|
case index
|
||||||
// TODO: Remove form or move equipment / component losses routes here.
|
// TODO: Remove form or move equipment / component losses routes here.
|
||||||
@@ -326,7 +387,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
case index
|
case index
|
||||||
case form(dismiss: Bool)
|
case form(dismiss: Bool)
|
||||||
case submit(EquipmentInfo.Create)
|
case submit(EquipmentInfo.Create)
|
||||||
case update(EquipmentInfo.Update)
|
case update(EquipmentInfo.ID, EquipmentInfo.Update)
|
||||||
|
|
||||||
static let rootPath = "equipment"
|
static let rootPath = "equipment"
|
||||||
|
|
||||||
@@ -359,11 +420,13 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Route(.case(Self.update)) {
|
Route(.case(Self.update)) {
|
||||||
Path { rootPath }
|
Path {
|
||||||
|
rootPath
|
||||||
|
EquipmentInfo.ID.parser()
|
||||||
|
}
|
||||||
Method.patch
|
Method.patch
|
||||||
Body {
|
Body {
|
||||||
FormData {
|
FormData {
|
||||||
Field("id") { EquipmentInfo.ID.parser() }
|
|
||||||
Optionally {
|
Optionally {
|
||||||
Field("staticPressure", default: nil) { Double.parser() }
|
Field("staticPressure", default: nil) { Double.parser() }
|
||||||
}
|
}
|
||||||
@@ -386,7 +449,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
case form(dismiss: Bool = false)
|
case form(dismiss: Bool = false)
|
||||||
case index
|
case index
|
||||||
case submit(FormStep)
|
case submit(FormStep)
|
||||||
case update(StepThree)
|
case update(EffectiveLength.ID, StepThree)
|
||||||
|
|
||||||
static let rootPath = "effective-lengths"
|
static let rootPath = "effective-lengths"
|
||||||
|
|
||||||
@@ -433,7 +496,10 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
FormStep.router
|
FormStep.router
|
||||||
}
|
}
|
||||||
Route(.case(Self.update)) {
|
Route(.case(Self.update)) {
|
||||||
Path { rootPath }
|
Path {
|
||||||
|
rootPath
|
||||||
|
EffectiveLength.ID.parser()
|
||||||
|
}
|
||||||
Method.patch
|
Method.patch
|
||||||
Body {
|
Body {
|
||||||
FormData {
|
FormData {
|
||||||
|
|||||||
@@ -50,11 +50,7 @@ extension EffectiveLength.Update {
|
|||||||
form: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepThree,
|
form: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepThree,
|
||||||
projectID: Project.ID
|
projectID: Project.ID
|
||||||
) throws {
|
) throws {
|
||||||
guard let id = form.id else {
|
|
||||||
throw ValidationError("Id not found.")
|
|
||||||
}
|
|
||||||
self.init(
|
self.init(
|
||||||
id: id,
|
|
||||||
name: form.name,
|
name: form.name,
|
||||||
type: form.type,
|
type: form.type,
|
||||||
straightLengths: form.straightLengths,
|
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)
|
try await database.projects.delete(id)
|
||||||
return EmptyHTML()
|
return EmptyHTML()
|
||||||
|
|
||||||
case .update(let form):
|
case .update(let id, let form):
|
||||||
let project = try await database.projects.update(form)
|
let project = try await database.projects.update(id, form)
|
||||||
return ProjectView(projectID: project.id, activeTab: .project)
|
return ProjectView(projectID: project.id, activeTab: .project)
|
||||||
|
|
||||||
case .detail(let projectID, let route):
|
case .detail(let projectID, let route):
|
||||||
@@ -115,6 +115,8 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
return request.view {
|
return request.view {
|
||||||
ProjectView(projectID: projectID, activeTab: tab)
|
ProjectView(projectID: projectID, activeTab: tab)
|
||||||
}
|
}
|
||||||
|
case .componentLoss(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):
|
||||||
@@ -147,8 +149,8 @@ extension SiteRoute.View.ProjectRoute.EquipmentInfoRoute {
|
|||||||
case .submit(let form):
|
case .submit(let form):
|
||||||
let equipment = try await database.equipment.create(form)
|
let equipment = try await database.equipment.create(form)
|
||||||
return EquipmentInfoView(equipmentInfo: equipment, projectID: projectID)
|
return EquipmentInfoView(equipmentInfo: equipment, projectID: projectID)
|
||||||
case .update(let updates):
|
case .update(let id, let updates):
|
||||||
let equipment = try await database.equipment.update(updates)
|
let equipment = try await database.equipment.update(id, updates)
|
||||||
return EquipmentInfoView(equipmentInfo: equipment, projectID: projectID)
|
return EquipmentInfoView(equipmentInfo: equipment, projectID: projectID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,13 +189,14 @@ extension SiteRoute.View.ProjectRoute.RoomRoute {
|
|||||||
ProjectView(projectID: projectID, activeTab: .rooms)
|
ProjectView(projectID: projectID, activeTab: .rooms)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .update(let form):
|
case .update(let id, let form):
|
||||||
let _ = try await database.rooms.update(form)
|
let _ = try await database.rooms.update(id, form)
|
||||||
return ProjectView(projectID: projectID, activeTab: .rooms)
|
return ProjectView(projectID: projectID, activeTab: .rooms)
|
||||||
|
|
||||||
case .updateSensibleHeatRatio(let form):
|
case .updateSensibleHeatRatio(let form):
|
||||||
let _ = try await database.projects.update(
|
let _ = try await database.projects.update(
|
||||||
.init(id: form.projectID, sensibleHeatRatio: form.sensibleHeatRatio)
|
form.projectID,
|
||||||
|
.init(sensibleHeatRatio: form.sensibleHeatRatio)
|
||||||
)
|
)
|
||||||
return request.view {
|
return request.view {
|
||||||
ProjectView(projectID: projectID, activeTab: .rooms)
|
ProjectView(projectID: projectID, activeTab: .rooms)
|
||||||
@@ -210,8 +213,8 @@ extension SiteRoute.View.ProjectRoute.FrictionRateRoute {
|
|||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
case .index:
|
case .index:
|
||||||
let equipment = try await database.equipment.fetch(projectID)
|
// 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)
|
||||||
@@ -224,12 +227,36 @@ extension SiteRoute.View.ProjectRoute.FrictionRateRoute {
|
|||||||
return div { "REMOVE ME!" }
|
return div { "REMOVE ME!" }
|
||||||
// return EquipmentForm(dismiss: dismiss, projectID: projectID)
|
// return EquipmentForm(dismiss: dismiss, projectID: projectID)
|
||||||
case .componentPressureLoss:
|
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 {
|
extension SiteRoute.View.ProjectRoute.FrictionRateRoute.FormType {
|
||||||
var id: String {
|
var id: String {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -271,8 +298,8 @@ extension SiteRoute.View.ProjectRoute.EquivalentLengthRoute {
|
|||||||
return GroupField(style: style ?? .supply)
|
return GroupField(style: style ?? .supply)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .update(let form):
|
case .update(let id, let form):
|
||||||
_ = try await database.effectiveLength.update(.init(form: form, projectID: projectID))
|
_ = try await database.effectiveLength.update(id, .init(form: form, projectID: projectID))
|
||||||
return ProjectView(projectID: projectID, activeTab: .equivalentLength)
|
return ProjectView(projectID: projectID, activeTab: .equivalentLength)
|
||||||
|
|
||||||
case .submit(let step):
|
case .submit(let step):
|
||||||
|
|||||||
@@ -4,38 +4,62 @@ import ManualDCore
|
|||||||
import Styleguide
|
import Styleguide
|
||||||
|
|
||||||
struct ComponentLossForm: HTML, Sendable {
|
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 dismiss: Bool
|
||||||
let projectID: Project.ID
|
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 {
|
var body: some HTML {
|
||||||
ModalForm(id: "componentLossForm", dismiss: dismiss) {
|
ModalForm(id: Self.id(componentLoss), dismiss: dismiss) {
|
||||||
h1(.class("text-2xl font-bold")) { "Component Loss" }
|
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 {
|
div {
|
||||||
label(.for("name")) { "Name" }
|
label(.for("name")) { "Name" }
|
||||||
Input(id: "name", placeholder: "Name")
|
Input(id: "name", placeholder: "Name")
|
||||||
.attributes(.type(.text), .required, .autofocus)
|
.attributes(.type(.text), .required, .autofocus, .value(componentLoss?.name))
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
label(.for("value")) { "Value" }
|
label(.for("value")) { "Value" }
|
||||||
Input(id: "name", placeholder: "Pressure loss")
|
Input(id: "value", placeholder: "Pressure loss")
|
||||||
.attributes(.type(.number), .min("0"), .max("1"), .step("0.1"), .required)
|
|
||||||
}
|
|
||||||
Row {
|
|
||||||
div {}
|
|
||||||
div {
|
|
||||||
CancelButton()
|
|
||||||
.attributes(
|
.attributes(
|
||||||
.hx.get(
|
.type(.number), .min("0.03"), .max("1.0"), .step("0.1"), .required,
|
||||||
route: .project(
|
.value(componentLoss?.value)
|
||||||
.detail(projectID, .frictionRate(.form(.componentPressureLoss, dismiss: true))))
|
|
||||||
),
|
|
||||||
.hx.target("#componentLossForm"),
|
|
||||||
.hx.swap(.outerHTML)
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
SubmitButton()
|
SubmitButton()
|
||||||
}
|
.attributes(.class("btn-block"))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,33 +23,71 @@ struct ComponentPressureLossesView: HTML, Sendable {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Row {
|
Row {
|
||||||
|
div(.class("flex space-x-4 items-center")) {
|
||||||
h1(.class("text-2xl font-bold")) { "Component Pressure Losses" }
|
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()
|
PlusButton()
|
||||||
.attributes(
|
.attributes(
|
||||||
.hx.get(
|
.showModal(id: ComponentLossForm.id())
|
||||||
route: .project(
|
|
||||||
.detail(projectID, .frictionRate(.form(.componentPressureLoss, dismiss: false))))
|
|
||||||
),
|
|
||||||
.hx.target("#componentLossForm"),
|
|
||||||
.hx.swap(.outerHTML)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table(.class("table table-zebra")) {
|
||||||
|
thead {
|
||||||
|
tr(.class("text-xl font-bold")) {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Value" }
|
||||||
|
th {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
for row in componentPressureLosses {
|
for row in componentPressureLosses {
|
||||||
Row {
|
TableRow(row: row)
|
||||||
Label { row.name }
|
|
||||||
Number(row.value)
|
|
||||||
}
|
}
|
||||||
.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
|
let stepTwo: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepTwo
|
||||||
|
|
||||||
var route: String {
|
var route: String {
|
||||||
if effectiveLength != nil {
|
|
||||||
return SiteRoute.View.router.path(
|
|
||||||
for: .project(.detail(projectID, .equivalentLength(.index))))
|
|
||||||
} else {
|
|
||||||
let baseRoute = SiteRoute.View.router.path(
|
let baseRoute = SiteRoute.View.router.path(
|
||||||
for: .project(.detail(projectID, .equivalentLength(.index)))
|
for: .project(.detail(projectID, .equivalentLength(.index)))
|
||||||
)
|
)
|
||||||
return "\(baseRoute)/stepThree"
|
|
||||||
|
if let effectiveLength {
|
||||||
|
return baseRoute.appendingPath(effectiveLength.id)
|
||||||
|
} else {
|
||||||
|
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")) {
|
div(.class("card-actions justify-end pt-6 space-y-4 mt-auto")) {
|
||||||
// TODO: Delete.
|
div(.class("join")) {
|
||||||
TrashButton()
|
TrashButton()
|
||||||
.attributes(
|
.attributes(
|
||||||
|
.class("join-item"),
|
||||||
.hx.delete(
|
.hx.delete(
|
||||||
route: .project(
|
route: .project(
|
||||||
.detail(
|
.detail(
|
||||||
effectiveLength.projectID, .equivalentLength(.delete(id: effectiveLength.id)))
|
effectiveLength.projectID,
|
||||||
)),
|
.equivalentLength(.delete(id: effectiveLength.id))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
.hx.confirm("Are you sure?"),
|
.hx.confirm("Are you sure?"),
|
||||||
.hx.target("#\(id)"),
|
.hx.target("#\(id)"),
|
||||||
.hx.swap(.outerHTML)
|
.hx.swap(.outerHTML)
|
||||||
)
|
)
|
||||||
EditButton()
|
EditButton()
|
||||||
.attributes(.showModal(id: EffectiveLengthForm.id(effectiveLength)))
|
.attributes(
|
||||||
|
.class("join-item"),
|
||||||
|
.showModal(id: EffectiveLengthForm.id(effectiveLength))
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EffectiveLengthForm(effectiveLength: effectiveLength)
|
EffectiveLengthForm(effectiveLength: effectiveLength)
|
||||||
|
|||||||
@@ -18,18 +18,11 @@ struct EquipmentInfoForm: HTML, Sendable {
|
|||||||
return "\(staticPressure)"
|
return "\(staticPressure)"
|
||||||
}
|
}
|
||||||
|
|
||||||
var heatingCFM: String {
|
var route: String {
|
||||||
guard let heatingCFM = equipmentInfo?.heatingCFM else {
|
SiteRoute.View.router.path(
|
||||||
return ""
|
for: .project(.detail(projectID, .equipment(.index)))
|
||||||
}
|
)
|
||||||
return "\(heatingCFM)"
|
.appendingPath(equipmentInfo?.id)
|
||||||
}
|
|
||||||
|
|
||||||
var coolingCFM: String {
|
|
||||||
guard let heatingCFM = equipmentInfo?.heatingCFM else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return "\(heatingCFM)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
@@ -38,8 +31,8 @@ struct EquipmentInfoForm: HTML, Sendable {
|
|||||||
form(
|
form(
|
||||||
.class("space-y-4 p-4"),
|
.class("space-y-4 p-4"),
|
||||||
equipmentInfo != nil
|
equipmentInfo != nil
|
||||||
? .hx.patch(route: .project(.detail(projectID, .equipment(.index))))
|
? .hx.patch(route)
|
||||||
: .hx.post(route: .project(.detail(projectID, .equipment(.index)))),
|
: .hx.post(route),
|
||||||
.hx.target("#equipmentInfo"),
|
.hx.target("#equipmentInfo"),
|
||||||
.hx.swap(.outerHTML)
|
.hx.swap(.outerHTML)
|
||||||
) {
|
) {
|
||||||
@@ -59,12 +52,12 @@ struct EquipmentInfoForm: HTML, Sendable {
|
|||||||
div {
|
div {
|
||||||
label(.for("heatingCFM")) { "Heating CFM" }
|
label(.for("heatingCFM")) { "Heating CFM" }
|
||||||
Input(id: "heatingCFM", placeholder: "CFM")
|
Input(id: "heatingCFM", placeholder: "CFM")
|
||||||
.attributes(.type(.number), .min("0"), .value(heatingCFM))
|
.attributes(.type(.number), .min("0"), .value(equipmentInfo?.heatingCFM))
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
label(.for("coolingCFM")) { "Cooling CFM" }
|
label(.for("coolingCFM")) { "Cooling CFM" }
|
||||||
Input(id: "coolingCFM", placeholder: "CFM")
|
Input(id: "coolingCFM", placeholder: "CFM")
|
||||||
.attributes(.type(.number), .min("0"), .value(coolingCFM))
|
.attributes(.type(.number), .min("0"), .value(equipmentInfo?.coolingCFM))
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
SubmitButton(title: "Save")
|
SubmitButton(title: "Save")
|
||||||
|
|||||||
@@ -23,24 +23,28 @@ struct EquipmentInfoView: HTML, Sendable {
|
|||||||
|
|
||||||
if let equipmentInfo {
|
if let equipmentInfo {
|
||||||
|
|
||||||
Row {
|
table(.class("table table-zebra")) {
|
||||||
Label { "Static Pressure" }
|
thead {
|
||||||
Number(equipmentInfo.staticPressure)
|
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(
|
EquipmentInfoForm(
|
||||||
dismiss: true, projectID: projectID, equipmentInfo: equipmentInfo
|
dismiss: true, projectID: projectID, equipmentInfo: equipmentInfo
|
||||||
|
|||||||
@@ -6,15 +6,95 @@ struct FrictionRateView: HTML, Sendable {
|
|||||||
|
|
||||||
let equipmentInfo: EquipmentInfo?
|
let equipmentInfo: EquipmentInfo?
|
||||||
let componentLosses: [ComponentPressureLoss]
|
let componentLosses: [ComponentPressureLoss]
|
||||||
|
let equivalentLengths: EffectiveLength.MaxContainer
|
||||||
let projectID: Project.ID
|
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 {
|
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" }
|
||||||
|
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)
|
EquipmentInfoView(equipmentInfo: equipmentInfo, projectID: projectID)
|
||||||
ComponentPressureLossesView(
|
ComponentPressureLossesView(
|
||||||
componentPressureLosses: componentLosses, projectID: projectID
|
componentPressureLosses: componentLosses, projectID: projectID
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,14 +18,19 @@ struct ProjectForm: HTML, Sendable {
|
|||||||
self.project = project
|
self.project = project
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var route: String {
|
||||||
|
SiteRoute.View.router.path(for: .project(.index))
|
||||||
|
.appendingPath(project?.id)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
ModalForm(id: Self.id, dismiss: dismiss) {
|
ModalForm(id: Self.id, dismiss: dismiss) {
|
||||||
h1(.class("text-3xl font-bold pb-6 ps-2")) { "Project" }
|
h1(.class("text-3xl font-bold pb-6 ps-2")) { "Project" }
|
||||||
form(
|
form(
|
||||||
.class("space-y-4 p-4"),
|
.class("space-y-4 p-4"),
|
||||||
project == nil
|
project == nil
|
||||||
? .hx.post(route: .project(.index))
|
? .hx.post(route)
|
||||||
: .hx.patch(route: .project(.index)),
|
: .hx.patch(route),
|
||||||
.hx.target("body"),
|
.hx.target("body"),
|
||||||
.hx.swap(.outerHTML)
|
.hx.swap(.outerHTML)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import ElementaryHTMX
|
|||||||
import ManualDCore
|
import ManualDCore
|
||||||
import Styleguide
|
import Styleguide
|
||||||
|
|
||||||
// TODO: Make view async and load based on the active tab.
|
|
||||||
|
|
||||||
struct ProjectView: HTML, Sendable {
|
struct ProjectView: HTML, Sendable {
|
||||||
@Dependency(\.database) var database
|
@Dependency(\.database) var database
|
||||||
|
|
||||||
@@ -58,7 +56,10 @@ struct ProjectView: HTML, Sendable {
|
|||||||
case .frictionRate:
|
case .frictionRate:
|
||||||
try await FrictionRateView(
|
try await FrictionRateView(
|
||||||
equipmentInfo: database.equipment.fetch(projectID),
|
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:
|
case .ductSizing:
|
||||||
div { "FIX ME!" }
|
div { "FIX ME!" }
|
||||||
|
|
||||||
@@ -75,8 +76,9 @@ struct ProjectView: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Update to use DaisyUI drawer.
|
extension ProjectView {
|
||||||
struct Sidebar: HTML {
|
|
||||||
|
struct Sidebar: HTML {
|
||||||
|
|
||||||
let active: SiteRoute.View.ProjectRoute.DetailRoute.Tab
|
let active: SiteRoute.View.ProjectRoute.DetailRoute.Tab
|
||||||
let projectID: Project.ID
|
let projectID: Project.ID
|
||||||
@@ -192,18 +194,17 @@ struct Sidebar: HTML {
|
|||||||
href: String,
|
href: String,
|
||||||
isComplete: Bool,
|
isComplete: Bool,
|
||||||
hideIsComplete: Bool = false
|
hideIsComplete: Bool = false
|
||||||
) -> some HTML<HTMLTag.div> {
|
) -> some HTML<HTMLTag.a> {
|
||||||
div(
|
|
||||||
.class(
|
|
||||||
"w-full is-drawer-close:tooltip is-drawer-close:tooltip-right"
|
|
||||||
),
|
|
||||||
.data("tip", value: title)
|
|
||||||
) {
|
|
||||||
a(
|
a(
|
||||||
.class(
|
.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")) {
|
div(.class("flex is-drawer-open:space-x-4")) {
|
||||||
SVG(icon)
|
SVG(icon)
|
||||||
@@ -226,7 +227,6 @@ struct Sidebar: HTML {
|
|||||||
.attributes(.class("is-drawer-close:text-green-400"), when: isComplete)
|
.attributes(.class("is-drawer-close:text-green-400"), when: isComplete)
|
||||||
.attributes(.class("is-drawer-close:text-error"), when: !isComplete && !hideIsComplete)
|
.attributes(.class("is-drawer-close:text-error"), when: !isComplete && !hideIsComplete)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private func row(
|
private func row(
|
||||||
title: String,
|
title: String,
|
||||||
@@ -234,10 +234,11 @@ struct Sidebar: HTML {
|
|||||||
route: SiteRoute.View,
|
route: SiteRoute.View,
|
||||||
isComplete: Bool,
|
isComplete: Bool,
|
||||||
hideIsComplete: Bool = false
|
hideIsComplete: Bool = false
|
||||||
) -> some HTML<HTMLTag.div> {
|
) -> some HTML<HTMLTag.a> {
|
||||||
row(
|
row(
|
||||||
title: title, icon: icon, href: SiteRoute.View.router.path(for: route),
|
title: title, icon: icon, href: SiteRoute.View.router.path(for: route),
|
||||||
isComplete: isComplete, hideIsComplete: hideIsComplete
|
isComplete: isComplete, hideIsComplete: hideIsComplete
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ struct RoomForm: HTML, Sendable {
|
|||||||
self.room = room
|
self.room = room
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var route: String {
|
||||||
|
SiteRoute.View.router.path(
|
||||||
|
for: .project(.detail(projectID, .rooms(.index)))
|
||||||
|
)
|
||||||
|
.appendingPath(room?.id)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
ModalForm(id: id, dismiss: dismiss) {
|
ModalForm(id: id, dismiss: dismiss) {
|
||||||
h1(.class("text-3xl font-bold pb-6")) { "Room" }
|
h1(.class("text-3xl font-bold pb-6")) { "Room" }
|
||||||
@@ -34,8 +41,8 @@ struct RoomForm: HTML, Sendable {
|
|||||||
.class("modal-backdrop"),
|
.class("modal-backdrop"),
|
||||||
.init(name: "method", value: "dialog"),
|
.init(name: "method", value: "dialog"),
|
||||||
room == nil
|
room == nil
|
||||||
? .hx.post(route: .project(.detail(projectID, .rooms(.index))))
|
? .hx.post(route)
|
||||||
: .hx.patch(route: .project(.detail(projectID, .rooms(.index)))),
|
: .hx.patch(route),
|
||||||
.hx.target("body"),
|
.hx.target("body"),
|
||||||
.hx.swap(.outerHTML)
|
.hx.swap(.outerHTML)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -121,9 +121,11 @@ struct RoomsView: HTML, Sendable {
|
|||||||
Number(room.registerCount)
|
Number(room.registerCount)
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
div(.class("flex justify-end space-x-6")) {
|
div(.class("flex justify-end")) {
|
||||||
|
div(.class("join")) {
|
||||||
TrashButton()
|
TrashButton()
|
||||||
.attributes(
|
.attributes(
|
||||||
|
.class("join-item"),
|
||||||
.hx.delete(
|
.hx.delete(
|
||||||
route: .project(.detail(room.projectID, .rooms(.delete(id: room.id))))),
|
route: .project(.detail(room.projectID, .rooms(.delete(id: room.id))))),
|
||||||
.hx.target("closest tr"),
|
.hx.target("closest tr"),
|
||||||
@@ -131,9 +133,11 @@ struct RoomsView: HTML, Sendable {
|
|||||||
)
|
)
|
||||||
EditButton()
|
EditButton()
|
||||||
.attributes(
|
.attributes(
|
||||||
|
.class("join-item"),
|
||||||
.showModal(id: "roomForm_\(room.name)")
|
.showModal(id: "roomForm_\(room.name)")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
RoomForm(
|
RoomForm(
|
||||||
id: "roomForm_\(room.name)",
|
id: "roomForm_\(room.name)",
|
||||||
dismiss: true,
|
dismiss: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user