WIP: Updates test html snapshots, working on validation when delegating airflow to a different room.
Some checks failed
CI / Linux Tests (push) Has been cancelled

This commit is contained in:
2026-02-06 17:01:43 -05:00
parent f2c79ad56f
commit 0775474f57
6 changed files with 179 additions and 47 deletions

View File

@@ -84,6 +84,14 @@ extension DatabaseClient.Rooms: TestDependencyKey {
extension Room.Create {
func toModel(projectID: Project.ID) throws -> RoomModel {
var registerCount = registerCount
// Set register count appropriately when delegatedTo is set / changes.
if delegatedTo != nil {
registerCount = 0
} else if registerCount == 0 {
registerCount = 1
}
return .init(
name: name,
heatingLoad: heatingLoad,
@@ -229,13 +237,28 @@ final class RoomModel: Model, @unchecked Sendable, Validatable {
Validator.validate(\.coolingLoad)
.errorLabel("Cooling Load", inline: true)
Validator.validate(\.registerCount, with: .greaterThanOrEquals(1))
Validator.validate(\.registerCount, with: .greaterThanOrEquals($room.id == nil ? 1 : 0))
.errorLabel("Register Count", inline: true)
Validator.validate(\.rectangularSizes)
}
}
func validateAndSave(on database: Database) async throws {
try self.validate()
if let delegateTo = $room.id {
guard let parent = try await RoomModel.find(delegateTo, on: database) else {
throw ValidationError("Can not find room: \(delegateTo), to delegate airflow to.")
}
guard parent.$room.id == nil else {
throw ValidationError(
"Can not delegate airflow to a room that also delegates it's own airflow."
)
}
}
try await save(on: database)
}
}
extension Room.CoolingLoad: Validatable {

View File

@@ -59,3 +59,24 @@ extension Select where Element: Identifiable, Element.ID == UUID, Element: Senda
}
}
extension Select
where Element: Identifiable, Element.ID == UUID, Element: Sendable, Label == HTMLText {
public init(
_ items: [Element],
label keyPath: KeyPath<Element, String>,
placeholder: String? = nil,
selected: @escaping @Sendable (Element) -> Bool = { _ in false }
) {
self.init(
items,
placeholder: placeholder,
value: { $0.id.uuidString },
selected: selected,
label: { HTMLText($0[keyPath: keyPath]) }
)
}
}

View File

@@ -30,13 +30,17 @@ struct RoomForm: HTML, Sendable {
self.room = room
}
var route: String {
private var route: String {
SiteRoute.View.router.path(
for: .project(.detail(projectID, .rooms(.index)))
)
.appendingPath(room?.id)
}
private var selectableRooms: [Room] {
rooms.filter { $0.delegatedTo == nil }
}
var body: some HTML {
ModalForm(id: Self.id(room), dismiss: dismiss) {
h1(.class("text-3xl font-bold pb-6")) { "Room" }
@@ -104,28 +108,15 @@ struct RoomForm: HTML, Sendable {
.id("registerCount")
)
label(.class("select w-full"), .id("delegateToSelect")) {
label(.class("select w-full")) {
span(.class("label")) { "Room" }
Select(rooms, placeholder: "Delegate Airflow") {
$0.name
}
Select(selectableRooms, label: \.name, placeholder: "Delegate Airflow")
.attributes(.name("delegatedTo"))
}
SubmitButton()
.attributes(.class("btn-block"))
}
}
script {
"""
function myClick() {
console.log('clicked');
const simple = document.getElementById('simple');
console.log(simple.style.display);
simple.style.display = 'block';
console.log(simple.style.display);
}
"""
}
}
}

View File

@@ -17,11 +17,6 @@ struct RoomsView: HTML, Sendable {
var body: some HTML {
div(.class("flex w-full flex-col")) {
input(.type(.checkbox), .name("delegateToCheckbox"), .on(.click, "showElement('simple');"))
div(.style("display: none;"), .id("simple"), .class("hidden")) {
"This is hidden"
}
PageTitleRow {
div(.class("flex grid grid-cols-3 w-full gap-y-4")) {
@@ -107,6 +102,11 @@ struct RoomsView: HTML, Sendable {
"Register Count"
}
}
th {
div(.class("flex justify-center")) {
"Delegated To"
}
}
th {
div(.class("flex justify-end me-2 space-x-4")) {
@@ -151,10 +151,11 @@ struct RoomsView: HTML, Sendable {
var coolingSensible: Double {
try! room.coolingLoad.ensured(shr: shr).sensible
// guard let value = room.coolingSensible else {
// return room.coolingTotal * shr
// }
// return value
}
var delegatedToRoomName: String? {
guard let delegatedToID = room.delegatedTo else { return nil }
return rooms.first(where: { $0.id == delegatedToID })?.name
}
init(room: Room, shr: Double?, rooms: [Room]) {
@@ -186,7 +187,14 @@ struct RoomsView: HTML, Sendable {
}
td {
div(.class("flex justify-center")) {
Number(room.registerCount)
Number(delegatedToRoomName != nil ? 0 : room.registerCount)
}
}
td {
if let name = delegatedToRoomName {
div(.class("flex justify-center")) {
name
}
}
}
td {

View File

@@ -11,9 +11,9 @@ struct CSVParsingTests {
let parser = CSVParser.liveValue
let input = """
Name,Heating Load,Cooling Total,Cooling Sensible,Register Count
Bed-1,12345,12345,,2
Bed-2,1223,,1123,1
Name,Heating Load,Cooling Total,Cooling Sensible,Register Count,Delegated To
Bed-1,12345,12345,,2,
Bed-2,1223,,1123,1,
"""
let rooms = try await parser.parseRooms(.init(file: Data(input.utf8)))

View File

@@ -54,7 +54,7 @@ p-6 w-full">
<div class="col-span-2">
<h1 class="text-3xl font-bold">Room Loads</h1>
</div>
<div class="flex justify-end grow">
<div class="flex justify-end grow space-x-4">
<div class="tooltip tooltip-left" data-tip="Set sensible heat ratio">
<button class="btn btn-primary text-lg font-bold py-2 " onclick="shrForm.showModal()">
<div class="flex grow justify-end items-end space-x-4">
@@ -63,12 +63,6 @@ p-6 w-full">
</div>
</button>
</div>
<div class="tooltip tooltip-left" data-tip="Upload csv file">
<form hx-post="/projects/00000000-0000-0000-0000-000000000000/rooms/csv" hx-target="body" hx-swap="outerHTML" enctype="multipart/form-data">
<input type="file" name="file" accept=".csv">
<button class="btn btn-secondary" type="submit">Submit</button>
</form>
</div>
</div>
<div class="flex items-end space-x-4 font-bold">
<span class="text-lg">Heating Total</span>
@@ -113,7 +107,13 @@ p-6 w-full">
<div class="flex justify-center">Register Count</div>
</th>
<th>
<div class="flex justify-end me-2">
<div class="flex justify-center">Delegated To</div>
</th>
<th>
<div class="flex justify-end me-2 space-x-4">
<div class="tooltip tooltip-left" data-tip="Upload CSV">
<button class="btn btn-secondary" onclick="uploadCSV.showModal()"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-plus-corner-icon lucide-file-plus-corner"><path d="M11.35 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v5.35"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="M14 19h6"/><path d="M17 16v6"/></svg></button>
</div>
<div class="tooltip tooltip-left" data-tip="Add Room">
<button type="button" class="btn btn-primary mx-auto tooltip-left" onclick="roomForm.showModal()"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-plus-icon lucide-circle-plus"><circle cx="12" cy="12" r="10"/><path d="M8 12h8"/><path d="M12 8v8"/></svg></button>
</div>
@@ -136,6 +136,7 @@ p-6 w-full">
<td>
<div class="flex justify-center"><span>1</span></div>
</td>
<td></td>
<td>
<div class="flex justify-end">
<div class="join">
@@ -167,7 +168,17 @@ p-6 w-full">
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
Registers</label><label class="input w-full"><span class="label"></span>
<input name="registerCount" type="number" min="1" required value="1"></label>
<input name="registerCount" type="number" min="1" required value="1" id="registerCount">
Room</label><label class="select w-full"><span class="label"></span>
<select name="delegatedTo">
<option selected disabled>Delegate Airflow</option>
<option value="00000000-0000-0000-0000-000000000001">Bed-1</option>
<option value="00000000-0000-0000-0000-000000000002">Entry</option>
<option value="00000000-0000-0000-0000-000000000003">Family Room</option>
<option value="00000000-0000-0000-0000-000000000004">Kitchen</option>
<option value="00000000-0000-0000-0000-000000000005">Living Room</option>
<option value="00000000-0000-0000-0000-000000000006">Master</option>
</select></label>
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
</form>
</div>
@@ -188,6 +199,7 @@ p-6 w-full">
<td>
<div class="flex justify-center"><span>2</span></div>
</td>
<td></td>
<td>
<div class="flex justify-end">
<div class="join">
@@ -219,7 +231,17 @@ p-6 w-full">
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
Registers</label><label class="input w-full"><span class="label"></span>
<input name="registerCount" type="number" min="1" required value="2"></label>
<input name="registerCount" type="number" min="1" required value="2" id="registerCount">
Room</label><label class="select w-full"><span class="label"></span>
<select name="delegatedTo">
<option selected disabled>Delegate Airflow</option>
<option value="00000000-0000-0000-0000-000000000001">Bed-1</option>
<option value="00000000-0000-0000-0000-000000000002">Entry</option>
<option value="00000000-0000-0000-0000-000000000003">Family Room</option>
<option value="00000000-0000-0000-0000-000000000004">Kitchen</option>
<option value="00000000-0000-0000-0000-000000000005">Living Room</option>
<option value="00000000-0000-0000-0000-000000000006">Master</option>
</select></label>
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
</form>
</div>
@@ -240,6 +262,7 @@ p-6 w-full">
<td>
<div class="flex justify-center"><span>3</span></div>
</td>
<td></td>
<td>
<div class="flex justify-end">
<div class="join">
@@ -271,7 +294,17 @@ p-6 w-full">
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
Registers</label><label class="input w-full"><span class="label"></span>
<input name="registerCount" type="number" min="1" required value="3"></label>
<input name="registerCount" type="number" min="1" required value="3" id="registerCount">
Room</label><label class="select w-full"><span class="label"></span>
<select name="delegatedTo">
<option selected disabled>Delegate Airflow</option>
<option value="00000000-0000-0000-0000-000000000001">Bed-1</option>
<option value="00000000-0000-0000-0000-000000000002">Entry</option>
<option value="00000000-0000-0000-0000-000000000003">Family Room</option>
<option value="00000000-0000-0000-0000-000000000004">Kitchen</option>
<option value="00000000-0000-0000-0000-000000000005">Living Room</option>
<option value="00000000-0000-0000-0000-000000000006">Master</option>
</select></label>
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
</form>
</div>
@@ -292,6 +325,7 @@ p-6 w-full">
<td>
<div class="flex justify-center"><span>2</span></div>
</td>
<td></td>
<td>
<div class="flex justify-end">
<div class="join">
@@ -323,7 +357,17 @@ p-6 w-full">
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
Registers</label><label class="input w-full"><span class="label"></span>
<input name="registerCount" type="number" min="1" required value="2"></label>
<input name="registerCount" type="number" min="1" required value="2" id="registerCount">
Room</label><label class="select w-full"><span class="label"></span>
<select name="delegatedTo">
<option selected disabled>Delegate Airflow</option>
<option value="00000000-0000-0000-0000-000000000001">Bed-1</option>
<option value="00000000-0000-0000-0000-000000000002">Entry</option>
<option value="00000000-0000-0000-0000-000000000003">Family Room</option>
<option value="00000000-0000-0000-0000-000000000004">Kitchen</option>
<option value="00000000-0000-0000-0000-000000000005">Living Room</option>
<option value="00000000-0000-0000-0000-000000000006">Master</option>
</select></label>
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
</form>
</div>
@@ -344,6 +388,7 @@ p-6 w-full">
<td>
<div class="flex justify-center"><span>2</span></div>
</td>
<td></td>
<td>
<div class="flex justify-end">
<div class="join">
@@ -375,7 +420,17 @@ p-6 w-full">
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
Registers</label><label class="input w-full"><span class="label"></span>
<input name="registerCount" type="number" min="1" required value="2"></label>
<input name="registerCount" type="number" min="1" required value="2" id="registerCount">
Room</label><label class="select w-full"><span class="label"></span>
<select name="delegatedTo">
<option selected disabled>Delegate Airflow</option>
<option value="00000000-0000-0000-0000-000000000001">Bed-1</option>
<option value="00000000-0000-0000-0000-000000000002">Entry</option>
<option value="00000000-0000-0000-0000-000000000003">Family Room</option>
<option value="00000000-0000-0000-0000-000000000004">Kitchen</option>
<option value="00000000-0000-0000-0000-000000000005">Living Room</option>
<option value="00000000-0000-0000-0000-000000000006">Master</option>
</select></label>
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
</form>
</div>
@@ -396,6 +451,7 @@ p-6 w-full">
<td>
<div class="flex justify-center"><span>2</span></div>
</td>
<td></td>
<td>
<div class="flex justify-end">
<div class="join">
@@ -427,7 +483,17 @@ p-6 w-full">
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
Registers</label><label class="input w-full"><span class="label"></span>
<input name="registerCount" type="number" min="1" required value="2"></label>
<input name="registerCount" type="number" min="1" required value="2" id="registerCount">
Room</label><label class="select w-full"><span class="label"></span>
<select name="delegatedTo">
<option selected disabled>Delegate Airflow</option>
<option value="00000000-0000-0000-0000-000000000001">Bed-1</option>
<option value="00000000-0000-0000-0000-000000000002">Entry</option>
<option value="00000000-0000-0000-0000-000000000003">Family Room</option>
<option value="00000000-0000-0000-0000-000000000004">Kitchen</option>
<option value="00000000-0000-0000-0000-000000000005">Living Room</option>
<option value="00000000-0000-0000-0000-000000000006">Master</option>
</select></label>
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
</form>
</div>
@@ -450,11 +516,34 @@ p-6 w-full">
Cooling Sensible</label><label class="input w-full"><span class="label"></span>
<input name="coolingSensible" type="number" placeholder="1234 (Optional)" min="0" value="">
Registers</label><label class="input w-full"><span class="label"></span>
<input name="registerCount" type="number" min="1" required value="1"></label>
<input name="registerCount" type="number" min="1" required value="1" id="registerCount">
Room</label><label class="select w-full"><span class="label"></span>
<select name="delegatedTo">
<option selected disabled>Delegate Airflow</option>
<option value="00000000-0000-0000-0000-000000000001">Bed-1</option>
<option value="00000000-0000-0000-0000-000000000002">Entry</option>
<option value="00000000-0000-0000-0000-000000000003">Family Room</option>
<option value="00000000-0000-0000-0000-000000000004">Kitchen</option>
<option value="00000000-0000-0000-0000-000000000005">Living Room</option>
<option value="00000000-0000-0000-0000-000000000006">Master</option>
</select></label>
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
</form>
</div>
</dialog>
<dialog id="uploadCSV" class="modal">
<div class="modal-box">
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="uploadCSV.close()"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
<div class="pb-6 space-y-3">
<h1 class="text-3xl font-bold">Upload CSV</h1>
<p class="text-sm italic">Drag and drop, or click to upload</p>
</div>
<form hx-post="/projects/00000000-0000-0000-0000-000000000000/rooms/csv" hx-target="body" hx-swap="outerHTML" enctype="multipart/form-data">
<input type="file" name="file" accept=".csv">
<button class="btn btn-secondary btn-block mt-6" type="submit">Submit</button>
</form>
</div>
</dialog>
</div>
</div>
</div>