Compare commits
12 Commits
1663c0a514
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
2e2c424850
|
|||
|
93894e4c25
|
|||
|
bab031f241
|
|||
|
c82f20bb60
|
|||
|
458b3bd644
|
|||
|
58023c4dbc
|
|||
|
30241fec60
|
|||
|
273da46db2
|
|||
|
6064b5267a
|
|||
|
69e8acc5d8
|
|||
|
066b3003d0
|
|||
|
b3c6c27a96
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,3 +11,5 @@ node_modules/
|
|||||||
tailwindcss
|
tailwindcss
|
||||||
.envrc
|
.envrc
|
||||||
*.pdf
|
*.pdf
|
||||||
|
.env
|
||||||
|
.env*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "5d6dad57209ac74e3c47d8e8eb162768b81c9e63e15df87d29019d46a13cfec2",
|
"originHash" : "c3efcfd33bc1490f59ae406e4e5292027b2d01cafee9fc625652213505df50fb",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "async-http-client",
|
"identity" : "async-http-client",
|
||||||
@@ -226,6 +226,15 @@
|
|||||||
"version" : "4.2.0"
|
"version" : "4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-custom-dump",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/pointfreeco/swift-custom-dump",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "93a8aa4937030b606de42f44b17870249f49af0b",
|
||||||
|
"version" : "1.3.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-dependencies",
|
"identity" : "swift-dependencies",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -361,6 +370,15 @@
|
|||||||
"version" : "2.9.1"
|
"version" : "2.9.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "swift-snapshot-testing",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/pointfreeco/swift-snapshot-testing",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "a8b7c5e0ed33d8ab8887d1654d9b59f2cbad529b",
|
||||||
|
"version" : "1.18.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "swift-syntax",
|
"identity" : "swift-syntax",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ let package = Package(
|
|||||||
.library(name: "ApiController", targets: ["ApiController"]),
|
.library(name: "ApiController", targets: ["ApiController"]),
|
||||||
.library(name: "AuthClient", targets: ["AuthClient"]),
|
.library(name: "AuthClient", targets: ["AuthClient"]),
|
||||||
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
|
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
|
||||||
|
.library(name: "EnvClient", targets: ["EnvClient"]),
|
||||||
|
.library(name: "FileClient", targets: ["FileClient"]),
|
||||||
|
.library(name: "HTMLSnapshotTesting", targets: ["HTMLSnapshotTesting"]),
|
||||||
.library(name: "PdfClient", targets: ["PdfClient"]),
|
.library(name: "PdfClient", targets: ["PdfClient"]),
|
||||||
.library(name: "ProjectClient", targets: ["ProjectClient"]),
|
.library(name: "ProjectClient", targets: ["ProjectClient"]),
|
||||||
.library(name: "ManualDCore", targets: ["ManualDCore"]),
|
.library(name: "ManualDCore", targets: ["ManualDCore"]),
|
||||||
@@ -22,6 +25,7 @@ let package = Package(
|
|||||||
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.6.0"),
|
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.6.0"),
|
||||||
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
|
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
|
||||||
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0"),
|
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0"),
|
||||||
|
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.12.0"),
|
||||||
.package(url: "https://github.com/pointfreeco/swift-url-routing.git", from: "0.6.2"),
|
.package(url: "https://github.com/pointfreeco/swift-url-routing.git", from: "0.6.2"),
|
||||||
.package(url: "https://github.com/pointfreeco/vapor-routing.git", from: "0.1.3"),
|
.package(url: "https://github.com/pointfreeco/vapor-routing.git", from: "0.1.3"),
|
||||||
.package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "1.6.0"),
|
.package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "1.6.0"),
|
||||||
@@ -76,21 +80,59 @@ let package = Package(
|
|||||||
.product(name: "Vapor", package: "vapor"),
|
.product(name: "Vapor", package: "vapor"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
||||||
|
.target(
|
||||||
|
name: "EnvClient",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "FileClient",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
|
.product(name: "Vapor", package: "vapor"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "HTMLSnapshotTesting",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "Elementary", package: "elementary"),
|
||||||
|
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
|
||||||
|
]
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "PdfClient",
|
name: "PdfClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
.target(name: "EnvClient"),
|
||||||
|
.target(name: "FileClient"),
|
||||||
.target(name: "ManualDCore"),
|
.target(name: "ManualDCore"),
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
.product(name: "Elementary", package: "elementary"),
|
.product(name: "Elementary", package: "elementary"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "PdfClientTests",
|
||||||
|
dependencies: [
|
||||||
|
.target(name: "HTMLSnapshotTesting"),
|
||||||
|
.target(name: "PdfClient"),
|
||||||
|
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
|
||||||
|
]
|
||||||
|
// ,
|
||||||
|
// resources: [
|
||||||
|
// .copy("__Snapshots__")
|
||||||
|
// ]
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "ProjectClient",
|
name: "ProjectClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.target(name: "DatabaseClient"),
|
.target(name: "DatabaseClient"),
|
||||||
.target(name: "ManualDClient"),
|
.target(name: "ManualDClient"),
|
||||||
.target(name: "PdfClient"),
|
.target(name: "PdfClient"),
|
||||||
|
.product(name: "Vapor", package: "vapor"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
@@ -148,5 +190,15 @@ let package = Package(
|
|||||||
.product(name: "Vapor", package: "vapor"),
|
.product(name: "Vapor", package: "vapor"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "ViewControllerTests",
|
||||||
|
dependencies: [
|
||||||
|
.target(name: "ViewController"),
|
||||||
|
.target(name: "HTMLSnapshotTesting"),
|
||||||
|
],
|
||||||
|
resources: [
|
||||||
|
.copy("__Snapshots__")
|
||||||
|
]
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
12
Public/css/htmx.css
Normal file
12
Public/css/htmx.css
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.htmx-indicator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.htmx-request .htmx-indicator {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.htmx-request.htmx-indicator {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,4 +2,3 @@
|
|||||||
@plugin "daisyui" {
|
@plugin "daisyui" {
|
||||||
themes: all;
|
themes: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@
|
|||||||
-webkit-print-color-adjust: exact;
|
-webkit-print-color-adjust: exact;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
* {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
h1 { font-size: 24px; }
|
||||||
|
h2 { font-size: 16px; }
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|||||||
63
Public/js/htmx-download.js
Normal file
63
Public/js/htmx-download.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// Copied from: https://github.com/dakixr/htmx-download/blob/main/htmx-download.js
|
||||||
|
htmx.defineExtension('htmx-download', {
|
||||||
|
onEvent: function(name, evt) {
|
||||||
|
|
||||||
|
if (name === 'htmx:beforeRequest') {
|
||||||
|
// Set the responseType to 'arraybuffer' to handle binary data
|
||||||
|
evt.detail.xhr.responseType = 'arraybuffer';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === 'htmx:beforeSwap') {
|
||||||
|
const xhr = evt.detail.xhr;
|
||||||
|
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
// Parse headers
|
||||||
|
const headers = {};
|
||||||
|
const headerStr = xhr.getAllResponseHeaders();
|
||||||
|
const headerArr = headerStr.trim().split(/[\r\n]+/);
|
||||||
|
headerArr.forEach((line) => {
|
||||||
|
const parts = line.split(": ");
|
||||||
|
const header = parts.shift().toLowerCase();
|
||||||
|
const value = parts.join(": ");
|
||||||
|
headers[header] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract filename
|
||||||
|
let filename = 'downloaded_file.xlsx';
|
||||||
|
if (headers['content-disposition']) {
|
||||||
|
const filenameMatch = headers['content-disposition'].match(/filename\*?=(?:UTF-8'')?"?([^;\n"]+)/i);
|
||||||
|
if (filenameMatch && filenameMatch[1]) {
|
||||||
|
filename = decodeURIComponent(filenameMatch[1].replace(/['"]/g, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine MIME type
|
||||||
|
const mimetype = headers['content-type'] || 'application/octet-stream';
|
||||||
|
|
||||||
|
// Create Blob
|
||||||
|
const blob = new Blob([xhr.response], { type: mimetype });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
// Trigger download
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.style.display = "none";
|
||||||
|
link.href = url;
|
||||||
|
link.download = filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
setTimeout(() => {
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
link.remove();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.warn(`[htmx-download] Unexpected response status: ${xhr.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent htmx from swapping content
|
||||||
|
evt.detail.shouldSwap = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -36,6 +36,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
|
|||||||
// $0.dateFormatter = .liveValue
|
// $0.dateFormatter = .liveValue
|
||||||
$0.viewController = viewController
|
$0.viewController = viewController
|
||||||
$0.pdfClient = .liveValue
|
$0.pdfClient = .liveValue
|
||||||
|
$0.fileClient = .live(fileIO: request.fileio)
|
||||||
} operation: {
|
} operation: {
|
||||||
try await next.respond(to: request)
|
try await next.respond(to: request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ private let viewRouteMiddleware: [any Middleware] = [
|
|||||||
extension SiteRoute.View {
|
extension SiteRoute.View {
|
||||||
var middleware: [any Middleware]? {
|
var middleware: [any Middleware]? {
|
||||||
switch self {
|
switch self {
|
||||||
case .project, .user:
|
|
||||||
return viewRouteMiddleware
|
|
||||||
case .login, .signup, .test:
|
case .login, .signup, .test:
|
||||||
return nil
|
return nil
|
||||||
|
case .project, .user:
|
||||||
|
return viewRouteMiddleware
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ private func setupDatabase(
|
|||||||
let databaseClient = makeDatabaseClient(app.db)
|
let databaseClient = makeDatabaseClient(app.db)
|
||||||
|
|
||||||
if app.environment != .testing {
|
if app.environment != .testing {
|
||||||
try await app.migrations.add(databaseClient.migrations.run())
|
try await app.migrations.add(databaseClient.migrations())
|
||||||
}
|
}
|
||||||
|
|
||||||
return databaseClient
|
return databaseClient
|
||||||
@@ -128,6 +128,10 @@ private func siteHandler(
|
|||||||
return try await apiController.respond(route, request: request)
|
return try await apiController.respond(route, request: request)
|
||||||
case .health:
|
case .health:
|
||||||
return HTTPStatus.ok
|
return HTTPStatus.ok
|
||||||
|
// Generating a pdf return's a `Response` instead of `HTML` like other views, so we
|
||||||
|
// need to handle it seperately.
|
||||||
|
case .view(.project(.detail(let projectID, .pdf))):
|
||||||
|
return try await projectClient.generatePdf(projectID)
|
||||||
case .view(let route):
|
case .view(let route):
|
||||||
return try await viewController.respond(route: route, request: request)
|
return try await viewController.respond(route: route, request: request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ extension DatabaseClient {
|
|||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct Migrations: Sendable {
|
public struct Migrations: Sendable {
|
||||||
public var run: @Sendable () async throws -> [any AsyncMigration]
|
public var run: @Sendable () async throws -> [any AsyncMigration]
|
||||||
|
|
||||||
|
public func callAsFunction() async throws -> [any AsyncMigration] {
|
||||||
|
try await self.run()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,17 @@ extension DatabaseClient.Projects: TestDependencyKey {
|
|||||||
.with(\.$equipment)
|
.with(\.$equipment)
|
||||||
.with(\.$equivalentLengths)
|
.with(\.$equivalentLengths)
|
||||||
.with(\.$rooms)
|
.with(\.$rooms)
|
||||||
.with(\.$trunks, { $0.with(\.$rooms) })
|
.with(
|
||||||
|
\.$trunks,
|
||||||
|
{ trunk in
|
||||||
|
trunk.with(
|
||||||
|
\.$rooms,
|
||||||
|
{
|
||||||
|
$0.with(\.$room)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
.filter(\.$id == id)
|
.filter(\.$id == id)
|
||||||
.first()
|
.first()
|
||||||
else {
|
else {
|
||||||
@@ -51,7 +61,7 @@ extension DatabaseClient.Projects: TestDependencyKey {
|
|||||||
// TODO: Different error ??
|
// TODO: Different error ??
|
||||||
guard let equipmentInfo = model.equipment else { return nil }
|
guard let equipmentInfo = model.equipment else { return nil }
|
||||||
|
|
||||||
let trunks = try await model.trunks.toDTO(on: database)
|
let trunks = try model.trunks.toDTO()
|
||||||
|
|
||||||
return try .init(
|
return try .init(
|
||||||
project: model.toDTO(),
|
project: model.toDTO(),
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
|||||||
type: request.type
|
type: request.type
|
||||||
)
|
)
|
||||||
try await model.save(on: database)
|
try await model.save(on: database)
|
||||||
try await roomProxies.append(model.toDTO(on: database))
|
roomProxies.append(
|
||||||
|
.init(room: try room.toDTO(), registers: registers)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return try .init(
|
return try .init(
|
||||||
@@ -60,23 +62,30 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
|||||||
fetch: { projectID in
|
fetch: { projectID in
|
||||||
try await TrunkModel.query(on: database)
|
try await TrunkModel.query(on: database)
|
||||||
.with(\.$project)
|
.with(\.$project)
|
||||||
.with(\.$rooms)
|
.with(\.$rooms, { $0.with(\.$room) })
|
||||||
.filter(\.$project.$id == projectID)
|
.filter(\.$project.$id == projectID)
|
||||||
.all()
|
.all()
|
||||||
.toDTO(on: database)
|
.toDTO()
|
||||||
},
|
},
|
||||||
get: { id in
|
get: { id in
|
||||||
guard let model = try await TrunkModel.find(id, on: database) else {
|
guard
|
||||||
|
let model =
|
||||||
|
try await TrunkModel
|
||||||
|
.query(on: database)
|
||||||
|
.with(\.$rooms, { $0.with(\.$room) })
|
||||||
|
.filter(\.$id == id)
|
||||||
|
.first()
|
||||||
|
else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return try await model.toDTO(on: database)
|
return try model.toDTO()
|
||||||
},
|
},
|
||||||
update: { id, updates in
|
update: { id, updates in
|
||||||
guard
|
guard
|
||||||
let model =
|
let model =
|
||||||
try await TrunkModel
|
try await TrunkModel
|
||||||
.query(on: database)
|
.query(on: database)
|
||||||
.with(\.$rooms)
|
.with(\.$rooms, { $0.with(\.$room) })
|
||||||
.filter(\.$id == id)
|
.filter(\.$id == id)
|
||||||
.first()
|
.first()
|
||||||
else {
|
else {
|
||||||
@@ -84,7 +93,7 @@ extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
|||||||
}
|
}
|
||||||
try updates.validate()
|
try updates.validate()
|
||||||
try await model.applyUpdates(updates, on: database)
|
try await model.applyUpdates(updates, on: database)
|
||||||
return try await model.toDTO(on: database)
|
return try model.toDTO()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -201,10 +210,10 @@ final class TrunkRoomModel: Model, @unchecked Sendable {
|
|||||||
self.type = type.rawValue
|
self.type = type.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func toDTO(on database: any Database) async throws -> TrunkSize.RoomProxy {
|
func toDTO() throws -> TrunkSize.RoomProxy {
|
||||||
guard let room = try await RoomModel.find($room.id, on: database) else {
|
// guard let room = try await RoomModel.find($room.id, on: database) else {
|
||||||
throw NotFoundError()
|
// throw NotFoundError()
|
||||||
}
|
// }
|
||||||
return .init(
|
return .init(
|
||||||
room: try room.toDTO(),
|
room: try room.toDTO(),
|
||||||
registers: registers
|
registers: registers
|
||||||
@@ -251,18 +260,22 @@ final class TrunkModel: Model, @unchecked Sendable {
|
|||||||
self.name = name
|
self.name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
func toDTO(on database: any Database) async throws -> TrunkSize {
|
func toDTO() throws -> TrunkSize {
|
||||||
let rooms = try await withThrowingTaskGroup(of: TrunkSize.RoomProxy.self) { group in
|
// let rooms = try await withThrowingTaskGroup(of: TrunkSize.RoomProxy.self) { group in
|
||||||
for room in self.rooms {
|
// for room in self.rooms {
|
||||||
group.addTask {
|
// group.addTask {
|
||||||
try await room.toDTO(on: database)
|
// try await room.toDTO(on: database)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return try await group.reduce(into: [TrunkSize.RoomProxy]()) {
|
// return try await group.reduce(into: [TrunkSize.RoomProxy]()) {
|
||||||
$0.append($1)
|
// $0.append($1)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
let rooms = try rooms.reduce(into: [TrunkSize.RoomProxy]()) {
|
||||||
|
$0.append(try $1.toDTO())
|
||||||
}
|
}
|
||||||
|
|
||||||
return try .init(
|
return try .init(
|
||||||
@@ -340,17 +353,17 @@ final class TrunkModel: Model, @unchecked Sendable {
|
|||||||
|
|
||||||
extension Array where Element == TrunkModel {
|
extension Array where Element == TrunkModel {
|
||||||
|
|
||||||
func toDTO(on database: any Database) async throws -> [TrunkSize] {
|
func toDTO() throws -> [TrunkSize] {
|
||||||
try await withThrowingTaskGroup(of: TrunkSize.self) { group in
|
// try await withThrowingTaskGroup(of: TrunkSize.self) { group in
|
||||||
for model in self {
|
// for model in self {
|
||||||
group.addTask {
|
// group.addTask {
|
||||||
try await model.toDTO(on: database)
|
// try await model.toDTO(on: database)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return try await group.reduce(into: [TrunkSize]()) {
|
return try reduce(into: [TrunkSize]()) {
|
||||||
$0.append($1)
|
$0.append(try $1.toDTO())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
74
Sources/EnvClient/Interface.swift
Normal file
74
Sources/EnvClient/Interface.swift
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import Dependencies
|
||||||
|
import DependenciesMacros
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension DependencyValues {
|
||||||
|
|
||||||
|
/// Holds values defined in the process environment that are needed.
|
||||||
|
///
|
||||||
|
/// These are generally loaded from a `.env` file, but also have default values,
|
||||||
|
/// if not found.
|
||||||
|
public var env: @Sendable () throws -> EnvVars {
|
||||||
|
get { self[EnvClient.self].env }
|
||||||
|
set { self[EnvClient.self].env = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
struct EnvClient: Sendable {
|
||||||
|
public var env: @Sendable () throws -> EnvVars
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds values defined in the process environment that are needed.
|
||||||
|
///
|
||||||
|
/// These are generally loaded from a `.env` file, but also have default values,
|
||||||
|
/// if not found.
|
||||||
|
public struct EnvVars: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
/// The path to the pandoc executable on the system, used to generate pdf's.
|
||||||
|
public let pandocPath: String
|
||||||
|
|
||||||
|
/// The pdf engine to use with pandoc when creating pdf's.
|
||||||
|
public let pdfEngine: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
pandocPath: String = "/usr/bin/pandoc",
|
||||||
|
pdfEngine: String = "weasyprint"
|
||||||
|
) {
|
||||||
|
self.pandocPath = pandocPath
|
||||||
|
self.pdfEngine = pdfEngine
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case pandocPath = "PANDOC_PATH"
|
||||||
|
case pdfEngine = "PDF_ENGINE"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EnvClient: DependencyKey {
|
||||||
|
static let testValue = Self()
|
||||||
|
|
||||||
|
static let liveValue = Self(env: {
|
||||||
|
// Convert default values into a dictionary.
|
||||||
|
let defaults =
|
||||||
|
(try? encoder.encode(EnvVars()))
|
||||||
|
.flatMap { try? decoder.decode([String: String].self, from: $0) }
|
||||||
|
?? [:]
|
||||||
|
|
||||||
|
// Merge the default values with values found in process environment.
|
||||||
|
let assigned = defaults.merging(ProcessInfo.processInfo.environment, uniquingKeysWith: { $1 })
|
||||||
|
|
||||||
|
return (try? JSONSerialization.data(withJSONObject: assigned))
|
||||||
|
.flatMap { try? decoder.decode(EnvVars.self, from: $0) }
|
||||||
|
?? .init()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private let encoder: JSONEncoder = {
|
||||||
|
JSONEncoder()
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let decoder: JSONDecoder = {
|
||||||
|
JSONDecoder()
|
||||||
|
}()
|
||||||
40
Sources/FileClient/Interface.swift
Normal file
40
Sources/FileClient/Interface.swift
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import Dependencies
|
||||||
|
import DependenciesMacros
|
||||||
|
import Foundation
|
||||||
|
import Vapor
|
||||||
|
|
||||||
|
extension DependencyValues {
|
||||||
|
public var fileClient: FileClient {
|
||||||
|
get { self[FileClient.self] }
|
||||||
|
set { self[FileClient.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct FileClient: Sendable {
|
||||||
|
public typealias OnCompleteHandler = @Sendable () async throws -> Void
|
||||||
|
|
||||||
|
public var writeFile: @Sendable (String, String) async throws -> Void
|
||||||
|
public var removeFile: @Sendable (String) async throws -> Void
|
||||||
|
public var streamFile: @Sendable (String, @escaping OnCompleteHandler) async throws -> Response
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FileClient: TestDependencyKey {
|
||||||
|
public static let testValue = Self()
|
||||||
|
|
||||||
|
public static func live(fileIO: FileIO) -> Self {
|
||||||
|
.init(
|
||||||
|
writeFile: { contents, path in
|
||||||
|
try await fileIO.writeFile(ByteBuffer(string: contents), at: path)
|
||||||
|
},
|
||||||
|
removeFile: { path in
|
||||||
|
try FileManager.default.removeItem(atPath: path)
|
||||||
|
},
|
||||||
|
streamFile: { path, onComplete in
|
||||||
|
try await fileIO.asyncStreamFile(at: path) { _ in
|
||||||
|
try await onComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Sources/HTMLSnapshotTesting/Snapshotting.swift
Normal file
22
Sources/HTMLSnapshotTesting/Snapshotting.swift
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import Elementary
|
||||||
|
import SnapshotTesting
|
||||||
|
|
||||||
|
extension Snapshotting where Value == (any HTML), Format == String {
|
||||||
|
public static var html: Snapshotting {
|
||||||
|
var snapshotting = SimplySnapshotting.lines
|
||||||
|
.pullback { (html: any HTML) in html.renderFormatted() }
|
||||||
|
|
||||||
|
snapshotting.pathExtension = "html"
|
||||||
|
return snapshotting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Snapshotting where Value == String, Format == String {
|
||||||
|
public static var html: Snapshotting {
|
||||||
|
var snapshotting = SimplySnapshotting.lines
|
||||||
|
.pullback { $0 }
|
||||||
|
|
||||||
|
snapshotting.pathExtension = "html"
|
||||||
|
return snapshotting
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,16 +140,22 @@ extension Project {
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
||||||
extension Project {
|
extension Project {
|
||||||
public static let mock = Self(
|
|
||||||
id: UUID(0),
|
public static var mock: Self {
|
||||||
name: "Testy McTestface",
|
@Dependency(\.uuid) var uuid
|
||||||
streetAddress: "1234 Sesame Street",
|
@Dependency(\.date.now) var now
|
||||||
city: "Monroe",
|
|
||||||
state: "OH",
|
return .init(
|
||||||
zipCode: "55555",
|
id: uuid(),
|
||||||
createdAt: Date(),
|
name: "Testy McTestface",
|
||||||
updatedAt: Date()
|
streetAddress: "1234 Sesame Street",
|
||||||
)
|
city: "Monroe",
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "55555",
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -65,3 +65,15 @@ extension User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
|
||||||
|
extension User {
|
||||||
|
public static var mock: Self {
|
||||||
|
@Dependency(\.uuid) var uuid
|
||||||
|
@Dependency(\.date.now) var now
|
||||||
|
return .init(id: uuid(), email: "testy@example.com", createdAt: now, updatedAt: now)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import Dependencies
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension User {
|
extension User {
|
||||||
@@ -113,3 +114,27 @@ extension User.Profile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
extension User.Profile {
|
||||||
|
public static func mock(userID: User.ID) -> Self {
|
||||||
|
@Dependency(\.uuid) var uuid
|
||||||
|
@Dependency(\.date.now) var now
|
||||||
|
|
||||||
|
return .init(
|
||||||
|
id: uuid(),
|
||||||
|
userID: userID,
|
||||||
|
firstName: "Testy",
|
||||||
|
lastName: "McTestface",
|
||||||
|
companyName: "Acme Co.",
|
||||||
|
streetAddress: "1234 Sesame St",
|
||||||
|
city: "Monroe",
|
||||||
|
state: "OH",
|
||||||
|
zipCode: "55555",
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import DependenciesMacros
|
import DependenciesMacros
|
||||||
import Elementary
|
import Elementary
|
||||||
|
import EnvClient
|
||||||
|
import FileClient
|
||||||
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
extension DependencyValues {
|
extension DependencyValues {
|
||||||
|
|
||||||
|
/// Access the pdf client dependency that can be used to generate pdf's for
|
||||||
|
/// a project.
|
||||||
public var pdfClient: PdfClient {
|
public var pdfClient: PdfClient {
|
||||||
get { self[PdfClient.self] }
|
get { self[PdfClient.self] }
|
||||||
set { self[PdfClient.self] = newValue }
|
set { self[PdfClient.self] = newValue }
|
||||||
@@ -13,8 +18,24 @@ extension DependencyValues {
|
|||||||
|
|
||||||
@DependencyClient
|
@DependencyClient
|
||||||
public struct PdfClient: Sendable {
|
public struct PdfClient: Sendable {
|
||||||
|
/// Generate the html used to convert to pdf for a project.
|
||||||
public var html: @Sendable (Request) async throws -> (any HTML & Sendable)
|
public var html: @Sendable (Request) async throws -> (any HTML & Sendable)
|
||||||
public var markdown: @Sendable (Request) async throws -> String
|
|
||||||
|
/// Converts the generated html to a pdf.
|
||||||
|
///
|
||||||
|
/// **NOTE:** This is generally not used directly, instead use the overload that accepts a request,
|
||||||
|
/// which generates the html and does the conversion all in one step.
|
||||||
|
public var generatePdf: @Sendable (Project.ID, any HTML & Sendable) async throws -> Response
|
||||||
|
|
||||||
|
/// Generate a pdf for the given project request.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - request: The project data used to generate the pdf.
|
||||||
|
public func generatePdf(request: Request) async throws -> Response {
|
||||||
|
let html = try await self.html(request)
|
||||||
|
return try await self.generatePdf(request.project.id, html)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PdfClient: DependencyKey {
|
extension PdfClient: DependencyKey {
|
||||||
@@ -24,14 +45,38 @@ extension PdfClient: DependencyKey {
|
|||||||
html: { request in
|
html: { request in
|
||||||
request.toHTML()
|
request.toHTML()
|
||||||
},
|
},
|
||||||
markdown: { request in
|
generatePdf: { projectID, html in
|
||||||
request.toMarkdown()
|
@Dependency(\.fileClient) var fileClient
|
||||||
|
@Dependency(\.env) var env
|
||||||
|
|
||||||
|
let envVars = try env()
|
||||||
|
let baseUrl = "/tmp/\(projectID)"
|
||||||
|
try await fileClient.writeFile(html.render(), "\(baseUrl).html")
|
||||||
|
|
||||||
|
let process = Process()
|
||||||
|
let standardInput = Pipe()
|
||||||
|
let standardOutput = Pipe()
|
||||||
|
process.standardInput = standardInput
|
||||||
|
process.standardOutput = standardOutput
|
||||||
|
process.executableURL = URL(fileURLWithPath: envVars.pandocPath)
|
||||||
|
process.arguments = [
|
||||||
|
"\(baseUrl).html",
|
||||||
|
"--pdf-engine=\(envVars.pdfEngine)",
|
||||||
|
"--from=html",
|
||||||
|
"--css=Public/css/pdf.css",
|
||||||
|
"--output=\(baseUrl).pdf",
|
||||||
|
]
|
||||||
|
try process.run()
|
||||||
|
process.waitUntilExit()
|
||||||
|
|
||||||
|
return .init(htmlPath: "\(baseUrl).html", pdfPath: "\(baseUrl).pdf")
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PdfClient {
|
extension PdfClient {
|
||||||
|
/// Container for the data required to generate a pdf for a given project.
|
||||||
public struct Request: Codable, Equatable, Sendable {
|
public struct Request: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
public let project: Project
|
public let project: Project
|
||||||
@@ -44,6 +89,10 @@ extension PdfClient {
|
|||||||
public let frictionRate: FrictionRate
|
public let frictionRate: FrictionRate
|
||||||
public let projectSHR: Double
|
public let projectSHR: Double
|
||||||
|
|
||||||
|
var totalEquivalentLength: Double {
|
||||||
|
maxReturnTEL.totalEquivalentLength + maxSupplyTEL.totalEquivalentLength
|
||||||
|
}
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
project: Project,
|
project: Project,
|
||||||
rooms: [Room],
|
rooms: [Room],
|
||||||
@@ -66,6 +115,17 @@ extension PdfClient {
|
|||||||
self.projectSHR = projectSHR
|
self.projectSHR = projectSHR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct Response: Equatable, Sendable {
|
||||||
|
|
||||||
|
public let htmlPath: String
|
||||||
|
public let pdfPath: String
|
||||||
|
|
||||||
|
public init(htmlPath: String, pdfPath: String) {
|
||||||
|
self.htmlPath = htmlPath
|
||||||
|
self.pdfPath = pdfPath
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ struct PdfDocument: HTMLDocument {
|
|||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
div {
|
div {
|
||||||
h1(.class("headline")) { "Duct Calc" }
|
// h1(.class("headline")) { "Duct Calc" }
|
||||||
|
|
||||||
h2 { "Project" }
|
h2 { "Project" }
|
||||||
|
|
||||||
@@ -63,7 +63,6 @@ struct PdfDocument: HTMLDocument {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div(.class("section")) {
|
div(.class("section")) {
|
||||||
h2 { "Duct Sizes" }
|
h2 { "Duct Sizes" }
|
||||||
DuctSizesTable(rooms: request.ductSizes.rooms)
|
DuctSizesTable(rooms: request.ductSizes.rooms)
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import ManualDCore
|
|
||||||
|
|
||||||
extension PdfClient.Request {
|
|
||||||
|
|
||||||
func toMarkdown() -> String {
|
|
||||||
var retval = """
|
|
||||||
# Duct Calc
|
|
||||||
|
|
||||||
**Name:** \(project.name)
|
|
||||||
**Address:** \(project.streetAddress)
|
|
||||||
\(project.city), \(project.state) \(project.zipCode)
|
|
||||||
|
|
||||||
## Equipment
|
|
||||||
|
|
||||||
| | Value |
|
|
||||||
|:----------------|:--------------------------------|
|
|
||||||
| Static Pressure | \(equipmentInfo.staticPressure.string()) |
|
|
||||||
| Heating CFM | \(equipmentInfo.heatingCFM.string()) |
|
|
||||||
| Cooling CFM | \(equipmentInfo.coolingCFM.string()) |
|
|
||||||
|
|
||||||
## Friction Rate
|
|
||||||
|
|
||||||
| Component Loss | Value |
|
|
||||||
|:----------------|:--------------------------------|
|
|
||||||
|
|
||||||
"""
|
|
||||||
for row in componentLosses {
|
|
||||||
retval += "\(componentLossRow(row))\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
retval += """
|
|
||||||
|
|
||||||
|
|
||||||
| Results | Value |
|
|
||||||
|:-----------------|:---------------------------------|
|
|
||||||
| Available Static Pressure | \(frictionRate.availableStaticPressure.string()) |
|
|
||||||
| Total Equivalent Length | \(totalEquivalentLength.string()) |
|
|
||||||
| Friction Rate Design Value | \(frictionRate.value.string()) |
|
|
||||||
|
|
||||||
## Duct Sizes
|
|
||||||
|
|
||||||
| Register | Dsn CFM | Round Size | Velocity | Final Size | Flex Size | Height | Width |
|
|
||||||
|:---------|:--------|:----------------|:---------|:-----------|:----------|:-------|:------|
|
|
||||||
|
|
||||||
"""
|
|
||||||
for row in ductSizes.rooms {
|
|
||||||
retval += "\(registerRow(row))\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
retval += """
|
|
||||||
|
|
||||||
## Trunk Sizes
|
|
||||||
|
|
||||||
### Supply Trunks
|
|
||||||
|
|
||||||
| Name | Associated Supplies | Dsn CFM | Velocity | Final Size | Flex Size | Height | Width |
|
|
||||||
|:---------|:--------------------|:--------|:---------|:-----------|:----------|:-------|:------|
|
|
||||||
|
|
||||||
"""
|
|
||||||
for row in ductSizes.trunks.filter({ $0.type == .supply }) {
|
|
||||||
retval += "\(trunkRow(row))\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
retval += """
|
|
||||||
|
|
||||||
### Return Trunks / Run Outs
|
|
||||||
|
|
||||||
| Name | Associated Supplies | Dsn CFM | Velocity | Final Size | Flex Size | Height | Width |
|
|
||||||
|:---------|:--------------------|:--------|:---------|:-----------|:----------|:-------|:------|
|
|
||||||
|
|
||||||
"""
|
|
||||||
for row in ductSizes.trunks.filter({ $0.type == .return }) {
|
|
||||||
retval += "\(trunkRow(row))\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
return retval
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerRow(_ row: DuctSizes.RoomContainer) -> String {
|
|
||||||
return """
|
|
||||||
| \(row.roomName) | \(row.designCFM.value.string(digits: 0)) | \(row.roundSize.string()) | \(row.velocity.string()) | \(row.finalSize.string()) | \(row.flexSize.string()) | \(row.height?.string() ?? "") | \(row.width?.string() ?? "") |
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
func trunkRow(_ row: DuctSizes.TrunkContainer) -> String {
|
|
||||||
return """
|
|
||||||
| \(row.name ?? "") | \(associatedSupplyString(row)) | \(row.designCFM.value.string(digits: 0)) | \(row.roundSize.string()) | \(row.velocity.string()) | \(row.finalSize.string()) | \(row.flexSize.string()) | \(row.ductSize.height?.string() ?? "") | \(row.width?.string() ?? "") |
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
func componentLossRow(_ row: ComponentPressureLoss) -> String {
|
|
||||||
return """
|
|
||||||
| \(row.name) | \(row.value.string()) |
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalEquivalentLength: Double {
|
|
||||||
maxSupplyTEL.totalEquivalentLength + maxReturnTEL.totalEquivalentLength
|
|
||||||
}
|
|
||||||
|
|
||||||
func associatedSupplyString(_ row: DuctSizes.TrunkContainer) -> String {
|
|
||||||
row.associatedSupplyString(rooms: ductSizes.rooms)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DuctSizes.TrunkContainer {
|
|
||||||
|
|
||||||
func associatedSupplyString(rooms: [DuctSizes.RoomContainer]) -> String {
|
|
||||||
self.registerIDS(rooms: rooms)
|
|
||||||
.joined(separator: ", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ import DependenciesMacros
|
|||||||
import Elementary
|
import Elementary
|
||||||
import ManualDClient
|
import ManualDClient
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
import Vapor
|
||||||
|
|
||||||
extension DependencyValues {
|
extension DependencyValues {
|
||||||
public var projectClient: ProjectClient {
|
public var projectClient: ProjectClient {
|
||||||
@@ -27,10 +28,7 @@ public struct ProjectClient: Sendable {
|
|||||||
@Sendable (User.ID, Project.Create) async throws -> CreateProjectResponse
|
@Sendable (User.ID, Project.Create) async throws -> CreateProjectResponse
|
||||||
|
|
||||||
public var frictionRate: @Sendable (Project.ID) async throws -> FrictionRateResponse
|
public var frictionRate: @Sendable (Project.ID) async throws -> FrictionRateResponse
|
||||||
|
public var generatePdf: @Sendable (Project.ID) async throws -> Response
|
||||||
// FIX: Name to something to do with generating a pdf, just experimenting now.
|
|
||||||
public var toMarkdown: @Sendable (Project.ID) async throws -> String
|
|
||||||
public var toHTML: @Sendable (Project.ID) async throws -> (any HTML & Sendable)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProjectClient: TestDependencyKey {
|
extension ProjectClient: TestDependencyKey {
|
||||||
|
|||||||
@@ -5,6 +5,14 @@ import ManualDCore
|
|||||||
|
|
||||||
extension DatabaseClient {
|
extension DatabaseClient {
|
||||||
|
|
||||||
|
func calculateDuctSizes(
|
||||||
|
details: Project.Detail
|
||||||
|
) async throws -> (DuctSizes, DuctSizeSharedRequest) {
|
||||||
|
let (rooms, shared) = try await calculateRoomDuctSizes(details: details)
|
||||||
|
let (trunks, _) = try await calculateTrunkDuctSizes(details: details)
|
||||||
|
return (.init(rooms: rooms, trunks: trunks), shared)
|
||||||
|
}
|
||||||
|
|
||||||
func calculateDuctSizes(
|
func calculateDuctSizes(
|
||||||
projectID: Project.ID
|
projectID: Project.ID
|
||||||
) async throws -> (DuctSizes, DuctSizeSharedRequest, [Room]) {
|
) async throws -> (DuctSizes, DuctSizeSharedRequest, [Room]) {
|
||||||
@@ -24,6 +32,16 @@ extension DatabaseClient {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calculateRoomDuctSizes(
|
||||||
|
details: Project.Detail
|
||||||
|
) async throws -> ([DuctSizes.RoomContainer], DuctSizeSharedRequest) {
|
||||||
|
@Dependency(\.manualD) var manualD
|
||||||
|
|
||||||
|
let shared = try sharedDuctRequest(details: details)
|
||||||
|
let rooms = try await manualD.calculateRoomSizes(rooms: details.rooms, sharedRequest: shared)
|
||||||
|
return (rooms, shared)
|
||||||
|
}
|
||||||
|
|
||||||
func calculateRoomDuctSizes(
|
func calculateRoomDuctSizes(
|
||||||
projectID: Project.ID
|
projectID: Project.ID
|
||||||
) async throws -> ([DuctSizes.RoomContainer], DuctSizeSharedRequest) {
|
) async throws -> ([DuctSizes.RoomContainer], DuctSizeSharedRequest) {
|
||||||
@@ -40,6 +58,20 @@ extension DatabaseClient {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calculateTrunkDuctSizes(
|
||||||
|
details: Project.Detail
|
||||||
|
) async throws -> ([DuctSizes.TrunkContainer], DuctSizeSharedRequest) {
|
||||||
|
@Dependency(\.manualD) var manualD
|
||||||
|
|
||||||
|
let shared = try sharedDuctRequest(details: details)
|
||||||
|
let trunks = try await manualD.calculateTrunkSizes(
|
||||||
|
rooms: details.rooms,
|
||||||
|
trunks: details.trunks,
|
||||||
|
sharedRequest: shared
|
||||||
|
)
|
||||||
|
return (trunks, shared)
|
||||||
|
}
|
||||||
|
|
||||||
func calculateTrunkDuctSizes(
|
func calculateTrunkDuctSizes(
|
||||||
projectID: Project.ID
|
projectID: Project.ID
|
||||||
) async throws -> ([DuctSizes.TrunkContainer], DuctSizeSharedRequest) {
|
) async throws -> ([DuctSizes.TrunkContainer], DuctSizeSharedRequest) {
|
||||||
@@ -57,6 +89,32 @@ extension DatabaseClient {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sharedDuctRequest(details: Project.Detail) throws -> DuctSizeSharedRequest {
|
||||||
|
guard
|
||||||
|
let dfrResponse = designFrictionRate(
|
||||||
|
componentLosses: details.componentLosses,
|
||||||
|
equipmentInfo: details.equipmentInfo,
|
||||||
|
equivalentLengths: details.maxContainer
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
throw ProjectClientError("Project not complete.")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let projectSHR = details.project.sensibleHeatRatio else {
|
||||||
|
throw ProjectClientError("Project sensible heat ratio not set.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let ensuredTEL = try dfrResponse.ensureMaxContainer()
|
||||||
|
|
||||||
|
return .init(
|
||||||
|
equipmentInfo: dfrResponse.equipmentInfo,
|
||||||
|
maxSupplyLength: ensuredTEL.supply,
|
||||||
|
maxReturnLenght: ensuredTEL.return,
|
||||||
|
designFrictionRate: dfrResponse.designFrictionRate,
|
||||||
|
projectSHR: projectSHR
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func sharedDuctRequest(_ projectID: Project.ID) async throws -> DuctSizeSharedRequest {
|
func sharedDuctRequest(_ projectID: Project.ID) async throws -> DuctSizeSharedRequest {
|
||||||
|
|
||||||
guard let dfrResponse = try await designFrictionRate(projectID: projectID) else {
|
guard let dfrResponse = try await designFrictionRate(projectID: projectID) else {
|
||||||
@@ -107,25 +165,36 @@ extension DatabaseClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func designFrictionRate(
|
func designFrictionRate(
|
||||||
projectID: Project.ID
|
componentLosses: [ComponentPressureLoss],
|
||||||
) async throws -> DesignFrictionRateResponse? {
|
equipmentInfo: EquipmentInfo,
|
||||||
guard let equipmentInfo = try await equipment.fetch(projectID) else {
|
equivalentLengths: EffectiveLength.MaxContainer
|
||||||
return nil
|
) -> DesignFrictionRateResponse? {
|
||||||
}
|
guard let tel = equivalentLengths.total,
|
||||||
|
componentLosses.count > 0
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
let equivalentLengths = try await effectiveLength.fetchMax(projectID)
|
let availableStaticPressure = equipmentInfo.staticPressure - componentLosses.total
|
||||||
guard let tel = equivalentLengths.total else { return nil }
|
|
||||||
|
|
||||||
let componentLosses = try await componentLoss.fetch(projectID)
|
|
||||||
guard componentLosses.count > 0 else { return nil }
|
|
||||||
|
|
||||||
let availableStaticPressure =
|
|
||||||
equipmentInfo.staticPressure - componentLosses.total
|
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
designFrictionRate: (availableStaticPressure * 100) / tel,
|
designFrictionRate: (availableStaticPressure * 100) / tel,
|
||||||
equipmentInfo: equipmentInfo,
|
equipmentInfo: equipmentInfo,
|
||||||
telMaxContainer: equivalentLengths
|
telMaxContainer: equivalentLengths
|
||||||
)
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func designFrictionRate(
|
||||||
|
projectID: Project.ID
|
||||||
|
) async throws -> DesignFrictionRateResponse? {
|
||||||
|
|
||||||
|
guard let equipmentInfo = try await equipment.fetch(projectID) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return try await designFrictionRate(
|
||||||
|
componentLosses: componentLoss.fetch(projectID),
|
||||||
|
equipmentInfo: equipmentInfo,
|
||||||
|
equivalentLengths: effectiveLength.fetchMax(projectID)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import DatabaseClient
|
import DatabaseClient
|
||||||
import Dependencies
|
import Dependencies
|
||||||
|
import FileClient
|
||||||
import Logging
|
import Logging
|
||||||
import ManualDClient
|
import ManualDClient
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
@@ -11,6 +12,7 @@ extension ProjectClient: DependencyKey {
|
|||||||
@Dependency(\.database) var database
|
@Dependency(\.database) var database
|
||||||
@Dependency(\.manualD) var manualD
|
@Dependency(\.manualD) var manualD
|
||||||
@Dependency(\.pdfClient) var pdfClient
|
@Dependency(\.pdfClient) var pdfClient
|
||||||
|
@Dependency(\.fileClient) var fileClient
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
calculateDuctSizes: { projectID in
|
calculateDuctSizes: { projectID in
|
||||||
@@ -35,11 +37,25 @@ extension ProjectClient: DependencyKey {
|
|||||||
frictionRate: { projectID in
|
frictionRate: { projectID in
|
||||||
try await manualD.frictionRate(projectID: projectID)
|
try await manualD.frictionRate(projectID: projectID)
|
||||||
},
|
},
|
||||||
toMarkdown: { projectID in
|
generatePdf: { projectID in
|
||||||
try await pdfClient.markdown(database.makePdfRequest(projectID))
|
let pdfResponse = try await pdfClient.generatePdf(
|
||||||
},
|
request: database.makePdfRequest(projectID)
|
||||||
toHTML: { projectID in
|
)
|
||||||
try await pdfClient.html(database.makePdfRequest(projectID))
|
|
||||||
|
let response = try await fileClient.streamFile(
|
||||||
|
pdfResponse.pdfPath,
|
||||||
|
{
|
||||||
|
try await fileClient.removeFile(pdfResponse.htmlPath)
|
||||||
|
try await fileClient.removeFile(pdfResponse.pdfPath)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
response.headers.replaceOrAdd(name: .contentType, value: "application/octet-stream")
|
||||||
|
response.headers.replaceOrAdd(
|
||||||
|
name: .contentDisposition, value: "attachment; filename=Duct-Calc.pdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -51,26 +67,44 @@ extension DatabaseClient {
|
|||||||
fileprivate func makePdfRequest(_ projectID: Project.ID) async throws -> PdfClient.Request {
|
fileprivate func makePdfRequest(_ projectID: Project.ID) async throws -> PdfClient.Request {
|
||||||
@Dependency(\.manualD) var manualD
|
@Dependency(\.manualD) var manualD
|
||||||
|
|
||||||
guard let project = try await projects.get(projectID) else {
|
guard let projectDetails = try await projects.detail(projectID) else {
|
||||||
throw ProjectClientError("Project not found. id: \(projectID)")
|
throw ProjectClientError("Project not found. id: \(projectID)")
|
||||||
}
|
}
|
||||||
let frictionRateResponse = try await manualD.frictionRate(projectID: projectID)
|
|
||||||
|
let (ductSizes, shared) = try await calculateDuctSizes(details: projectDetails)
|
||||||
|
|
||||||
|
let frictionRateResponse = try await manualD.frictionRate(details: projectDetails)
|
||||||
guard let frictionRate = frictionRateResponse.frictionRate else {
|
guard let frictionRate = frictionRateResponse.frictionRate else {
|
||||||
throw ProjectClientError("Friction rate not found. id: \(projectID)")
|
throw ProjectClientError("Friction rate not found. id: \(projectID)")
|
||||||
}
|
}
|
||||||
let (ductSizes, sharedInfo, rooms) = try await calculateDuctSizes(projectID: projectID)
|
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
project: project,
|
details: projectDetails,
|
||||||
rooms: rooms,
|
|
||||||
componentLosses: frictionRateResponse.componentLosses,
|
|
||||||
ductSizes: ductSizes,
|
ductSizes: ductSizes,
|
||||||
equipmentInfo: sharedInfo.equipmentInfo,
|
shared: shared,
|
||||||
maxSupplyTEL: sharedInfo.maxSupplyLength,
|
frictionRate: frictionRate
|
||||||
maxReturnTEL: sharedInfo.maxReturnLenght,
|
|
||||||
frictionRate: frictionRate,
|
|
||||||
projectSHR: sharedInfo.projectSHR
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension PdfClient.Request {
|
||||||
|
init(
|
||||||
|
details: Project.Detail,
|
||||||
|
ductSizes: DuctSizes,
|
||||||
|
shared: DuctSizeSharedRequest,
|
||||||
|
frictionRate: FrictionRate
|
||||||
|
) {
|
||||||
|
self.init(
|
||||||
|
project: details.project,
|
||||||
|
rooms: details.rooms,
|
||||||
|
componentLosses: details.componentLosses,
|
||||||
|
ductSizes: ductSizes,
|
||||||
|
equipmentInfo: details.equipmentInfo,
|
||||||
|
maxSupplyTEL: shared.maxSupplyLength,
|
||||||
|
maxReturnTEL: shared.maxReturnLenght,
|
||||||
|
frictionRate: frictionRate,
|
||||||
|
projectSHR: shared.projectSHR
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,6 @@ extension HTMLAttribute.hx {
|
|||||||
extension HTMLAttribute.hx {
|
extension HTMLAttribute.hx {
|
||||||
@Sendable
|
@Sendable
|
||||||
public static func indicator() -> HTMLAttribute {
|
public static func indicator() -> HTMLAttribute {
|
||||||
indicator(".hx-indicator")
|
indicator(".htmx-indicator")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +1,76 @@
|
|||||||
import Elementary
|
import Elementary
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct ResultView<
|
public struct ResultView<ValueView, ErrorView>: HTML where ValueView: HTML, ErrorView: HTML {
|
||||||
V: Sendable,
|
|
||||||
E: Error,
|
|
||||||
ValueView: HTML,
|
|
||||||
ErrorView: HTML
|
|
||||||
>: HTML {
|
|
||||||
|
|
||||||
let onSuccess: @Sendable (V) -> ValueView
|
let result: Result<ValueView, any Error>
|
||||||
let onError: @Sendable (E) -> ErrorView
|
let errorView: @Sendable (any Error) -> ErrorView
|
||||||
let result: Result<V, E>
|
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
result: Result<V, E>,
|
_ content: @escaping @Sendable () async throws -> ValueView,
|
||||||
@HTMLBuilder onSuccess: @escaping @Sendable (V) -> ValueView,
|
onError: @escaping @Sendable (any Error) -> ErrorView
|
||||||
@HTMLBuilder onError: @escaping @Sendable (E) -> ErrorView
|
) async {
|
||||||
) {
|
self.result = await Result(catching: content)
|
||||||
self.result = result
|
self.errorView = onError
|
||||||
self.onError = onError
|
|
||||||
self.onSuccess = onSuccess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some HTML {
|
public var body: some HTML {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let value):
|
case .success(let view):
|
||||||
onSuccess(value)
|
view
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
onError(error)
|
errorView(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ResultView {
|
extension ResultView where ErrorView == Styleguide.ErrorView {
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
result: Result<V, E>,
|
_ content: @escaping @Sendable () async throws -> ValueView
|
||||||
@HTMLBuilder onSuccess: @escaping @Sendable (V) -> ValueView
|
) async {
|
||||||
) where ErrorView == Styleguide.ErrorView<E> {
|
await self.init(
|
||||||
self.init(result: result, onSuccess: onSuccess) { error in
|
content,
|
||||||
Styleguide.ErrorView(error: error)
|
onError: { Styleguide.ErrorView(error: $0) }
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init<V: Sendable>(
|
||||||
|
catching: @escaping @Sendable () async throws -> V,
|
||||||
|
onSuccess content: @escaping @Sendable (V) -> ValueView
|
||||||
|
) async where ValueView: Sendable {
|
||||||
|
await self.init(
|
||||||
|
{
|
||||||
|
try await content(catching())
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
catching: @escaping @Sendable () async throws(E) -> V,
|
catching: @escaping @Sendable () async throws -> Void
|
||||||
@HTMLBuilder onSuccess: @escaping @Sendable (V) -> ValueView
|
) async where ValueView == EmptyHTML {
|
||||||
) async where ErrorView == Styleguide.ErrorView<E> {
|
|
||||||
await self.init(
|
await self.init(
|
||||||
result: .init(catching: catching),
|
catching: catching,
|
||||||
onSuccess: onSuccess
|
|
||||||
) { error in
|
|
||||||
Styleguide.ErrorView(error: error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(
|
|
||||||
catching: @escaping @Sendable () async throws(E) -> V,
|
|
||||||
) async where ErrorView == Styleguide.ErrorView<E>, V == Void, ValueView == EmptyHTML {
|
|
||||||
await self.init(
|
|
||||||
result: .init(catching: catching),
|
|
||||||
onSuccess: { EmptyHTML() }
|
onSuccess: { EmptyHTML() }
|
||||||
) { error in
|
)
|
||||||
Styleguide.ErrorView(error: error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ResultView where V == ValueView {
|
extension ResultView: Sendable where ValueView: Sendable, ErrorView: Sendable {}
|
||||||
|
|
||||||
public init(
|
public struct ErrorView: HTML, Sendable {
|
||||||
catching: @escaping @Sendable () async throws(E) -> V
|
let error: any Error
|
||||||
) async where ErrorView == Styleguide.ErrorView<E> {
|
|
||||||
await self.init(result: .init(catching: catching)) {
|
|
||||||
$0
|
|
||||||
} onError: { error in
|
|
||||||
Styleguide.ErrorView(error: error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ResultView: Sendable where Error: Sendable, ValueView: Sendable, ErrorView: Sendable {}
|
public init(error: any Error) {
|
||||||
|
|
||||||
public struct ErrorView<E: Error>: HTML, Sendable where Error: Sendable {
|
|
||||||
|
|
||||||
let error: E
|
|
||||||
|
|
||||||
public init(error: E) {
|
|
||||||
self.error = error
|
self.error = error
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some HTML<HTMLTag.div> {
|
public var body: some HTML<HTMLTag.div> {
|
||||||
div {
|
div {
|
||||||
h1(.class("text-2xl font-bold text-error")) { "Oops: Error" }
|
h1(.class("text-xl font-bold text-error")) { "Oops: Error" }
|
||||||
p {
|
p {
|
||||||
"\(error)"
|
"\(error.localizedDescription)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ extension ViewController.Request {
|
|||||||
// // TestPage(trunks: result.trunks, rooms: result.rooms)
|
// // TestPage(trunks: result.trunks, rooms: result.rooms)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
return try! await pdfClient.html(.mock())
|
// return try! await pdfClient.html(.mock())
|
||||||
|
return EmptyHTML()
|
||||||
case .login(let route):
|
case .login(let route):
|
||||||
switch route {
|
switch route {
|
||||||
case .index(let next):
|
case .index(let next):
|
||||||
@@ -193,10 +194,11 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
case .frictionRate(let route):
|
case .frictionRate(let route):
|
||||||
return await route.renderView(on: request, projectID: projectID)
|
return await route.renderView(on: request, projectID: projectID)
|
||||||
case .pdf:
|
case .pdf:
|
||||||
return await ResultView {
|
// FIX: This should return a pdf to download or be wrapped in a
|
||||||
try await projectClient.toHTML(projectID)
|
// result view.
|
||||||
}
|
// return try! await projectClient.toHTML(projectID)
|
||||||
// fatalError()
|
// This get's handled elsewhere because it returns a response, not a view.
|
||||||
|
fatalError()
|
||||||
case .rooms(let route):
|
case .rooms(let route):
|
||||||
return await route.renderView(on: request, projectID: projectID)
|
return await route.renderView(on: request, projectID: projectID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,11 +24,20 @@ struct DuctSizingView: HTML, Sendable {
|
|||||||
.attributes(.class("text-error font-bold italic mt-4"))
|
.attributes(.class("text-error font-bold italic mt-4"))
|
||||||
}
|
}
|
||||||
|
|
||||||
a(
|
div {
|
||||||
.class("btn btn-primary"),
|
button(
|
||||||
.href(route: .project(.detail(projectID, .pdf)))
|
.class("btn btn-primary"),
|
||||||
) {
|
.hx.get(route: .project(.detail(projectID, .pdf))),
|
||||||
"PDF"
|
.hx.ext("htmx-download"),
|
||||||
|
.hx.swap(.none),
|
||||||
|
.hx.indicator()
|
||||||
|
) {
|
||||||
|
span { "PDF" }
|
||||||
|
Indicator()
|
||||||
|
}
|
||||||
|
// div {
|
||||||
|
// Indicator()
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,10 @@ public struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable
|
|||||||
meta(.content("1024"), .name("og:image:height"))
|
meta(.content("1024"), .name("og:image:height"))
|
||||||
meta(.content(keywords), .name(.keywords))
|
meta(.content(keywords), .name(.keywords))
|
||||||
script(.src("https://unpkg.com/htmx.org@2.0.8")) {}
|
script(.src("https://unpkg.com/htmx.org@2.0.8")) {}
|
||||||
|
script(.src("/js/htmx-download.js")) {}
|
||||||
script(.src("/js/main.js")) {}
|
script(.src("/js/main.js")) {}
|
||||||
link(.rel(.stylesheet), .href("/css/output.css"))
|
link(.rel(.stylesheet), .href("/css/output.css"))
|
||||||
|
link(.rel(.stylesheet), .href("/css/htmx.css"))
|
||||||
link(
|
link(
|
||||||
.rel(.icon),
|
.rel(.icon),
|
||||||
.href("/images/favicon.ico"),
|
.href("/images/favicon.ico"),
|
||||||
|
|||||||
@@ -33,31 +33,31 @@ struct ManualDClientTests {
|
|||||||
#expect(response.velocity == 329)
|
#expect(response.velocity == 329)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
// @Test
|
||||||
func frictionRate() async throws {
|
// func frictionRate() async throws {
|
||||||
let response = try await manualD.frictionRate(
|
// let response = try await manualD.frictionRate(
|
||||||
.init(
|
// .init(
|
||||||
externalStaticPressure: 0.5,
|
// externalStaticPressure: 0.5,
|
||||||
componentPressureLosses: .mock,
|
// componentPressureLosses: .mock,
|
||||||
totalEffectiveLength: 185
|
// totalEffectiveLength: 185
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
#expect(numberFormatter.string(for: response.availableStaticPressure) == "0.11")
|
// #expect(numberFormatter.string(for: response.availableStaticPressure) == "0.11")
|
||||||
#expect(numberFormatter.string(for: response.frictionRate) == "0.06")
|
// #expect(numberFormatter.string(for: response.frictionRate) == "0.06")
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Test
|
// @Test
|
||||||
func frictionRateFails() async throws {
|
// func frictionRateFails() async throws {
|
||||||
await #expect(throws: ManualDError.self) {
|
// await #expect(throws: ManualDError.self) {
|
||||||
_ = try await manualD.frictionRate(
|
// _ = try await manualD.frictionRate(
|
||||||
.init(
|
// .init(
|
||||||
externalStaticPressure: 0.5,
|
// externalStaticPressure: 0.5,
|
||||||
componentPressureLosses: .mock,
|
// componentPressureLosses: .mock,
|
||||||
totalEffectiveLength: 0
|
// totalEffectiveLength: 0
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func totalEffectiveLength() async throws {
|
func totalEffectiveLength() async throws {
|
||||||
|
|||||||
26
Tests/PdfClientTests/PdfClientTests.swift
Normal file
26
Tests/PdfClientTests/PdfClientTests.swift
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import HTMLSnapshotTesting
|
||||||
|
import PdfClient
|
||||||
|
import SnapshotTesting
|
||||||
|
import Testing
|
||||||
|
|
||||||
|
@Suite(.snapshots(record: .missing))
|
||||||
|
struct PdfClientTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func html() async throws {
|
||||||
|
|
||||||
|
try await withDependencies {
|
||||||
|
$0.pdfClient = .liveValue
|
||||||
|
$0.uuid = .incrementing
|
||||||
|
$0.date.now = Date(timeIntervalSince1970: 1_234_567_890)
|
||||||
|
} operation: {
|
||||||
|
@Dependency(\.pdfClient) var pdfClient
|
||||||
|
|
||||||
|
let html = try await pdfClient.html(.mock())
|
||||||
|
assertSnapshot(of: html, as: .html)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
583
Tests/PdfClientTests/__Snapshots__/PdfClientTests/html.1.html
Normal file
583
Tests/PdfClientTests/__Snapshots__/PdfClientTests/html.1.html
Normal file
@@ -0,0 +1,583 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Duct Calc</title>
|
||||||
|
<link rel="stylesheet" href="/css/pdf.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<h2>Project</h2>
|
||||||
|
<div class="flex">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="label">Name</td>
|
||||||
|
<td>Testy McTestface</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label">Address</td>
|
||||||
|
<td>
|
||||||
|
<p>
|
||||||
|
1234 Sesame Street
|
||||||
|
<br>
|
||||||
|
Monroe, OH 55555
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table></table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<div class="flex">
|
||||||
|
<h2>Equipment</h2>
|
||||||
|
<h2>Friction Rate</h2>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="container">
|
||||||
|
<div class="table-container">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Equipment</th>
|
||||||
|
<th class="justify-end">Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Static Pressure</td>
|
||||||
|
<td class="justify-end">0.5</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Heating CFM</td>
|
||||||
|
<td class="justify-end">900</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Cooling CFM</td>
|
||||||
|
<td class="justify-end">1,000</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="table-container">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Friction Rate</th>
|
||||||
|
<th class="justify-end">Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>evaporator-coil</td>
|
||||||
|
<td class="justify-end">0.2</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>filter</td>
|
||||||
|
<td class="justify-end">0.1</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>supply-outlet</td>
|
||||||
|
<td class="justify-end">0.03</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>return-grille</td>
|
||||||
|
<td class="justify-end">0.03</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>balancing-damper</td>
|
||||||
|
<td class="justify-end">0.03</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Duct Sizes</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Dsn CFM</th>
|
||||||
|
<th>Round Size</th>
|
||||||
|
<th>Velocity</th>
|
||||||
|
<th>Final Size</th>
|
||||||
|
<th>Flex Size</th>
|
||||||
|
<th>Height</th>
|
||||||
|
<th>Width</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Bed-1</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entry</td>
|
||||||
|
<td>88</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entry</td>
|
||||||
|
<td>88</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kitchen</td>
|
||||||
|
<td>95</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kitchen</td>
|
||||||
|
<td>95</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Living Room</td>
|
||||||
|
<td>127</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Living Room</td>
|
||||||
|
<td>127</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Master</td>
|
||||||
|
<td>87</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Master</td>
|
||||||
|
<td>87</td>
|
||||||
|
<td>7</td>
|
||||||
|
<td>489</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td>8</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Supply Trunk / Run Outs</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead class="bg-green">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Dsn CFM</th>
|
||||||
|
<th>Round Size</th>
|
||||||
|
<th>Velocity</th>
|
||||||
|
<th>Final Size</th>
|
||||||
|
<th>Flex Size</th>
|
||||||
|
<th>Height</th>
|
||||||
|
<th>Width</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>1,000</td>
|
||||||
|
<td>18</td>
|
||||||
|
<td>987</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Return Trunk / Run Outs</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead class="bg-green">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Dsn CFM</th>
|
||||||
|
<th>Round Size</th>
|
||||||
|
<th>Velocity</th>
|
||||||
|
<th>Final Size</th>
|
||||||
|
<th>Flex Size</th>
|
||||||
|
<th>Height</th>
|
||||||
|
<th>Width</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td>1,000</td>
|
||||||
|
<td>18</td>
|
||||||
|
<td>987</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Total Equivalent Lengths</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Straight Lengths</th>
|
||||||
|
<th>Groups</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Supply - 1</td>
|
||||||
|
<td>supply</td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<li>10</li>
|
||||||
|
<li>25</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="effectiveLengthGroupHeader">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Length</th>
|
||||||
|
<th>Quantity</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>1-a</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>20</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>2-b</td>
|
||||||
|
<td>30</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>30</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>3-a</td>
|
||||||
|
<td>10</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>10</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>12-a</td>
|
||||||
|
<td>10</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>10</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td>105</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Return - 1</td>
|
||||||
|
<td>return</td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
<li>10</li>
|
||||||
|
<li>20</li>
|
||||||
|
<li>5</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="effectiveLengthGroupHeader">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Length</th>
|
||||||
|
<th>Quantity</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>5-a</td>
|
||||||
|
<td>10</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>10</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>6-a</td>
|
||||||
|
<td>15</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>15</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>7-a</td>
|
||||||
|
<td>20</td>
|
||||||
|
<td>1</td>
|
||||||
|
<td>20</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td>80</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Register Detail</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Heating BTU</th>
|
||||||
|
<th>Cooling BTU</th>
|
||||||
|
<th>Heating CFM</th>
|
||||||
|
<th>Cooling CFM</th>
|
||||||
|
<th>Design CFM</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Bed-1</td>
|
||||||
|
<td>3,913</td>
|
||||||
|
<td>2,472</td>
|
||||||
|
<td>83</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>92</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entry</td>
|
||||||
|
<td>4,142</td>
|
||||||
|
<td>1,458</td>
|
||||||
|
<td>88</td>
|
||||||
|
<td>54</td>
|
||||||
|
<td>88</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entry</td>
|
||||||
|
<td>4,142</td>
|
||||||
|
<td>1,458</td>
|
||||||
|
<td>88</td>
|
||||||
|
<td>54</td>
|
||||||
|
<td>88</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>3,262</td>
|
||||||
|
<td>2,482</td>
|
||||||
|
<td>69</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>92</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>3,262</td>
|
||||||
|
<td>2,482</td>
|
||||||
|
<td>69</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>92</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>3,262</td>
|
||||||
|
<td>2,482</td>
|
||||||
|
<td>69</td>
|
||||||
|
<td>92</td>
|
||||||
|
<td>92</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kitchen</td>
|
||||||
|
<td>2,259</td>
|
||||||
|
<td>2,548</td>
|
||||||
|
<td>48</td>
|
||||||
|
<td>95</td>
|
||||||
|
<td>95</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kitchen</td>
|
||||||
|
<td>2,259</td>
|
||||||
|
<td>2,548</td>
|
||||||
|
<td>48</td>
|
||||||
|
<td>95</td>
|
||||||
|
<td>95</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Living Room</td>
|
||||||
|
<td>3,776</td>
|
||||||
|
<td>3,414</td>
|
||||||
|
<td>80</td>
|
||||||
|
<td>127</td>
|
||||||
|
<td>127</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Living Room</td>
|
||||||
|
<td>3,776</td>
|
||||||
|
<td>3,414</td>
|
||||||
|
<td>80</td>
|
||||||
|
<td>127</td>
|
||||||
|
<td>127</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Master</td>
|
||||||
|
<td>4,101</td>
|
||||||
|
<td>1,038</td>
|
||||||
|
<td>87</td>
|
||||||
|
<td>39</td>
|
||||||
|
<td>87</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Master</td>
|
||||||
|
<td>4,101</td>
|
||||||
|
<td>1,038</td>
|
||||||
|
<td>87</td>
|
||||||
|
<td>39</td>
|
||||||
|
<td>87</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<h2>Room Detail</h2>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-green">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Heating BTU</th>
|
||||||
|
<th>Cooling Total BTU</th>
|
||||||
|
<th>Cooling Sensible BTU</th>
|
||||||
|
<th>Register Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Bed-1</td>
|
||||||
|
<td>3,913</td>
|
||||||
|
<td>2,472</td>
|
||||||
|
<td>2,052</td>
|
||||||
|
<td>1</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Entry</td>
|
||||||
|
<td>8,284</td>
|
||||||
|
<td>2,916</td>
|
||||||
|
<td>2,420</td>
|
||||||
|
<td>2</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>9,785</td>
|
||||||
|
<td>7,446</td>
|
||||||
|
<td>6,180</td>
|
||||||
|
<td>3</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Kitchen</td>
|
||||||
|
<td>4,518</td>
|
||||||
|
<td>5,096</td>
|
||||||
|
<td>4,230</td>
|
||||||
|
<td>2</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Living Room</td>
|
||||||
|
<td>7,553</td>
|
||||||
|
<td>6,829</td>
|
||||||
|
<td>5,668</td>
|
||||||
|
<td>2</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Master</td>
|
||||||
|
<td>8,202</td>
|
||||||
|
<td>2,076</td>
|
||||||
|
<td>1,723</td>
|
||||||
|
<td>2</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label">Totals</td>
|
||||||
|
<td class="heating label">42,255</td>
|
||||||
|
<td class="coolingTotal label">26,835</td>
|
||||||
|
<td class="coolingSensible label">22,273</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
208
Tests/ViewControllerTests/ViewControllerTests.swift
Normal file
208
Tests/ViewControllerTests/ViewControllerTests.swift
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
import AuthClient
|
||||||
|
import DatabaseClient
|
||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import HTMLSnapshotTesting
|
||||||
|
import Logging
|
||||||
|
import ManualDClient
|
||||||
|
import ManualDCore
|
||||||
|
import ProjectClient
|
||||||
|
import SnapshotTesting
|
||||||
|
import Testing
|
||||||
|
import ViewController
|
||||||
|
|
||||||
|
@Suite(.snapshots(record: .failed))
|
||||||
|
struct ViewControllerTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func login() async throws {
|
||||||
|
try await withDependencies {
|
||||||
|
$0.viewController = .liveValue
|
||||||
|
$0.authClient = .failing
|
||||||
|
} operation: {
|
||||||
|
@Dependency(\.viewController) var viewController
|
||||||
|
|
||||||
|
let login = try await viewController.view(.test(.login(.index())))
|
||||||
|
assertSnapshot(of: login, as: .html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func signup() async throws {
|
||||||
|
try await withDependencies {
|
||||||
|
$0.viewController = .liveValue
|
||||||
|
$0.authClient = .failing
|
||||||
|
} operation: {
|
||||||
|
@Dependency(\.viewController) var viewController
|
||||||
|
|
||||||
|
let signup = try await viewController.view(.test(.login(.index())))
|
||||||
|
assertSnapshot(of: signup, as: .html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func userProfile() async throws {
|
||||||
|
try await withDefaultDependencies {
|
||||||
|
@Dependency(\.viewController) var viewController
|
||||||
|
let html = try await viewController.view(.test(.user(.profile(.index))))
|
||||||
|
assertSnapshot(of: html, as: .html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func projectIndex() async throws {
|
||||||
|
let project = withDependencies {
|
||||||
|
$0.uuid = .incrementing
|
||||||
|
$0.date = .constant(.mock)
|
||||||
|
} operation: {
|
||||||
|
Project.mock
|
||||||
|
}
|
||||||
|
|
||||||
|
try await withDefaultDependencies {
|
||||||
|
$0.database.projects.fetch = { _, _ in
|
||||||
|
.init(items: [project], metadata: .init(page: 1, per: 25, total: 1))
|
||||||
|
}
|
||||||
|
} operation: {
|
||||||
|
@Dependency(\.viewController) var viewController
|
||||||
|
let html = try await viewController.view(.test(.project(.index)))
|
||||||
|
assertSnapshot(of: html, as: .html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func projectDetail() async throws {
|
||||||
|
|
||||||
|
let (
|
||||||
|
project,
|
||||||
|
rooms,
|
||||||
|
equipment,
|
||||||
|
tels,
|
||||||
|
componentLosses,
|
||||||
|
trunks
|
||||||
|
) = withDependencies {
|
||||||
|
$0.uuid = .incrementing
|
||||||
|
$0.date = .constant(.mock)
|
||||||
|
} operation: {
|
||||||
|
let project = Project.mock
|
||||||
|
let rooms = Room.mock(projectID: project.id)
|
||||||
|
let equipment = EquipmentInfo.mock(projectID: project.id)
|
||||||
|
let tels = EffectiveLength.mock(projectID: project.id)
|
||||||
|
let componentLosses = ComponentPressureLoss.mock(projectID: project.id)
|
||||||
|
let trunks = TrunkSize.mock(projectID: project.id, rooms: rooms)
|
||||||
|
|
||||||
|
return (
|
||||||
|
project,
|
||||||
|
rooms,
|
||||||
|
equipment,
|
||||||
|
tels,
|
||||||
|
componentLosses,
|
||||||
|
trunks
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
try await withDefaultDependencies {
|
||||||
|
$0.database.projects.get = { _ in project }
|
||||||
|
$0.database.projects.getCompletedSteps = { _ in
|
||||||
|
.init(equipmentInfo: true, rooms: true, equivalentLength: true, frictionRate: true)
|
||||||
|
}
|
||||||
|
$0.database.projects.getSensibleHeatRatio = { _ in 0.83 }
|
||||||
|
$0.database.rooms.fetch = { _ in rooms }
|
||||||
|
$0.database.equipment.fetch = { _ in equipment }
|
||||||
|
$0.database.effectiveLength.fetch = { _ in tels }
|
||||||
|
$0.database.effectiveLength.fetchMax = { _ in
|
||||||
|
.init(supply: tels.first, return: tels.last)
|
||||||
|
}
|
||||||
|
$0.database.componentLoss.fetch = { _ in componentLosses }
|
||||||
|
$0.projectClient.calculateDuctSizes = { _ in
|
||||||
|
.mock(equipmentInfo: equipment, rooms: rooms, trunks: trunks)
|
||||||
|
}
|
||||||
|
} operation: {
|
||||||
|
@Dependency(\.viewController) var viewController
|
||||||
|
|
||||||
|
var html = try await viewController.view(.test(.project(.detail(project.id, .index))))
|
||||||
|
assertSnapshot(of: html, as: .html)
|
||||||
|
|
||||||
|
html = try await viewController.view(.test(.project(.detail(project.id, .rooms(.index)))))
|
||||||
|
assertSnapshot(of: html, as: .html)
|
||||||
|
|
||||||
|
html = try await viewController.view(.test(.project(.detail(project.id, .equipment(.index)))))
|
||||||
|
assertSnapshot(of: html, as: .html)
|
||||||
|
|
||||||
|
html = try await viewController.view(
|
||||||
|
.test(.project(.detail(project.id, .equivalentLength(.index)))))
|
||||||
|
assertSnapshot(of: html, as: .html)
|
||||||
|
|
||||||
|
html = try await viewController.view(
|
||||||
|
.test(.project(.detail(project.id, .frictionRate(.index)))))
|
||||||
|
assertSnapshot(of: html, as: .html)
|
||||||
|
|
||||||
|
html = try await viewController.view(
|
||||||
|
.test(.project(.detail(project.id, .ductSizing(.index)))))
|
||||||
|
assertSnapshot(of: html, as: .html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createUserDependencies() -> (User, User.Profile) {
|
||||||
|
withDependencies {
|
||||||
|
$0.uuid = .incrementing
|
||||||
|
$0.date = .constant(.mock)
|
||||||
|
} operation: {
|
||||||
|
let user = User.mock
|
||||||
|
let profile = User.Profile.mock(userID: user.id)
|
||||||
|
return (user, profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func withDefaultDependencies<R>(
|
||||||
|
isolation: isolated (any Actor)? = #isolation,
|
||||||
|
_ updateDependencies: (inout DependencyValues) async throws -> Void = { _ in },
|
||||||
|
operation: () async throws -> R
|
||||||
|
) async rethrows -> R {
|
||||||
|
let (user, profile) = createUserDependencies()
|
||||||
|
|
||||||
|
return try await withDependencies {
|
||||||
|
$0.viewController = .liveValue
|
||||||
|
$0.authClient.currentUser = { user }
|
||||||
|
$0.database.userProfile.fetch = { _ in profile }
|
||||||
|
$0.manualD = .liveValue
|
||||||
|
try await updateDependencies(&$0)
|
||||||
|
} operation: {
|
||||||
|
try await operation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Date {
|
||||||
|
static let mock = Self(timeIntervalSince1970: 1_234_567_890)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ViewController.Request {
|
||||||
|
|
||||||
|
static func test(
|
||||||
|
_ route: SiteRoute.View,
|
||||||
|
isHtmxRequest: Bool = false,
|
||||||
|
logger: Logger = .init(label: "ViewControllerTests")
|
||||||
|
) -> Self {
|
||||||
|
.init(route: route, isHtmxRequest: isHtmxRequest, logger: logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AuthClient {
|
||||||
|
static let failing = Self(
|
||||||
|
createAndLogin: { _ in
|
||||||
|
throw TestError()
|
||||||
|
},
|
||||||
|
currentUser: {
|
||||||
|
throw TestError()
|
||||||
|
},
|
||||||
|
login: { _ in
|
||||||
|
throw TestError()
|
||||||
|
},
|
||||||
|
logout: {
|
||||||
|
throw TestError()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestError: Error {}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Duct Calc</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta content="ductcalc.com" name="og:site_name">
|
||||||
|
<meta content="Duct Calc" name="og:title">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="description">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="og:description">
|
||||||
|
<meta content="/images/mand_logo.png" name="og:image">
|
||||||
|
<meta content="/images/mand_logo.png" name="twitter:image">
|
||||||
|
<meta content="Duct Calc" name="twitter:image:alt">
|
||||||
|
<meta content="summary_large_image" name="twitter:card">
|
||||||
|
<meta content="1536" name="og:image:width">
|
||||||
|
<meta content="1024" name="og:image:height">
|
||||||
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<script src="https://unpkg.com/htmx-remove@latest" crossorigin="anonymous" integrity="sha384-NwB2Xh66PNEYfVki0ao13UAFmdNtMIdBKZ8sNGRT6hKfCPaINuZ4ScxS6vVAycPT"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="flex flex-col min-h-screen min-w-full justify-between">
|
||||||
|
<main class="flex flex-col min-h-screen min-w-full grow mb-auto">
|
||||||
|
<dialog id="loginForm" class="modal modal-open">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h1 class="text-2xl font-bold mb-6">Login</h1>
|
||||||
|
<form method="post" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="input validator w-full"> <svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="2.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<rect width="20" height="16" x="2" y="4" rx="2"></rect>
|
||||||
|
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input type="email" placeholder="Email" required name="email" id="email" autofocus></label>
|
||||||
|
<div class="validator-hint hidden">Enter valid email address.</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="input validator w-full"> <svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="2.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z"
|
||||||
|
></path>
|
||||||
|
<circle cx="16.5" cy="7.5" r=".5" fill="currentColor"></circle>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input type="password" placeholder="Password" required pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" minlength="8" name="password" id="password"></label>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<button class="btn btn-secondary mt-4 w-full">Login</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center"><a class="btn btn-link" href="/signup">Sign Up</a></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</main>
|
||||||
|
<div class="bottom-0 left-0 bg-error"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Duct Calc</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta content="ductcalc.com" name="og:site_name">
|
||||||
|
<meta content="Duct Calc" name="og:title">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="description">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="og:description">
|
||||||
|
<meta content="/images/mand_logo.png" name="og:image">
|
||||||
|
<meta content="/images/mand_logo.png" name="twitter:image">
|
||||||
|
<meta content="Duct Calc" name="twitter:image:alt">
|
||||||
|
<meta content="summary_large_image" name="twitter:card">
|
||||||
|
<meta content="1536" name="og:image:width">
|
||||||
|
<meta content="1024" name="og:image:height">
|
||||||
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<script src="https://unpkg.com/htmx-remove@latest" crossorigin="anonymous" integrity="sha384-NwB2Xh66PNEYfVki0ao13UAFmdNtMIdBKZ8sNGRT6hKfCPaINuZ4ScxS6vVAycPT"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="flex flex-col min-h-screen min-w-full justify-between">
|
||||||
|
<main class="flex flex-col min-h-screen min-w-full grow mb-auto">
|
||||||
|
<div class="drawer lg:drawer-open h-full">
|
||||||
|
<input id="my-drawer-1" type="checkbox" class="drawer-toggle">
|
||||||
|
<div class="drawer-content overflow-auto">
|
||||||
|
<nav class="navbar w-full bg-base-300 text-base-content shadow-sm mb-4">
|
||||||
|
<div class="flex flex-1 space-x-4 items-center">
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Open sidebar"><label for="my-drawer-1" class="size-7 btn btn-square btn-ghost hover:bg-neutral hover:text-white" aria-label="open sidebar"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-linejoin="round" stroke-linecap="round" stroke-width="2" fill="none" stroke="currentColor" class="my-1.5 inline-block"><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path><path d="M9 4v16"></path><path d="M14 10l2 2l-2 2"></path></svg></label></div>
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Home">
|
||||||
|
<a class="flex w-fit h-fit text-xl items-end px-4 py-2 btn btn-square btn-ghost hover:bg-neutral hover:text-white" href="/projects">
|
||||||
|
<img src="/images/mand_logo_sm.webp">
|
||||||
|
Duct Calc<span></span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Profile"><a href="/profile" class="btn btn-square btn-ghost hover:bg-neutral hover:text-white"><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-user-icon lucide-circle-user"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="10" r="3"/><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"/></svg></a></div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="p-4">
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between bg-secondary border-2 border-primary rounded-sm shadow-sm
|
||||||
|
p-6 w-full">
|
||||||
|
<h1 class="text-3xl font-bold">Project</h1>
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Edit project">
|
||||||
|
<button class="btn btn-primary" type="button" onclick="projectForm.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-zebra text-lg">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="label font-bold">Name</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end">Testy McTestface</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label font-bold">Street Address</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end">1234 Sesame Street</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label font-bold">City</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end">Monroe</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label font-bold">State</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end">OH</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="label font-bold">Zip</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end">55555</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<dialog id="projectForm" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="projectForm.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>
|
||||||
|
<h1 class="text-3xl font-bold pb-6 ps-2">Project</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" value="Testy McTestface" placeholder="Project Name" required autofocus>
|
||||||
|
Address</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="streetAddress" type="text" value="1234 Sesame Street" placeholder="Street Address" required>
|
||||||
|
City</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="city" type="text" value="Monroe" placeholder="City" required>
|
||||||
|
State</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="state" type="text" value="OH" placeholder="State" required>
|
||||||
|
Zip</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="zipCode" type="text" value="55555" placeholder="Zip Code" required></label>
|
||||||
|
<button class="btn btn-secondary btn-block my-6" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="drawer-side is-drawer-close:overflow-visible grow">
|
||||||
|
<label for="my-drawer-1" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||||
|
<div class="flex grow h-full flex-col items-start bg-base-300 text-base-content
|
||||||
|
is-drawer-close:min-w-[80px] is-drawer-open:max-w-[300px]">
|
||||||
|
<ul class="w-full grow">
|
||||||
|
<li class="flex w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000" hx-push-url="true" hx-target="body" hx-swap="outerHTML" data-active="true">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-map-pin-icon lucide-map-pin"><path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Project</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/rooms" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-door-closed-icon lucide-door-closed"><path d="M10 12h.01"/><path d="M18 20V6a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v14"/><path d="M2 20h20"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Rooms</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="flex w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/equipment" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-fan-icon lucide-fan"><path d="M10.827 16.379a6.082 6.082 0 0 1-8.618-7.002l5.412 1.45a6.082 6.082 0 0 1 7.002-8.618l-1.45 5.412a6.082 6.082 0 0 1 8.618 7.002l-5.412-1.45a6.082 6.082 0 0 1-7.002 8.618l1.45-5.412Z"/><path d="M12 12v.01"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Equipment</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/effective-lengths" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-ruler-dimension-line-icon lucide-ruler-dimension-line"><path d="M10 15v-3"/><path d="M14 15v-3"/><path d="M18 15v-3"/><path d="M2 8V4"/><path d="M22 6H2"/><path d="M22 8V4"/><path d="M6 15v-3"/><rect x="2" y="12" width="20" height="8" rx="2"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>T.E.L.</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/friction-rate" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-function-icon lucide-square-function"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="M9 17c2 0 2.8-1 2.8-2.8V10c0-2 1-3.3 3.2-3"/><path d="M9 11.2h5.7"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Friction Rate</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/duct-sizing" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center"><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-wind-icon lucide-wind"><path d="M12.8 19.6A2 2 0 1 0 14 16H2"/><path d="M17.5 8a2.5 2.5 0 1 1 2 4H2"/><path d="M9.8 4.4A2 2 0 1 1 11 8H2"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Duct Sizes</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<div class="bottom-0 left-0 bg-error">
|
||||||
|
<footer class="footer sm:footer-horizontal footer-center
|
||||||
|
bg-base-300 text-base-content p-4">
|
||||||
|
<aside>
|
||||||
|
<p>Copyright © 2026 - All rights reserved by Michael Housh</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,595 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Duct Calc</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta content="ductcalc.com" name="og:site_name">
|
||||||
|
<meta content="Duct Calc" name="og:title">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="description">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="og:description">
|
||||||
|
<meta content="/images/mand_logo.png" name="og:image">
|
||||||
|
<meta content="/images/mand_logo.png" name="twitter:image">
|
||||||
|
<meta content="Duct Calc" name="twitter:image:alt">
|
||||||
|
<meta content="summary_large_image" name="twitter:card">
|
||||||
|
<meta content="1536" name="og:image:width">
|
||||||
|
<meta content="1024" name="og:image:height">
|
||||||
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<script src="https://unpkg.com/htmx-remove@latest" crossorigin="anonymous" integrity="sha384-NwB2Xh66PNEYfVki0ao13UAFmdNtMIdBKZ8sNGRT6hKfCPaINuZ4ScxS6vVAycPT"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="flex flex-col min-h-screen min-w-full justify-between">
|
||||||
|
<main class="flex flex-col min-h-screen min-w-full grow mb-auto">
|
||||||
|
<div class="drawer lg:drawer-open h-full">
|
||||||
|
<input id="my-drawer-1" type="checkbox" class="drawer-toggle">
|
||||||
|
<div class="drawer-content overflow-auto">
|
||||||
|
<nav class="navbar w-full bg-base-300 text-base-content shadow-sm mb-4">
|
||||||
|
<div class="flex flex-1 space-x-4 items-center">
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Open sidebar"><label for="my-drawer-1" class="size-7 btn btn-square btn-ghost hover:bg-neutral hover:text-white" aria-label="open sidebar"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-linejoin="round" stroke-linecap="round" stroke-width="2" fill="none" stroke="currentColor" class="my-1.5 inline-block"><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path><path d="M9 4v16"></path><path d="M14 10l2 2l-2 2"></path></svg></label></div>
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Home">
|
||||||
|
<a class="flex w-fit h-fit text-xl items-end px-4 py-2 btn btn-square btn-ghost hover:bg-neutral hover:text-white" href="/projects">
|
||||||
|
<img src="/images/mand_logo_sm.webp">
|
||||||
|
Duct Calc<span></span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Profile"><a href="/profile" class="btn btn-square btn-ghost hover:bg-neutral hover:text-white"><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-user-icon lucide-circle-user"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="10" r="3"/><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"/></svg></a></div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="flex w-full flex-col">
|
||||||
|
<div class="flex justify-between bg-secondary border-2 border-primary rounded-sm shadow-sm
|
||||||
|
p-6 w-full">
|
||||||
|
<div class="flex grid grid-cols-3 w-full gap-y-4">
|
||||||
|
<div class="col-span-2">
|
||||||
|
<h1 class="text-3xl font-bold">Room Loads</h1>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end grow">
|
||||||
|
<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">
|
||||||
|
<span>Sensible Heat Ratio</span>
|
||||||
|
<div class="badge badge-lg badge-outline"><span>0.83</span></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-end space-x-4 font-bold">
|
||||||
|
<span class="text-lg">Heating Total</span>
|
||||||
|
<div class="badge badge-lg badge-outline badge-error"><span>42,255</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center items-end space-x-4 my-auto font-bold">
|
||||||
|
<span class="text-lg">Cooling Total</span>
|
||||||
|
<div class="badge badge-lg badge-outline badge-success"><span>26,835</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end space-x-4 me-4 my-auto font-bold">
|
||||||
|
<span class="text-lg">Cooling Sensible</span>
|
||||||
|
<div class="badge badge-lg badge-outline badge-info"><span>22,273</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="shrForm" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="shrForm.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>
|
||||||
|
<h1 class="text-xl font-bold mb-6">Sensible Heat Ratio</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/update-shr" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
SHR<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="sensibleHeatRatio" type="number" value="0.83" placeholder="0.83" min="0" max="1" step="0.01" autofocus></label>
|
||||||
|
<button class="btn btn-secondary btn-block my-6" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
<table class="table table-zebra text-lg" id="roomsTable">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-lg font-bold">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>
|
||||||
|
<div class="flex justify-center">Heating Load</div>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<div class="flex justify-center">Cooling Total</div>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<div class="flex justify-center">Cooling Sensible</div>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<div class="flex justify-center">Register Count</div>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<div class="flex justify-end me-2">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr id="roomRow_00000000000000000000000000000001">
|
||||||
|
<td>Bed-1</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>3,913</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>2,472</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>2,052</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>1</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<div class="join">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete room">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000001" hx-target="closest tr" hx-confirm="Are you sure?"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit room">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="roomForm_00000000000000000000000000000001.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="roomForm_00000000000000000000000000000001" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000001.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>
|
||||||
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000001" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000001">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" placeholder="Name" required autofocus value="Bed-1">
|
||||||
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="3913.0">
|
||||||
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="2472.0">
|
||||||
|
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>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="roomRow_00000000000000000000000000000002">
|
||||||
|
<td>Entry</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>8,284</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>2,916</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>2,420</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>2</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<div class="join">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete room">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000002" hx-target="closest tr" hx-confirm="Are you sure?"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit room">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="roomForm_00000000000000000000000000000002.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="roomForm_00000000000000000000000000000002" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000002.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>
|
||||||
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000002" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000002">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" placeholder="Name" required autofocus value="Entry">
|
||||||
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="8284.0">
|
||||||
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="2916.0">
|
||||||
|
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>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="roomRow_00000000000000000000000000000003">
|
||||||
|
<td>Family Room</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>9,785</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>7,446</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>6,180</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>3</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<div class="join">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete room">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000003" hx-target="closest tr" hx-confirm="Are you sure?"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit room">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="roomForm_00000000000000000000000000000003.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="roomForm_00000000000000000000000000000003" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000003.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>
|
||||||
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000003" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000003">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" placeholder="Name" required autofocus value="Family Room">
|
||||||
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="9785.0">
|
||||||
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="7446.0">
|
||||||
|
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>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="roomRow_00000000000000000000000000000004">
|
||||||
|
<td>Kitchen</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>4,518</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>5,096</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>4,230</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>2</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<div class="join">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete room">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000004" hx-target="closest tr" hx-confirm="Are you sure?"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit room">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="roomForm_00000000000000000000000000000004.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="roomForm_00000000000000000000000000000004" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000004.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>
|
||||||
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000004" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000004">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" placeholder="Name" required autofocus value="Kitchen">
|
||||||
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="4518.0">
|
||||||
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="5096.0">
|
||||||
|
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>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="roomRow_00000000000000000000000000000005">
|
||||||
|
<td>Living Room</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>7,553</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>6,829</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>5,668</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>2</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<div class="join">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete room">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000005" hx-target="closest tr" hx-confirm="Are you sure?"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit room">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="roomForm_00000000000000000000000000000005.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="roomForm_00000000000000000000000000000005" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000005.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>
|
||||||
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000005" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000005">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" placeholder="Name" required autofocus value="Living Room">
|
||||||
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="7553.0">
|
||||||
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="6829.0">
|
||||||
|
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>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="roomRow_00000000000000000000000000000006">
|
||||||
|
<td>Master</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>8,202</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>2,076</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>1,723</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-center"><span>2</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<div class="join">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete room">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000006" hx-target="closest tr" hx-confirm="Are you sure?"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit room">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="roomForm_00000000000000000000000000000006.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="roomForm_00000000000000000000000000000006" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm_00000000000000000000000000000006.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>
|
||||||
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/rooms/00000000-0000-0000-0000-000000000006" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000006">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" placeholder="Name" required autofocus value="Master">
|
||||||
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="8202.0">
|
||||||
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="2076.0">
|
||||||
|
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>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<dialog id="roomForm" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="roomForm.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>
|
||||||
|
<h1 class="text-3xl font-bold pb-6">Room</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4" hx-post="/projects/00000000-0000-0000-0000-000000000000/rooms" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" placeholder="Name" required autofocus value="">
|
||||||
|
Heating Load</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="heatingLoad" type="number" placeholder="1234" required min="0" value="">
|
||||||
|
Cooling Total</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="coolingTotal" type="number" placeholder="1234" required min="0" value="">
|
||||||
|
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>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="drawer-side is-drawer-close:overflow-visible grow">
|
||||||
|
<label for="my-drawer-1" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||||
|
<div class="flex grow h-full flex-col items-start bg-base-300 text-base-content
|
||||||
|
is-drawer-close:min-w-[80px] is-drawer-open:max-w-[300px]">
|
||||||
|
<ul class="w-full grow">
|
||||||
|
<li class="flex w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-map-pin-icon lucide-map-pin"><path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Project</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/rooms" hx-push-url="true" hx-target="body" hx-swap="outerHTML" data-active="true">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-door-closed-icon lucide-door-closed"><path d="M10 12h.01"/><path d="M18 20V6a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v14"/><path d="M2 20h20"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Rooms</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="flex w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/equipment" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-fan-icon lucide-fan"><path d="M10.827 16.379a6.082 6.082 0 0 1-8.618-7.002l5.412 1.45a6.082 6.082 0 0 1 7.002-8.618l-1.45 5.412a6.082 6.082 0 0 1 8.618 7.002l-5.412-1.45a6.082 6.082 0 0 1-7.002 8.618l1.45-5.412Z"/><path d="M12 12v.01"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Equipment</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/effective-lengths" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-ruler-dimension-line-icon lucide-ruler-dimension-line"><path d="M10 15v-3"/><path d="M14 15v-3"/><path d="M18 15v-3"/><path d="M2 8V4"/><path d="M22 6H2"/><path d="M22 8V4"/><path d="M6 15v-3"/><rect x="2" y="12" width="20" height="8" rx="2"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>T.E.L.</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/friction-rate" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-function-icon lucide-square-function"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="M9 17c2 0 2.8-1 2.8-2.8V10c0-2 1-3.3 3.2-3"/><path d="M9 11.2h5.7"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Friction Rate</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/duct-sizing" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center"><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-wind-icon lucide-wind"><path d="M12.8 19.6A2 2 0 1 0 14 16H2"/><path d="M17.5 8a2.5 2.5 0 1 1 2 4H2"/><path d="M9.8 4.4A2 2 0 1 1 11 8H2"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Duct Sizes</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<div class="bottom-0 left-0 bg-error">
|
||||||
|
<footer class="footer sm:footer-horizontal footer-center
|
||||||
|
bg-base-300 text-base-content p-4">
|
||||||
|
<aside>
|
||||||
|
<p>Copyright © 2026 - All rights reserved by Michael Housh</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Duct Calc</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta content="ductcalc.com" name="og:site_name">
|
||||||
|
<meta content="Duct Calc" name="og:title">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="description">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="og:description">
|
||||||
|
<meta content="/images/mand_logo.png" name="og:image">
|
||||||
|
<meta content="/images/mand_logo.png" name="twitter:image">
|
||||||
|
<meta content="Duct Calc" name="twitter:image:alt">
|
||||||
|
<meta content="summary_large_image" name="twitter:card">
|
||||||
|
<meta content="1536" name="og:image:width">
|
||||||
|
<meta content="1024" name="og:image:height">
|
||||||
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<script src="https://unpkg.com/htmx-remove@latest" crossorigin="anonymous" integrity="sha384-NwB2Xh66PNEYfVki0ao13UAFmdNtMIdBKZ8sNGRT6hKfCPaINuZ4ScxS6vVAycPT"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="flex flex-col min-h-screen min-w-full justify-between">
|
||||||
|
<main class="flex flex-col min-h-screen min-w-full grow mb-auto">
|
||||||
|
<div class="drawer lg:drawer-open h-full">
|
||||||
|
<input id="my-drawer-1" type="checkbox" class="drawer-toggle">
|
||||||
|
<div class="drawer-content overflow-auto">
|
||||||
|
<nav class="navbar w-full bg-base-300 text-base-content shadow-sm mb-4">
|
||||||
|
<div class="flex flex-1 space-x-4 items-center">
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Open sidebar"><label for="my-drawer-1" class="size-7 btn btn-square btn-ghost hover:bg-neutral hover:text-white" aria-label="open sidebar"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-linejoin="round" stroke-linecap="round" stroke-width="2" fill="none" stroke="currentColor" class="my-1.5 inline-block"><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path><path d="M9 4v16"></path><path d="M14 10l2 2l-2 2"></path></svg></label></div>
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Home">
|
||||||
|
<a class="flex w-fit h-fit text-xl items-end px-4 py-2 btn btn-square btn-ghost hover:bg-neutral hover:text-white" href="/projects">
|
||||||
|
<img src="/images/mand_logo_sm.webp">
|
||||||
|
Duct Calc<span></span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Profile"><a href="/profile" class="btn btn-square btn-ghost hover:bg-neutral hover:text-white"><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-user-icon lucide-circle-user"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="10" r="3"/><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"/></svg></a></div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="space-y-4" id="equipmentInfo">
|
||||||
|
<div class="flex justify-between bg-secondary border-2 border-primary rounded-sm shadow-sm
|
||||||
|
p-6 w-full">
|
||||||
|
<h1 class="text-3xl font-bold">Equipment Details</h1>
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Edit equipment details">
|
||||||
|
<button class="btn btn-primary" type="button" onclick="equipmentForm.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-zebra">
|
||||||
|
<tbody class="text-lg">
|
||||||
|
<tr>
|
||||||
|
<td><span class="text-lg label font-bold">Static Pressure</span></td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end"><span>0.5</span></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="text-lg label font-bold">Heating CFM</span></td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end"><span>900</span></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="text-lg label font-bold">Cooling CFM</span></td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end"><span>1,000</span></div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<dialog id="equipmentForm" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="equipmentForm.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>
|
||||||
|
<h1 class="text-3xl font-bold pb-6 ps-2">Equipment Info</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/equipment/00000000-0000-0000-0000-000000000007" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000007">
|
||||||
|
Static Pressure<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="staticPressure" type="number" value="0.5" min="0" max="1.0" step="0.1" required>
|
||||||
|
Heating CFM</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="heatingCFM" type="number" value="900" placeholder="1000" min="0" required autofocus>
|
||||||
|
Cooling CFM</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="coolingCFM" type="number" value="1000" placeholder="1000" min="0" required></label>
|
||||||
|
<button class="btn btn-secondary btn-block my-6" type="submit">Save</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="drawer-side is-drawer-close:overflow-visible grow">
|
||||||
|
<label for="my-drawer-1" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||||
|
<div class="flex grow h-full flex-col items-start bg-base-300 text-base-content
|
||||||
|
is-drawer-close:min-w-[80px] is-drawer-open:max-w-[300px]">
|
||||||
|
<ul class="w-full grow">
|
||||||
|
<li class="flex w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-map-pin-icon lucide-map-pin"><path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Project</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/rooms" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-door-closed-icon lucide-door-closed"><path d="M10 12h.01"/><path d="M18 20V6a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v14"/><path d="M2 20h20"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Rooms</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="flex w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/equipment" hx-push-url="true" hx-target="body" hx-swap="outerHTML" data-active="true">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-fan-icon lucide-fan"><path d="M10.827 16.379a6.082 6.082 0 0 1-8.618-7.002l5.412 1.45a6.082 6.082 0 0 1 7.002-8.618l-1.45 5.412a6.082 6.082 0 0 1 8.618 7.002l-5.412-1.45a6.082 6.082 0 0 1-7.002 8.618l1.45-5.412Z"/><path d="M12 12v.01"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Equipment</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/effective-lengths" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-ruler-dimension-line-icon lucide-ruler-dimension-line"><path d="M10 15v-3"/><path d="M14 15v-3"/><path d="M18 15v-3"/><path d="M2 8V4"/><path d="M22 6H2"/><path d="M22 8V4"/><path d="M6 15v-3"/><rect x="2" y="12" width="20" height="8" rx="2"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>T.E.L.</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/friction-rate" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-function-icon lucide-square-function"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="M9 17c2 0 2.8-1 2.8-2.8V10c0-2 1-3.3 3.2-3"/><path d="M9 11.2h5.7"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Friction Rate</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/duct-sizing" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center"><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-wind-icon lucide-wind"><path d="M12.8 19.6A2 2 0 1 0 14 16H2"/><path d="M17.5 8a2.5 2.5 0 1 1 2 4H2"/><path d="M9.8 4.4A2 2 0 1 1 11 8H2"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Duct Sizes</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<div class="bottom-0 left-0 bg-error">
|
||||||
|
<footer class="footer sm:footer-horizontal footer-center
|
||||||
|
bg-base-300 text-base-content p-4">
|
||||||
|
<aside>
|
||||||
|
<p>Copyright © 2026 - All rights reserved by Michael Housh</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Duct Calc</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta content="ductcalc.com" name="og:site_name">
|
||||||
|
<meta content="Duct Calc" name="og:title">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="description">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="og:description">
|
||||||
|
<meta content="/images/mand_logo.png" name="og:image">
|
||||||
|
<meta content="/images/mand_logo.png" name="twitter:image">
|
||||||
|
<meta content="Duct Calc" name="twitter:image:alt">
|
||||||
|
<meta content="summary_large_image" name="twitter:card">
|
||||||
|
<meta content="1536" name="og:image:width">
|
||||||
|
<meta content="1024" name="og:image:height">
|
||||||
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<script src="https://unpkg.com/htmx-remove@latest" crossorigin="anonymous" integrity="sha384-NwB2Xh66PNEYfVki0ao13UAFmdNtMIdBKZ8sNGRT6hKfCPaINuZ4ScxS6vVAycPT"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="flex flex-col min-h-screen min-w-full justify-between">
|
||||||
|
<main class="flex flex-col min-h-screen min-w-full grow mb-auto">
|
||||||
|
<div class="drawer lg:drawer-open h-full">
|
||||||
|
<input id="my-drawer-1" type="checkbox" class="drawer-toggle">
|
||||||
|
<div class="drawer-content overflow-auto">
|
||||||
|
<nav class="navbar w-full bg-base-300 text-base-content shadow-sm mb-4">
|
||||||
|
<div class="flex flex-1 space-x-4 items-center">
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Open sidebar"><label for="my-drawer-1" class="size-7 btn btn-square btn-ghost hover:bg-neutral hover:text-white" aria-label="open sidebar"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-linejoin="round" stroke-linecap="round" stroke-width="2" fill="none" stroke="currentColor" class="my-1.5 inline-block"><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path><path d="M9 4v16"></path><path d="M14 10l2 2l-2 2"></path></svg></label></div>
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Home">
|
||||||
|
<a class="flex w-fit h-fit text-xl items-end px-4 py-2 btn btn-square btn-ghost hover:bg-neutral hover:text-white" href="/projects">
|
||||||
|
<img src="/images/mand_logo_sm.webp">
|
||||||
|
Duct Calc<span></span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Profile"><a href="/profile" class="btn btn-square btn-ghost hover:bg-neutral hover:text-white"><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-user-icon lucide-circle-user"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="10" r="3"/><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"/></svg></a></div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex justify-between bg-secondary border-2 border-primary rounded-sm shadow-sm
|
||||||
|
p-6 w-full pb-6">
|
||||||
|
<h1 class="text-3xl font-bold">Equivalent Lengths</h1>
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Add equivalent length">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="equivalentLengthForm.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>
|
||||||
|
</div>
|
||||||
|
<dialog id="equivalentLengthForm" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="equivalentLengthForm.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>
|
||||||
|
<h1 class="text-2xl font-bold">Effective Length</h1>
|
||||||
|
<div id="formStep_equivalentLengthForm" class="mt-4">
|
||||||
|
<form class="space-y-4" hx-post="/projects/00000000-0000-0000-0000-000000000000/effective-lengths/stepOne" hx-target="#formStep_equivalentLengthForm" hx-swap="innerHTML">
|
||||||
|
<label class="input w-full"><span class="label">Name</span>
|
||||||
|
<input name="name" type="text" value="" required autofocus>
|
||||||
|
Type</label><label class="select w-full"><span class="label"></span>
|
||||||
|
<select name="type" id="type">
|
||||||
|
<option value="return">Return</option>
|
||||||
|
<option value="supply" selected>Supply</option>
|
||||||
|
</select></label>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div></div>
|
||||||
|
<button class="btn btn-secondary" type="submit">Next</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
<table class="table table-zebra text-lg">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-lg">
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Straight Lengths</th>
|
||||||
|
<th>
|
||||||
|
<div class="grid grid-cols-3 gap-2 min-w-[220px]">
|
||||||
|
<div class="flex justify-center col-span-3">Groups</div>
|
||||||
|
<div>Group</div>
|
||||||
|
<div class="flex justify-center">T.E.L.</div>
|
||||||
|
<div class="flex justify-end">Quantity</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<div class="flex justify-end me-[140px]">T.E.L.</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr id="00000000000000000000000000000008">
|
||||||
|
<td>
|
||||||
|
<div class="badge badge-lg badge-outline badge-info"><span>supply</span></div>
|
||||||
|
</td>
|
||||||
|
<td>Supply - 1</td>
|
||||||
|
<td>
|
||||||
|
<div class="grid grid-cols-1 gap-2"><span>10</span><span>25</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="grid grid-cols-3 gap-2 min-w-[220px]">
|
||||||
|
<span>1-a</span>
|
||||||
|
<div class="flex justify-center"><span>20</span></div>
|
||||||
|
<div class="flex justify-end"><span>1</span></div>
|
||||||
|
2-b<span></span>
|
||||||
|
<div class="flex justify-center"><span>30</span></div>
|
||||||
|
<div class="flex justify-end"><span>1</span></div>
|
||||||
|
3-a<span></span>
|
||||||
|
<div class="flex justify-center"><span>10</span></div>
|
||||||
|
<div class="flex justify-end"><span>1</span></div>
|
||||||
|
12-a<span></span>
|
||||||
|
<div class="flex justify-center"><span>10</span></div>
|
||||||
|
<div class="flex justify-end"><span>1</span></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end mx-auto space-x-4">
|
||||||
|
<div class="badge badge-lg badge-outline badge-primary badge-lg pt-2"><span>105</span></div>
|
||||||
|
<div class="flex justify-end -mt-2">
|
||||||
|
<div class="join">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/effective-lengths/00000000-0000-0000-0000-000000000008" hx-confirm="Are you sure?" hx-target="#00000000000000000000000000000008" hx-swap="outerHTML"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="equivalentLengthForm_00000000000000000000000000000008.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="equivalentLengthForm_00000000000000000000000000000008" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="equivalentLengthForm_00000000000000000000000000000008.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>
|
||||||
|
<h1 class="text-2xl font-bold">Effective Length</h1>
|
||||||
|
<div id="formStep_equivalentLengthForm_00000000000000000000000000000008" class="mt-4">
|
||||||
|
<form class="space-y-4" hx-post="/projects/00000000-0000-0000-0000-000000000000/effective-lengths/stepOne" hx-target="#formStep_equivalentLengthForm_00000000000000000000000000000008" hx-swap="innerHTML">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000008">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" value="Supply - 1" required autofocus>
|
||||||
|
Type</label><label class="select w-full"><span class="label"></span>
|
||||||
|
<select name="type" id="type">
|
||||||
|
<option value="return">Return</option>
|
||||||
|
<option value="supply" selected>Supply</option>
|
||||||
|
</select></label>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div></div>
|
||||||
|
<button class="btn btn-secondary" type="submit">Next</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="00000000000000000000000000000009">
|
||||||
|
<td>
|
||||||
|
<div class="badge badge-lg badge-outline badge-error"><span>return</span></div>
|
||||||
|
</td>
|
||||||
|
<td>Return - 1</td>
|
||||||
|
<td>
|
||||||
|
<div class="grid grid-cols-1 gap-2"><span>10</span><span>20</span><span>5</span></div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="grid grid-cols-3 gap-2 min-w-[220px]">
|
||||||
|
<span>5-a</span>
|
||||||
|
<div class="flex justify-center"><span>10</span></div>
|
||||||
|
<div class="flex justify-end"><span>1</span></div>
|
||||||
|
6-a<span></span>
|
||||||
|
<div class="flex justify-center"><span>15</span></div>
|
||||||
|
<div class="flex justify-end"><span>1</span></div>
|
||||||
|
7-a<span></span>
|
||||||
|
<div class="flex justify-center"><span>20</span></div>
|
||||||
|
<div class="flex justify-end"><span>1</span></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end mx-auto space-x-4">
|
||||||
|
<div class="badge badge-lg badge-outline badge-primary badge-lg pt-2"><span>80</span></div>
|
||||||
|
<div class="flex justify-end -mt-2">
|
||||||
|
<div class="join">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/effective-lengths/00000000-0000-0000-0000-000000000009" hx-confirm="Are you sure?" hx-target="#00000000000000000000000000000009" hx-swap="outerHTML"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="equivalentLengthForm_00000000000000000000000000000009.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="equivalentLengthForm_00000000000000000000000000000009" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="equivalentLengthForm_00000000000000000000000000000009.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>
|
||||||
|
<h1 class="text-2xl font-bold">Effective Length</h1>
|
||||||
|
<div id="formStep_equivalentLengthForm_00000000000000000000000000000009" class="mt-4">
|
||||||
|
<form class="space-y-4" hx-post="/projects/00000000-0000-0000-0000-000000000000/effective-lengths/stepOne" hx-target="#formStep_equivalentLengthForm_00000000000000000000000000000009" hx-swap="innerHTML">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000009">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" value="Return - 1" required autofocus>
|
||||||
|
Type</label><label class="select w-full"><span class="label"></span>
|
||||||
|
<select name="type" id="type">
|
||||||
|
<option value="return" selected>Return</option>
|
||||||
|
<option value="supply">Supply</option>
|
||||||
|
</select></label>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div></div>
|
||||||
|
<button class="btn btn-secondary" type="submit">Next</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="drawer-side is-drawer-close:overflow-visible grow">
|
||||||
|
<label for="my-drawer-1" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||||
|
<div class="flex grow h-full flex-col items-start bg-base-300 text-base-content
|
||||||
|
is-drawer-close:min-w-[80px] is-drawer-open:max-w-[300px]">
|
||||||
|
<ul class="w-full grow">
|
||||||
|
<li class="flex w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-map-pin-icon lucide-map-pin"><path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Project</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/rooms" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-door-closed-icon lucide-door-closed"><path d="M10 12h.01"/><path d="M18 20V6a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v14"/><path d="M2 20h20"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Rooms</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="flex w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/equipment" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-fan-icon lucide-fan"><path d="M10.827 16.379a6.082 6.082 0 0 1-8.618-7.002l5.412 1.45a6.082 6.082 0 0 1 7.002-8.618l-1.45 5.412a6.082 6.082 0 0 1 8.618 7.002l-5.412-1.45a6.082 6.082 0 0 1-7.002 8.618l1.45-5.412Z"/><path d="M12 12v.01"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Equipment</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/effective-lengths" hx-push-url="true" hx-target="body" hx-swap="outerHTML" data-active="true">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-ruler-dimension-line-icon lucide-ruler-dimension-line"><path d="M10 15v-3"/><path d="M14 15v-3"/><path d="M18 15v-3"/><path d="M2 8V4"/><path d="M22 6H2"/><path d="M22 8V4"/><path d="M6 15v-3"/><rect x="2" y="12" width="20" height="8" rx="2"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>T.E.L.</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/friction-rate" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-function-icon lucide-square-function"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="M9 17c2 0 2.8-1 2.8-2.8V10c0-2 1-3.3 3.2-3"/><path d="M9 11.2h5.7"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Friction Rate</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/duct-sizing" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center"><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-wind-icon lucide-wind"><path d="M12.8 19.6A2 2 0 1 0 14 16H2"/><path d="M17.5 8a2.5 2.5 0 1 1 2 4H2"/><path d="M9.8 4.4A2 2 0 1 1 11 8H2"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Duct Sizes</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<div class="bottom-0 left-0 bg-error">
|
||||||
|
<footer class="footer sm:footer-horizontal footer-center
|
||||||
|
bg-base-300 text-base-content p-4">
|
||||||
|
<aside>
|
||||||
|
<p>Copyright © 2026 - All rights reserved by Michael Housh</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,444 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Duct Calc</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta content="ductcalc.com" name="og:site_name">
|
||||||
|
<meta content="Duct Calc" name="og:title">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="description">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="og:description">
|
||||||
|
<meta content="/images/mand_logo.png" name="og:image">
|
||||||
|
<meta content="/images/mand_logo.png" name="twitter:image">
|
||||||
|
<meta content="Duct Calc" name="twitter:image:alt">
|
||||||
|
<meta content="summary_large_image" name="twitter:card">
|
||||||
|
<meta content="1536" name="og:image:width">
|
||||||
|
<meta content="1024" name="og:image:height">
|
||||||
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<script src="https://unpkg.com/htmx-remove@latest" crossorigin="anonymous" integrity="sha384-NwB2Xh66PNEYfVki0ao13UAFmdNtMIdBKZ8sNGRT6hKfCPaINuZ4ScxS6vVAycPT"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="flex flex-col min-h-screen min-w-full justify-between">
|
||||||
|
<main class="flex flex-col min-h-screen min-w-full grow mb-auto">
|
||||||
|
<div class="drawer lg:drawer-open h-full">
|
||||||
|
<input id="my-drawer-1" type="checkbox" class="drawer-toggle">
|
||||||
|
<div class="drawer-content overflow-auto">
|
||||||
|
<nav class="navbar w-full bg-base-300 text-base-content shadow-sm mb-4">
|
||||||
|
<div class="flex flex-1 space-x-4 items-center">
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Open sidebar"><label for="my-drawer-1" class="size-7 btn btn-square btn-ghost hover:bg-neutral hover:text-white" aria-label="open sidebar"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-linejoin="round" stroke-linecap="round" stroke-width="2" fill="none" stroke="currentColor" class="my-1.5 inline-block"><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z"></path><path d="M9 4v16"></path><path d="M14 10l2 2l-2 2"></path></svg></label></div>
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Home">
|
||||||
|
<a class="flex w-fit h-fit text-xl items-end px-4 py-2 btn btn-square btn-ghost hover:bg-neutral hover:text-white" href="/projects">
|
||||||
|
<img src="/images/mand_logo_sm.webp">
|
||||||
|
Duct Calc<span></span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Profile"><a href="/profile" class="btn btn-square btn-ghost hover:bg-neutral hover:text-white"><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-user-icon lucide-circle-user"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="10" r="3"/><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"/></svg></a></div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="flex justify-between bg-secondary border-2 border-primary rounded-sm shadow-sm
|
||||||
|
p-6 w-full">
|
||||||
|
<div class="grid grid-cols-2 px-4 w-full">
|
||||||
|
<h1 class="text-3xl font-bold">Friction Rate</h1>
|
||||||
|
<div class="space-y-2 justify-end font-bold text-lg">
|
||||||
|
<div class="flex space-x-4 justify-end mx-auto">
|
||||||
|
<span>Friction Rate Design Value</span>
|
||||||
|
<div class="badge badge-lg badge-outline badge-info badge-lg font-bold"><span>0.06</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-4 justify-end mx-auto">
|
||||||
|
<span>Available Static Pressure</span>
|
||||||
|
<div class="badge badge-lg badge-outline"><span>0.11</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-4 justify-end mx-auto">
|
||||||
|
<span>Component Pressure Losses</span>
|
||||||
|
<div class="badge badge-lg badge-outline"><span>0.39</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-error font-bold italic col-span-2">
|
||||||
|
<div class="flex space-x-2 hidden">
|
||||||
|
<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-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
|
||||||
|
<p>Must complete previous sections.</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2 hidden">
|
||||||
|
<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-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
|
||||||
|
<p>No component pressures losses</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2 hidden">
|
||||||
|
<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-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
|
||||||
|
<p class="block">
|
||||||
|
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)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-2 hidden">
|
||||||
|
<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-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
|
||||||
|
<p class="block">
|
||||||
|
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)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex justify-between px-4">
|
||||||
|
<h1 class="text-2xl font-bold">Component Pressure Losses</h1>
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Add component loss">
|
||||||
|
<button type="button" class="btn btn-primary text-2xl me-2" onclick="componentLossForm.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>
|
||||||
|
</div>
|
||||||
|
<table class="table table-zebra">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-xl font-bold">
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Value</th>
|
||||||
|
<th class="min-w-[200px]"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr class="text-lg">
|
||||||
|
<td>evaporator-coil</td>
|
||||||
|
<td><span>0.2</span></td>
|
||||||
|
<td>
|
||||||
|
<div class="flex join items-end justify-end mx-auto">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/component-loss/00000000-0000-0000-0000-00000000000A" hx-target="body" hx-swap="outerHTML" hx-confirm="Are your sure?"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="componentLossForm_0000000000000000000000000000000A.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="componentLossForm_0000000000000000000000000000000A" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="componentLossForm_0000000000000000000000000000000A.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>
|
||||||
|
<h1 class="text-2xl font-bold">Component Loss</h1>
|
||||||
|
<form class="space-y-4 p-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/component-loss/00000000-0000-0000-0000-00000000000A" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-00000000000A">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" value="evaporator-coil" placeholder="Name" required autofocus>
|
||||||
|
Value</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="value" type="number" value="0.2" placeholder="0.2" min="0.03" max="1.0" step="0.01" required></label>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="text-lg">
|
||||||
|
<td>filter</td>
|
||||||
|
<td><span>0.1</span></td>
|
||||||
|
<td>
|
||||||
|
<div class="flex join items-end justify-end mx-auto">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/component-loss/00000000-0000-0000-0000-00000000000B" hx-target="body" hx-swap="outerHTML" hx-confirm="Are your sure?"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="componentLossForm_0000000000000000000000000000000B.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="componentLossForm_0000000000000000000000000000000B" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="componentLossForm_0000000000000000000000000000000B.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>
|
||||||
|
<h1 class="text-2xl font-bold">Component Loss</h1>
|
||||||
|
<form class="space-y-4 p-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/component-loss/00000000-0000-0000-0000-00000000000B" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-00000000000B">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" value="filter" placeholder="Name" required autofocus>
|
||||||
|
Value</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="value" type="number" value="0.1" placeholder="0.2" min="0.03" max="1.0" step="0.01" required></label>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="text-lg">
|
||||||
|
<td>supply-outlet</td>
|
||||||
|
<td><span>0.03</span></td>
|
||||||
|
<td>
|
||||||
|
<div class="flex join items-end justify-end mx-auto">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/component-loss/00000000-0000-0000-0000-00000000000C" hx-target="body" hx-swap="outerHTML" hx-confirm="Are your sure?"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="componentLossForm_0000000000000000000000000000000C.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="componentLossForm_0000000000000000000000000000000C" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="componentLossForm_0000000000000000000000000000000C.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>
|
||||||
|
<h1 class="text-2xl font-bold">Component Loss</h1>
|
||||||
|
<form class="space-y-4 p-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/component-loss/00000000-0000-0000-0000-00000000000C" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-00000000000C">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" value="supply-outlet" placeholder="Name" required autofocus>
|
||||||
|
Value</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="value" type="number" value="0.03" placeholder="0.2" min="0.03" max="1.0" step="0.01" required></label>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="text-lg">
|
||||||
|
<td>return-grille</td>
|
||||||
|
<td><span>0.03</span></td>
|
||||||
|
<td>
|
||||||
|
<div class="flex join items-end justify-end mx-auto">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/component-loss/00000000-0000-0000-0000-00000000000D" hx-target="body" hx-swap="outerHTML" hx-confirm="Are your sure?"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="componentLossForm_0000000000000000000000000000000D.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="componentLossForm_0000000000000000000000000000000D" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="componentLossForm_0000000000000000000000000000000D.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>
|
||||||
|
<h1 class="text-2xl font-bold">Component Loss</h1>
|
||||||
|
<form class="space-y-4 p-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/component-loss/00000000-0000-0000-0000-00000000000D" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-00000000000D">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" value="return-grille" placeholder="Name" required autofocus>
|
||||||
|
Value</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="value" type="number" value="0.03" placeholder="0.2" min="0.03" max="1.0" step="0.01" required></label>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="text-lg">
|
||||||
|
<td>balancing-damper</td>
|
||||||
|
<td><span>0.03</span></td>
|
||||||
|
<td>
|
||||||
|
<div class="flex join items-end justify-end mx-auto">
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Delete">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000/component-loss/00000000-0000-0000-0000-00000000000E" hx-target="body" hx-swap="outerHTML" hx-confirm="Are your sure?"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-bottom" data-tip="Edit">
|
||||||
|
<button class="btn join-item btn-ghost" type="button" onclick="componentLossForm_0000000000000000000000000000000E.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dialog id="componentLossForm_0000000000000000000000000000000E" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="componentLossForm_0000000000000000000000000000000E.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>
|
||||||
|
<h1 class="text-2xl font-bold">Component Loss</h1>
|
||||||
|
<form class="space-y-4 p-4" hx-patch="/projects/00000000-0000-0000-0000-000000000000/component-loss/00000000-0000-0000-0000-00000000000E" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-00000000000E">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" value="balancing-damper" placeholder="Name" required autofocus>
|
||||||
|
Value</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="value" type="number" value="0.03" placeholder="0.2" min="0.03" max="1.0" step="0.01" required></label>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<dialog id="componentLossForm" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="componentLossForm.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>
|
||||||
|
<h1 class="text-2xl font-bold">Component Loss</h1>
|
||||||
|
<form class="space-y-4 p-4" hx-post="/projects/00000000-0000-0000-0000-000000000000/component-loss" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="projectID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="name" type="text" value="" placeholder="Name" required autofocus>
|
||||||
|
Value</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="value" type="number" value="" placeholder="0.2" min="0.03" max="1.0" step="0.01" required></label>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="drawer-side is-drawer-close:overflow-visible grow">
|
||||||
|
<label for="my-drawer-1" aria-label="close sidebar" class="drawer-overlay"></label>
|
||||||
|
<div class="flex grow h-full flex-col items-start bg-base-300 text-base-content
|
||||||
|
is-drawer-close:min-w-[80px] is-drawer-open:max-w-[300px]">
|
||||||
|
<ul class="w-full grow">
|
||||||
|
<li class="flex w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-map-pin-icon lucide-map-pin"><path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"/><circle cx="12" cy="10" r="3"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Project</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/rooms" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-door-closed-icon lucide-door-closed"><path d="M10 12h.01"/><path d="M18 20V6a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v14"/><path d="M2 20h20"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Rooms</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="flex w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/equipment" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-fan-icon lucide-fan"><path d="M10.827 16.379a6.082 6.082 0 0 1-8.618-7.002l5.412 1.45a6.082 6.082 0 0 1 7.002-8.618l-1.45 5.412a6.082 6.082 0 0 1 8.618 7.002l-5.412-1.45a6.082 6.082 0 0 1-7.002 8.618l1.45-5.412Z"/><path d="M12 12v.01"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Equipment</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/effective-lengths" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><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-ruler-dimension-line-icon lucide-ruler-dimension-line"><path d="M10 15v-3"/><path d="M14 15v-3"/><path d="M18 15v-3"/><path d="M2 8V4"/><path d="M22 6H2"/><path d="M22 8V4"/><path d="M6 15v-3"/><rect x="2" y="12" width="20" height="8" rx="2"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>T.E.L.</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/friction-rate" hx-push-url="true" hx-target="body" hx-swap="outerHTML" data-active="true">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center is-drawer-close:text-green-400"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-function-icon lucide-square-function"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="M9 17c2 0 2.8-1 2.8-2.8V10c0-2 1-3.3 3.2-3"/><path d="M9 11.2h5.7"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Friction Rate</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex grow justify-end items-end is-drawer-close:hidden text-green-400"><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-badge-check-icon lucide-badge-check"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg></div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="w-full">
|
||||||
|
<button class="w-full gap-1 py-2 border-b-1 border-gray-200
|
||||||
|
hover:bg-neutral data-active:bg-neutral
|
||||||
|
hover:text-white data-active:text-white
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1" hx-get="/projects/00000000-0000-0000-0000-000000000000/duct-sizing" hx-push-url="true" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<div class="w-full p-2 gap-1
|
||||||
|
is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:grid-cols-1">
|
||||||
|
<div class="items-center
|
||||||
|
is-drawer-open:justify-start is-drawer-open:flex is-drawer-open:space-x-4
|
||||||
|
is-drawer-close:justify-center is-drawer-close:mx-auto is-drawer-close:space-y-2">
|
||||||
|
<div class="flex items-center justify-center"><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-wind-icon lucide-wind"><path d="M12.8 19.6A2 2 0 1 0 14 16H2"/><path d="M17.5 8a2.5 2.5 0 1 1 2 4H2"/><path d="M9.8 4.4A2 2 0 1 1 11 8H2"/></svg></div>
|
||||||
|
<div class="flex items-center justify-center"><span>Duct Sizes</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<div class="bottom-0 left-0 bg-error">
|
||||||
|
<footer class="footer sm:footer-horizontal footer-center
|
||||||
|
bg-base-300 text-base-content p-4">
|
||||||
|
<aside>
|
||||||
|
<p>Copyright © 2026 - All rights reserved by Michael Housh</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,113 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Duct Calc</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta content="ductcalc.com" name="og:site_name">
|
||||||
|
<meta content="Duct Calc" name="og:title">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="description">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="og:description">
|
||||||
|
<meta content="/images/mand_logo.png" name="og:image">
|
||||||
|
<meta content="/images/mand_logo.png" name="twitter:image">
|
||||||
|
<meta content="Duct Calc" name="twitter:image:alt">
|
||||||
|
<meta content="summary_large_image" name="twitter:card">
|
||||||
|
<meta content="1536" name="og:image:width">
|
||||||
|
<meta content="1024" name="og:image:height">
|
||||||
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<script src="https://unpkg.com/htmx-remove@latest" crossorigin="anonymous" integrity="sha384-NwB2Xh66PNEYfVki0ao13UAFmdNtMIdBKZ8sNGRT6hKfCPaINuZ4ScxS6vVAycPT"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="flex flex-col min-h-screen min-w-full justify-between">
|
||||||
|
<main class="flex flex-col min-h-screen min-w-full grow mb-auto">
|
||||||
|
<div>
|
||||||
|
<nav class="navbar w-full bg-base-300 text-base-content shadow-sm mb-4">
|
||||||
|
<div class="flex flex-1 space-x-4 items-center">
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Home">
|
||||||
|
<a class="flex w-fit h-fit text-xl items-end px-4 py-2 btn btn-square btn-ghost hover:bg-neutral hover:text-white" href="/projects">
|
||||||
|
<img src="/images/mand_logo_sm.webp">
|
||||||
|
Duct Calc<span></span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Profile"><a href="/profile" class="btn btn-square btn-ghost hover:bg-neutral hover:text-white"><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-user-icon lucide-circle-user"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="10" r="3"/><path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"/></svg></a></div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="m-6">
|
||||||
|
<div class="flex justify-between bg-secondary border-2 border-primary rounded-sm shadow-sm
|
||||||
|
p-6 w-full pb-6">
|
||||||
|
<h1 class="text-3xl font-bold">Projects</h1>
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Add project">
|
||||||
|
<button type="button" class="btn btn-primary" onclick="projectForm.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>
|
||||||
|
</div>
|
||||||
|
<table class="table table-zebra">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><span class="text-lg label font-bold">Date</span></th>
|
||||||
|
<th><span class="text-lg label font-bold">Name</span></th>
|
||||||
|
<th><span class="text-lg label font-bold">Address</span></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr id="00000000-0000-0000-0000-000000000000">
|
||||||
|
<td><span>02/13/2009</span></td>
|
||||||
|
<td>Testy McTestface</td>
|
||||||
|
<td>1234 Sesame Street</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex justify-end space-x-6">
|
||||||
|
<div class="join">
|
||||||
|
<div class="tooltip tooltip-left" data-tip="Delete project">
|
||||||
|
<button type="button" class="btn btn-error join-item btn-ghost" hx-delete="/projects/00000000-0000-0000-0000-000000000000" hx-confirm="Are you sure?" hx-target="closest tr"><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-trash2-icon lucide-trash-2"><path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip tooltip-left" data-tip="View project"><a class="join-item btn btn-success btn-ghost" href="/projects/00000000-0000-0000-0000-000000000000/rooms"><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-chevron-right-icon lucide-chevron-right"><path d="m9 18 6-6-6-6"/></svg></a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<dialog id="projectForm" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="projectForm.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>
|
||||||
|
<h1 class="text-3xl font-bold pb-6 ps-2">Project</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4" hx-post="/projects" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<label class="input w-full"><span class="label">Name</span>
|
||||||
|
<input name="name" type="text" value="" placeholder="Project Name" required autofocus>
|
||||||
|
Address</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="streetAddress" type="text" value="" placeholder="Street Address" required>
|
||||||
|
City</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="city" type="text" value="" placeholder="City" required>
|
||||||
|
State</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="state" type="text" value="" placeholder="State" required>
|
||||||
|
Zip</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="zipCode" type="text" value="" placeholder="Zip Code" required></label>
|
||||||
|
<button class="btn btn-secondary btn-block my-6" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<div class="bottom-0 left-0 bg-error">
|
||||||
|
<footer class="footer sm:footer-horizontal footer-center
|
||||||
|
bg-base-300 text-base-content p-4">
|
||||||
|
<aside>
|
||||||
|
<p>Copyright © 2026 - All rights reserved by Michael Housh</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Duct Calc</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta content="ductcalc.com" name="og:site_name">
|
||||||
|
<meta content="Duct Calc" name="og:title">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="description">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="og:description">
|
||||||
|
<meta content="/images/mand_logo.png" name="og:image">
|
||||||
|
<meta content="/images/mand_logo.png" name="twitter:image">
|
||||||
|
<meta content="Duct Calc" name="twitter:image:alt">
|
||||||
|
<meta content="summary_large_image" name="twitter:card">
|
||||||
|
<meta content="1536" name="og:image:width">
|
||||||
|
<meta content="1024" name="og:image:height">
|
||||||
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<script src="https://unpkg.com/htmx-remove@latest" crossorigin="anonymous" integrity="sha384-NwB2Xh66PNEYfVki0ao13UAFmdNtMIdBKZ8sNGRT6hKfCPaINuZ4ScxS6vVAycPT"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="flex flex-col min-h-screen min-w-full justify-between">
|
||||||
|
<main class="flex flex-col min-h-screen min-w-full grow mb-auto">
|
||||||
|
<dialog id="loginForm" class="modal modal-open">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h1 class="text-2xl font-bold mb-6">Login</h1>
|
||||||
|
<form method="post" class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="input validator w-full"> <svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="2.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<rect width="20" height="16" x="2" y="4" rx="2"></rect>
|
||||||
|
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input type="email" placeholder="Email" required name="email" id="email" autofocus></label>
|
||||||
|
<div class="validator-hint hidden">Enter valid email address.</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="input validator w-full"> <svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="2.5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z"
|
||||||
|
></path>
|
||||||
|
<circle cx="16.5" cy="7.5" r=".5" fill="currentColor"></circle>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input type="password" placeholder="Password" required pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" minlength="8" name="password" id="password"></label>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<button class="btn btn-secondary mt-4 w-full">Login</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center"><a class="btn btn-link" href="/signup">Sign Up</a></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</main>
|
||||||
|
<div class="bottom-0 left-0 bg-error"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Duct Calc</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta content="ductcalc.com" name="og:site_name">
|
||||||
|
<meta content="Duct Calc" name="og:title">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="description">
|
||||||
|
<meta content="Duct sizing based on ACCA, Manual-D." name="og:description">
|
||||||
|
<meta content="/images/mand_logo.png" name="og:image">
|
||||||
|
<meta content="/images/mand_logo.png" name="twitter:image">
|
||||||
|
<meta content="Duct Calc" name="twitter:image:alt">
|
||||||
|
<meta content="summary_large_image" name="twitter:card">
|
||||||
|
<meta content="1536" name="og:image:width">
|
||||||
|
<meta content="1024" name="og:image:height">
|
||||||
|
<meta content="duct, hvac, duct-design, duct design, manual-d, manual d, design" name="keywords">
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.8"></script>
|
||||||
|
<script src="/js/htmx-download.js"></script>
|
||||||
|
<script src="/js/main.js"></script>
|
||||||
|
<link rel="stylesheet" href="/css/output.css">
|
||||||
|
<link rel="stylesheet" href="/css/htmx.css">
|
||||||
|
<link rel="icon" href="/images/favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="icon" href="/images/favicon-32x32.png" type="image/png">
|
||||||
|
<link rel="icon" href="/images/favicon-16x16.png" type="image/png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
|
||||||
|
<link rel="manifest" href="/site.webmanifest">
|
||||||
|
<script src="https://unpkg.com/htmx-remove@latest" crossorigin="anonymous" integrity="sha384-NwB2Xh66PNEYfVki0ao13UAFmdNtMIdBKZ8sNGRT6hKfCPaINuZ4ScxS6vVAycPT"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="flex flex-col min-h-screen min-w-full justify-between">
|
||||||
|
<main class="flex flex-col min-h-screen min-w-full grow mb-auto">
|
||||||
|
<div>
|
||||||
|
<nav class="navbar w-full bg-base-300 text-base-content shadow-sm mb-4">
|
||||||
|
<div class="flex flex-1 space-x-4 items-center">
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Home">
|
||||||
|
<a class="flex w-fit h-fit text-xl items-end px-4 py-2 btn btn-square btn-ghost hover:bg-neutral hover:text-white" href="/projects">
|
||||||
|
<img src="/images/mand_logo_sm.webp">
|
||||||
|
Duct Calc<span></span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<h1 class="text-2xl font-bold">Account</h1>
|
||||||
|
<button class="btn" type="button" onclick="userProfileForm_00000000000000000000000000000001.showModal()">
|
||||||
|
<div class="flex">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-pen-icon lucide-square-pen"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<table class="table table-zebra border rounded-lg">
|
||||||
|
<tr>
|
||||||
|
<td><span class="text-lg label font-bold">Name</span></td>
|
||||||
|
<td>Testy McTestface</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="text-lg label font-bold">Company</span></td>
|
||||||
|
<td>Acme Co.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="text-lg label font-bold">Street Address</span></td>
|
||||||
|
<td>1234 Sesame St</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="text-lg label font-bold">City</span></td>
|
||||||
|
<td>Monroe</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="text-lg label font-bold">State</span></td>
|
||||||
|
<td>OH</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="text-lg label font-bold">Zip Code</span></td>
|
||||||
|
<td>55555</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="text-lg label font-bold">Theme</span></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<dialog id="userProfileForm_00000000000000000000000000000001" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick="userProfileForm_00000000000000000000000000000001.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>
|
||||||
|
<h1 class="text-xl font-bold pb-6">Profile</h1>
|
||||||
|
<form class="grid grid-cols-1 gap-4 p-4" hx-patch="/profile/00000000-0000-0000-0000-000000000001" hx-target="body" hx-swap="outerHTML">
|
||||||
|
<input class="hidden" name="id" value="00000000-0000-0000-0000-000000000001">
|
||||||
|
<input class="hidden" name="userID" value="00000000-0000-0000-0000-000000000000">
|
||||||
|
First Name<label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="firstName" value="Testy" required autofocus>
|
||||||
|
Last Name</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="lastName" value="McTestface" required>
|
||||||
|
Company</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="companyName" value="Acme Co." required>
|
||||||
|
Address</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="streetAddress" value="1234 Sesame St" required>
|
||||||
|
City</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="city" value="Monroe" required>
|
||||||
|
State</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="state" value="OH" required>
|
||||||
|
Zip</label><label class="input w-full"><span class="label"></span>
|
||||||
|
<input name="zipCode" value="55555" required></label>
|
||||||
|
<div class="dropdown dropdown-top">
|
||||||
|
<div class="input btn m-1 w-full" tabindex="0" role="button">Theme<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-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg></div>
|
||||||
|
<ul tabindex="-1" class="dropdown-content bg-base-300 rounded-box z-1 p-2 shadow-2xl">
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="theme" class="theme-controller w-full btn btn-sm btn-block btn-ghost justify-start" aria-label="Default" value="default">
|
||||||
|
</li>
|
||||||
|
<li><span class="text-sm font-bold text-gray-400">Light</span></li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="theme" class="theme-controller w-full btn btn-sm btn-block btn-ghost justify-start" aria-label="Cupcake" value="cupcake">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="theme" class="theme-controller w-full btn btn-sm btn-block btn-ghost justify-start" aria-label="Light" value="light">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="theme" class="theme-controller w-full btn btn-sm btn-block btn-ghost justify-start" aria-label="Nord" value="nord">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="theme" class="theme-controller w-full btn btn-sm btn-block btn-ghost justify-start" aria-label="Retro" value="retro">
|
||||||
|
</li>
|
||||||
|
<li><span class="text-sm font-bold text-gray-400">Dark</span></li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="theme" class="theme-controller w-full btn btn-sm btn-block btn-ghost justify-start" aria-label="Aqua" value="aqua">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="theme" class="theme-controller w-full btn btn-sm btn-block btn-ghost justify-start" aria-label="Cyberpunk" value="cyberpunk">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="theme" class="theme-controller w-full btn btn-sm btn-block btn-ghost justify-start" aria-label="Dark" value="dark">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="theme" class="theme-controller w-full btn btn-sm btn-block btn-ghost justify-start" aria-label="Dracula" value="dracula">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="theme" class="theme-controller w-full btn btn-sm btn-block btn-ghost justify-start" aria-label="Night" value="night">
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="radio" name="theme" class="theme-controller w-full btn btn-sm btn-block btn-ghost justify-start" aria-label="Synthwave" value="synthwave">
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-secondary btn-block" type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<div class="bottom-0 left-0 bg-error">
|
||||||
|
<footer class="footer sm:footer-horizontal footer-center
|
||||||
|
bg-base-300 text-base-content p-4">
|
||||||
|
<aside>
|
||||||
|
<p>Copyright © 2026 - All rights reserved by Michael Housh</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -66,6 +66,8 @@ RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
|
|||||||
# libxml2 \
|
# libxml2 \
|
||||||
sqlite3 \
|
sqlite3 \
|
||||||
curl \
|
curl \
|
||||||
|
pandoc \
|
||||||
|
weasyprint \
|
||||||
&& rm -r /var/lib/apt/lists/*
|
&& rm -r /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Create a vapor user and group with /app as its home directory
|
# Create a vapor user and group with /app as its home directory
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
|
|||||||
build-essential \
|
build-essential \
|
||||||
curl \
|
curl \
|
||||||
wkhtmltopdf \
|
wkhtmltopdf \
|
||||||
|
pandoc \
|
||||||
|
weasyprint \
|
||||||
&& rm -r /var/lib/apt/lists/*
|
&& rm -r /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Set up a build area
|
# Set up a build area
|
||||||
|
|||||||
Reference in New Issue
Block a user