WIP: Style updates for pdf view.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ DerivedData/
|
|||||||
node_modules/
|
node_modules/
|
||||||
tailwindcss
|
tailwindcss
|
||||||
.envrc
|
.envrc
|
||||||
|
*.pdf
|
||||||
|
|||||||
103
Public/css/pdf.css
Normal file
103
Public/css/pdf.css
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
table td, table th {
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 10px auto;
|
||||||
|
border: none !important;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
padding: 10px;
|
||||||
|
border: none;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
.table-bordered {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
.table-bordered th, td {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
.table-bordered tr:nth-child(even) {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.w-half {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
.table-footer {
|
||||||
|
background-color: #75af4c;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.bg-green {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.heating {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.coolingTotal {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
.coolingSensible {
|
||||||
|
color: cyan;
|
||||||
|
}
|
||||||
|
.justify-end {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.flex table {
|
||||||
|
width: 50%;
|
||||||
|
margin: 0;
|
||||||
|
flex: 1 1 calc(50% - 10px);
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.table-container {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.table-container table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.customerTable {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.effectiveLengthGroupTable, .effectiveLengthGroupHeader {
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.headline {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
@@ -15,106 +15,7 @@ struct PdfDocument: HTMLDocument {
|
|||||||
let request: PdfClient.Request
|
let request: PdfClient.Request
|
||||||
|
|
||||||
var head: some HTML {
|
var head: some HTML {
|
||||||
style {
|
link(.rel(.stylesheet), .href("/css/pdf.css"))
|
||||||
"""
|
|
||||||
@media print {
|
|
||||||
body {
|
|
||||||
-webkit-print-color-adjust: exact;
|
|
||||||
color-adjust: exact;
|
|
||||||
print-color-adjust: exact;
|
|
||||||
}
|
|
||||||
table td, table th {
|
|
||||||
-webkit-print-color-adjust: exact;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
max-width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin: 10px auto;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
th, td {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
tr:nth-child(even) {
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
}
|
|
||||||
.w-full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.w-half {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
.table-footer {
|
|
||||||
background-color: #75af4c;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.bg-green {
|
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.heating {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
.coolingTotal {
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
.coolingSensible {
|
|
||||||
color: cyan;
|
|
||||||
}
|
|
||||||
.justify-end {
|
|
||||||
text-align: end;
|
|
||||||
}
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.flex table {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
width: 50%;
|
|
||||||
margin: 0;
|
|
||||||
flex: 1 1 calc(50% - 10px);
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.table-container {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.table-container table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
.customerTable {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
.section {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.effectiveLengthGroupTable, .effectiveLengthGroupHeader {
|
|
||||||
background-color: white;
|
|
||||||
color: black;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.headline {
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
@@ -124,24 +25,7 @@ struct PdfDocument: HTMLDocument {
|
|||||||
h2 { "Project" }
|
h2 { "Project" }
|
||||||
|
|
||||||
div(.class("flex")) {
|
div(.class("flex")) {
|
||||||
table(.class("table customer-table")) {
|
ProjectTable(project: request.project)
|
||||||
tbody {
|
|
||||||
tr {
|
|
||||||
td { "Name" }
|
|
||||||
td { request.project.name }
|
|
||||||
}
|
|
||||||
tr {
|
|
||||||
td { "Street Address" }
|
|
||||||
td {
|
|
||||||
p {
|
|
||||||
request.project.streetAddress
|
|
||||||
br()
|
|
||||||
request.project.cityStateZipString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// HACK:
|
// HACK:
|
||||||
table {}
|
table {}
|
||||||
}
|
}
|
||||||
@@ -156,7 +40,6 @@ struct PdfDocument: HTMLDocument {
|
|||||||
div(.class("table-container")) {
|
div(.class("table-container")) {
|
||||||
EquipmentTable(title: "Equipment", equipmentInfo: request.equipmentInfo)
|
EquipmentTable(title: "Equipment", equipmentInfo: request.equipmentInfo)
|
||||||
}
|
}
|
||||||
// .attributes(.style("height: 140px;"))
|
|
||||||
div(.class("table-container")) {
|
div(.class("table-container")) {
|
||||||
FrictionRateTable(
|
FrictionRateTable(
|
||||||
title: "Friction Rate",
|
title: "Friction Rate",
|
||||||
@@ -222,310 +105,4 @@ struct PdfDocument: HTMLDocument {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EffectiveLengthsTable: HTML, Sendable {
|
|
||||||
let effectiveLengths: [EffectiveLength]
|
|
||||||
|
|
||||||
var body: some HTML<HTMLTag.table> {
|
|
||||||
table {
|
|
||||||
thead {
|
|
||||||
tr(.class("bg-green")) {
|
|
||||||
th { "Name" }
|
|
||||||
th { "Type" }
|
|
||||||
th { "Straight Lengths" }
|
|
||||||
th { "Groups" }
|
|
||||||
th { "Total" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tbody {
|
|
||||||
for row in effectiveLengths {
|
|
||||||
tr {
|
|
||||||
td { row.name }
|
|
||||||
td { row.type.rawValue }
|
|
||||||
td {
|
|
||||||
ul {
|
|
||||||
for length in row.straightLengths {
|
|
||||||
li { length.string() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
EffectiveLengthGroupTable(groups: row.groups)
|
|
||||||
.attributes(.class("w-full"))
|
|
||||||
}
|
|
||||||
td { row.totalEquivalentLength.string(digits: 0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EffectiveLengthGroupTable: HTML, Sendable {
|
|
||||||
let groups: [EffectiveLength.Group]
|
|
||||||
|
|
||||||
var body: some HTML<HTMLTag.table> {
|
|
||||||
table {
|
|
||||||
thead {
|
|
||||||
tr(.class("effectiveLengthGroupHeader")) {
|
|
||||||
th { "Name" }
|
|
||||||
th { "Length" }
|
|
||||||
th { "Quantity" }
|
|
||||||
th { "Total" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tbody {
|
|
||||||
for row in groups {
|
|
||||||
tr {
|
|
||||||
td { "\(row.group)-\(row.letter)" }
|
|
||||||
td { row.value.string(digits: 0) }
|
|
||||||
td { row.quantity.string() }
|
|
||||||
td { (row.value * Double(row.quantity)).string(digits: 0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RoomsTable: HTML, Sendable {
|
|
||||||
let rooms: [Room]
|
|
||||||
let projectSHR: Double
|
|
||||||
|
|
||||||
var body: some HTML<HTMLTag.table> {
|
|
||||||
table {
|
|
||||||
thead {
|
|
||||||
tr(.class("bg-green")) {
|
|
||||||
th { "Name" }
|
|
||||||
th { "Heating BTU" }
|
|
||||||
th { "Cooling Total BTU" }
|
|
||||||
th { "Cooling Sensible BTU" }
|
|
||||||
th { "Register Count" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tbody {
|
|
||||||
for room in rooms {
|
|
||||||
tr {
|
|
||||||
td { room.name }
|
|
||||||
td { room.heatingLoad.string(digits: 0) }
|
|
||||||
td { room.coolingTotal.string(digits: 0) }
|
|
||||||
td {
|
|
||||||
(room.coolingSensible
|
|
||||||
?? (room.coolingTotal * projectSHR)).string(digits: 0)
|
|
||||||
}
|
|
||||||
td { room.registerCount.string() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Totals
|
|
||||||
// tr(.class("table-footer")) {
|
|
||||||
tr {
|
|
||||||
td(.class("label")) { "Totals" }
|
|
||||||
td(.class("heating label")) {
|
|
||||||
rooms.totalHeatingLoad.string(digits: 0)
|
|
||||||
}
|
|
||||||
td(.class("coolingTotal label")) {
|
|
||||||
rooms.totalCoolingLoad.string(digits: 0)
|
|
||||||
}
|
|
||||||
td(.class("coolingSensible label")) {
|
|
||||||
rooms.totalCoolingSensible(shr: projectSHR).string(digits: 0)
|
|
||||||
}
|
|
||||||
td {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RegisterDetailTable: HTML, Sendable {
|
|
||||||
let rooms: [DuctSizes.RoomContainer]
|
|
||||||
|
|
||||||
var body: some HTML<HTMLTag.table> {
|
|
||||||
table {
|
|
||||||
thead {
|
|
||||||
tr(.class("bg-green")) {
|
|
||||||
th { "Name" }
|
|
||||||
th { "Heating BTU" }
|
|
||||||
th { "Cooling BTU" }
|
|
||||||
th { "Heating CFM" }
|
|
||||||
th { "Cooling CFM" }
|
|
||||||
th { "Design CFM" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tbody {
|
|
||||||
for row in rooms {
|
|
||||||
tr {
|
|
||||||
td { row.roomName }
|
|
||||||
td { row.heatingLoad.string(digits: 0) }
|
|
||||||
td { row.coolingLoad.string(digits: 0) }
|
|
||||||
td { row.heatingCFM.string(digits: 0) }
|
|
||||||
td { row.coolingCFM.string(digits: 0) }
|
|
||||||
td { row.designCFM.value.string(digits: 0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TrunkTable: HTML, Sendable {
|
|
||||||
public let sizes: DuctSizes
|
|
||||||
public let type: TrunkSize.TrunkType
|
|
||||||
|
|
||||||
var trunks: [DuctSizes.TrunkContainer] {
|
|
||||||
sizes.trunks.filter { $0.type == type }
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some HTML<HTMLTag.table> {
|
|
||||||
table {
|
|
||||||
thead(.class("bg-green")) {
|
|
||||||
tr {
|
|
||||||
th { "Name" }
|
|
||||||
th { "Dsn CFM" }
|
|
||||||
th { "Round Size" }
|
|
||||||
th { "Velocity" }
|
|
||||||
th { "Final Size" }
|
|
||||||
th { "Flex Size" }
|
|
||||||
th { "Height" }
|
|
||||||
th { "Width" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tbody {
|
|
||||||
for row in trunks {
|
|
||||||
tr {
|
|
||||||
td { row.name ?? "" }
|
|
||||||
td { row.designCFM.value.string(digits: 0) }
|
|
||||||
td { row.ductSize.roundSize.string() }
|
|
||||||
td { row.velocity.string() }
|
|
||||||
td { row.finalSize.string() }
|
|
||||||
td { row.flexSize.string() }
|
|
||||||
td { row.ductSize.height?.string() ?? "" }
|
|
||||||
td { row.width?.string() ?? "" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DuctSizesTable: HTML, Sendable {
|
|
||||||
let rooms: [DuctSizes.RoomContainer]
|
|
||||||
|
|
||||||
var body: some HTML<HTMLTag.table> {
|
|
||||||
table {
|
|
||||||
thead {
|
|
||||||
tr(.class("bg-green")) {
|
|
||||||
th { "Name" }
|
|
||||||
th { "Dsn CFM" }
|
|
||||||
th { "Round Size" }
|
|
||||||
th { "Velocity" }
|
|
||||||
th { "Final Size" }
|
|
||||||
th { "Flex Size" }
|
|
||||||
th { "Height" }
|
|
||||||
th { "Width" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tbody {
|
|
||||||
for row in rooms {
|
|
||||||
tr {
|
|
||||||
td { row.roomName }
|
|
||||||
td { row.designCFM.value.string(digits: 0) }
|
|
||||||
td { row.roundSize.string() }
|
|
||||||
td { row.velocity.string() }
|
|
||||||
td { row.flexSize.string() }
|
|
||||||
td { row.finalSize.string() }
|
|
||||||
td { row.ductSize.height?.string() ?? "" }
|
|
||||||
td { row.width?.string() ?? "" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EquipmentTable: HTML, Sendable {
|
|
||||||
let title: String?
|
|
||||||
let equipmentInfo: EquipmentInfo
|
|
||||||
|
|
||||||
init(title: String? = nil, equipmentInfo: EquipmentInfo) {
|
|
||||||
self.title = title
|
|
||||||
self.equipmentInfo = equipmentInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some HTML<HTMLTag.table> {
|
|
||||||
|
|
||||||
table {
|
|
||||||
thead {
|
|
||||||
tr(.class("bg-green")) {
|
|
||||||
th { title ?? "" }
|
|
||||||
th(.class("justify-end")) { "Value" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tbody {
|
|
||||||
tr {
|
|
||||||
td { "Static Pressure" }
|
|
||||||
td(.class("justify-end")) { equipmentInfo.staticPressure.string() }
|
|
||||||
}
|
|
||||||
tr {
|
|
||||||
td { "Heating CFM" }
|
|
||||||
td(.class("justify-end")) { equipmentInfo.heatingCFM.string() }
|
|
||||||
}
|
|
||||||
tr {
|
|
||||||
td { "Cooling CFM" }
|
|
||||||
td(.class("justify-end")) { equipmentInfo.coolingCFM.string() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FrictionRateTable: HTML, Sendable {
|
|
||||||
let title: String?
|
|
||||||
let componentLosses: [ComponentPressureLoss]
|
|
||||||
let frictionRate: FrictionRate
|
|
||||||
let totalEquivalentLength: Double
|
|
||||||
let displayTotals: Bool
|
|
||||||
|
|
||||||
var sortedLosses: [ComponentPressureLoss] {
|
|
||||||
componentLosses.sorted { $0.value > $1.value }
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some HTML<HTMLTag.table> {
|
|
||||||
table {
|
|
||||||
thead {
|
|
||||||
tr(.class("bg-green")) {
|
|
||||||
th { title ?? "" }
|
|
||||||
th(.class("justify-end")) { "Value" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tbody {
|
|
||||||
for row in sortedLosses {
|
|
||||||
tr {
|
|
||||||
td { row.name }
|
|
||||||
td(.class("justify-end")) { row.value.string() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if displayTotals {
|
|
||||||
tr {
|
|
||||||
td(.class("label justify-end")) { "Available Static Pressure" }
|
|
||||||
td(.class("justify-end")) { frictionRate.availableStaticPressure.string() }
|
|
||||||
}
|
|
||||||
tr {
|
|
||||||
td(.class("label justify-end")) { "Total Equivalent Length" }
|
|
||||||
td(.class("justify-end")) { totalEquivalentLength.string() }
|
|
||||||
}
|
|
||||||
tr {
|
|
||||||
td(.class("label justify-end")) { "Friction Rate Design Value" }
|
|
||||||
td(.class("justify-end")) { frictionRate.value.string() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Project {
|
|
||||||
var cityStateZipString: String {
|
|
||||||
return "\(city), \(state) \(zipCode)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
37
Sources/PdfClient/Views/DuctSizeTable.swift
Normal file
37
Sources/PdfClient/Views/DuctSizeTable.swift
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
struct DuctSizesTable: HTML, Sendable {
|
||||||
|
let rooms: [DuctSizes.RoomContainer]
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Dsn CFM" }
|
||||||
|
th { "Round Size" }
|
||||||
|
th { "Velocity" }
|
||||||
|
th { "Final Size" }
|
||||||
|
th { "Flex Size" }
|
||||||
|
th { "Height" }
|
||||||
|
th { "Width" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in rooms {
|
||||||
|
tr {
|
||||||
|
td { row.roomName }
|
||||||
|
td { row.designCFM.value.string(digits: 0) }
|
||||||
|
td { row.roundSize.string() }
|
||||||
|
td { row.velocity.string() }
|
||||||
|
td { row.flexSize.string() }
|
||||||
|
td { row.finalSize.string() }
|
||||||
|
td { row.ductSize.height?.string() ?? "" }
|
||||||
|
td { row.width?.string() ?? "" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
Sources/PdfClient/Views/EquipmentTable.swift
Normal file
38
Sources/PdfClient/Views/EquipmentTable.swift
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
struct EquipmentTable: HTML, Sendable {
|
||||||
|
let title: String?
|
||||||
|
let equipmentInfo: EquipmentInfo
|
||||||
|
|
||||||
|
init(title: String? = nil, equipmentInfo: EquipmentInfo) {
|
||||||
|
self.title = title
|
||||||
|
self.equipmentInfo = equipmentInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { title ?? "" }
|
||||||
|
th(.class("justify-end")) { "Value" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
td { "Static Pressure" }
|
||||||
|
td(.class("justify-end")) { equipmentInfo.staticPressure.string() }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Heating CFM" }
|
||||||
|
td(.class("justify-end")) { equipmentInfo.heatingCFM.string() }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td { "Cooling CFM" }
|
||||||
|
td(.class("justify-end")) { equipmentInfo.coolingCFM.string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
Sources/PdfClient/Views/EquivalentLengthTable.swift
Normal file
68
Sources/PdfClient/Views/EquivalentLengthTable.swift
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
struct EffectiveLengthsTable: HTML, Sendable {
|
||||||
|
let effectiveLengths: [EffectiveLength]
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Type" }
|
||||||
|
th { "Straight Lengths" }
|
||||||
|
th { "Groups" }
|
||||||
|
th { "Total" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in effectiveLengths {
|
||||||
|
tr {
|
||||||
|
td { row.name }
|
||||||
|
td { row.type.rawValue }
|
||||||
|
td {
|
||||||
|
ul {
|
||||||
|
for length in row.straightLengths {
|
||||||
|
li { length.string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
EffectiveLengthGroupTable(groups: row.groups)
|
||||||
|
.attributes(.class("w-full"))
|
||||||
|
}
|
||||||
|
td { row.totalEquivalentLength.string(digits: 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EffectiveLengthGroupTable: HTML, Sendable {
|
||||||
|
let groups: [EffectiveLength.Group]
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("effectiveLengthGroupHeader")) {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Length" }
|
||||||
|
th { "Quantity" }
|
||||||
|
th { "Total" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in groups {
|
||||||
|
tr {
|
||||||
|
td { "\(row.group)-\(row.letter)" }
|
||||||
|
td { row.value.string(digits: 0) }
|
||||||
|
td { row.quantity.string() }
|
||||||
|
td { (row.value * Double(row.quantity)).string(digits: 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Sources/PdfClient/Views/FrictionRateTable.swift
Normal file
47
Sources/PdfClient/Views/FrictionRateTable.swift
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
struct FrictionRateTable: HTML, Sendable {
|
||||||
|
let title: String?
|
||||||
|
let componentLosses: [ComponentPressureLoss]
|
||||||
|
let frictionRate: FrictionRate
|
||||||
|
let totalEquivalentLength: Double
|
||||||
|
let displayTotals: Bool
|
||||||
|
|
||||||
|
var sortedLosses: [ComponentPressureLoss] {
|
||||||
|
componentLosses.sorted { $0.value > $1.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { title ?? "" }
|
||||||
|
th(.class("justify-end")) { "Value" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in sortedLosses {
|
||||||
|
tr {
|
||||||
|
td { row.name }
|
||||||
|
td(.class("justify-end")) { row.value.string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if displayTotals {
|
||||||
|
tr {
|
||||||
|
td(.class("label justify-end")) { "Available Static Pressure" }
|
||||||
|
td(.class("justify-end")) { frictionRate.availableStaticPressure.string() }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td(.class("label justify-end")) { "Total Equivalent Length" }
|
||||||
|
td(.class("justify-end")) { totalEquivalentLength.string() }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td(.class("label justify-end")) { "Friction Rate Design Value" }
|
||||||
|
td(.class("justify-end")) { frictionRate.value.string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Sources/PdfClient/Views/ProjectTable.swift
Normal file
33
Sources/PdfClient/Views/ProjectTable.swift
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
struct ProjectTable: HTML, Sendable {
|
||||||
|
let project: Project
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
td(.class("label")) { "Name" }
|
||||||
|
td { project.name }
|
||||||
|
}
|
||||||
|
tr {
|
||||||
|
td(.class("label")) { "Address" }
|
||||||
|
td {
|
||||||
|
p {
|
||||||
|
project.streetAddress
|
||||||
|
br()
|
||||||
|
project.cityStateZipString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Project {
|
||||||
|
var cityStateZipString: String {
|
||||||
|
return "\(city), \(state) \(zipCode)"
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Sources/PdfClient/Views/RegisterTable.swift
Normal file
33
Sources/PdfClient/Views/RegisterTable.swift
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
struct RegisterDetailTable: HTML, Sendable {
|
||||||
|
let rooms: [DuctSizes.RoomContainer]
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Heating BTU" }
|
||||||
|
th { "Cooling BTU" }
|
||||||
|
th { "Heating CFM" }
|
||||||
|
th { "Cooling CFM" }
|
||||||
|
th { "Design CFM" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in rooms {
|
||||||
|
tr {
|
||||||
|
td { row.roomName }
|
||||||
|
td { row.heatingLoad.string(digits: 0) }
|
||||||
|
td { row.coolingLoad.string(digits: 0) }
|
||||||
|
td { row.heatingCFM.string(digits: 0) }
|
||||||
|
td { row.coolingCFM.string(digits: 0) }
|
||||||
|
td { row.designCFM.value.string(digits: 0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Sources/PdfClient/Views/RoomTable.swift
Normal file
50
Sources/PdfClient/Views/RoomTable.swift
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
struct RoomsTable: HTML, Sendable {
|
||||||
|
let rooms: [Room]
|
||||||
|
let projectSHR: Double
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
tr(.class("bg-green")) {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Heating BTU" }
|
||||||
|
th { "Cooling Total BTU" }
|
||||||
|
th { "Cooling Sensible BTU" }
|
||||||
|
th { "Register Count" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for room in rooms {
|
||||||
|
tr {
|
||||||
|
td { room.name }
|
||||||
|
td { room.heatingLoad.string(digits: 0) }
|
||||||
|
td { room.coolingTotal.string(digits: 0) }
|
||||||
|
td {
|
||||||
|
(room.coolingSensible
|
||||||
|
?? (room.coolingTotal * projectSHR)).string(digits: 0)
|
||||||
|
}
|
||||||
|
td { room.registerCount.string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Totals
|
||||||
|
// tr(.class("table-footer")) {
|
||||||
|
tr {
|
||||||
|
td(.class("label")) { "Totals" }
|
||||||
|
td(.class("heating label")) {
|
||||||
|
rooms.totalHeatingLoad.string(digits: 0)
|
||||||
|
}
|
||||||
|
td(.class("coolingTotal label")) {
|
||||||
|
rooms.totalCoolingLoad.string(digits: 0)
|
||||||
|
}
|
||||||
|
td(.class("coolingSensible label")) {
|
||||||
|
rooms.totalCoolingSensible(shr: projectSHR).string(digits: 0)
|
||||||
|
}
|
||||||
|
td {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Sources/PdfClient/Views/TrunkTable.swift
Normal file
42
Sources/PdfClient/Views/TrunkTable.swift
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import Elementary
|
||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
struct TrunkTable: HTML, Sendable {
|
||||||
|
public let sizes: DuctSizes
|
||||||
|
public let type: TrunkSize.TrunkType
|
||||||
|
|
||||||
|
var trunks: [DuctSizes.TrunkContainer] {
|
||||||
|
sizes.trunks.filter { $0.type == type }
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some HTML<HTMLTag.table> {
|
||||||
|
table {
|
||||||
|
thead(.class("bg-green")) {
|
||||||
|
tr {
|
||||||
|
th { "Name" }
|
||||||
|
th { "Dsn CFM" }
|
||||||
|
th { "Round Size" }
|
||||||
|
th { "Velocity" }
|
||||||
|
th { "Final Size" }
|
||||||
|
th { "Flex Size" }
|
||||||
|
th { "Height" }
|
||||||
|
th { "Width" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
for row in trunks {
|
||||||
|
tr {
|
||||||
|
td { row.name ?? "" }
|
||||||
|
td { row.designCFM.value.string(digits: 0) }
|
||||||
|
td { row.ductSize.roundSize.string() }
|
||||||
|
td { row.velocity.string() }
|
||||||
|
td { row.finalSize.string() }
|
||||||
|
td { row.flexSize.string() }
|
||||||
|
td { row.ductSize.height?.string() ?? "" }
|
||||||
|
td { row.width?.string() ?? "" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
|
|||||||
npm \
|
npm \
|
||||||
build-essential \
|
build-essential \
|
||||||
curl \
|
curl \
|
||||||
|
wkhtmltopdf \
|
||||||
&& 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