feat: Adds update path to equivalent length form / database / view routes.

This commit is contained in:
2026-01-07 17:31:54 -05:00
parent f8bed40670
commit 79b7892d9a
11 changed files with 460 additions and 118 deletions

View File

@@ -11,12 +11,22 @@ import Styleguide
// TODO: Add back buttons / capability??
// TODO: Add patch / update capability
struct EffectiveLengthForm: HTML, Sendable {
static let id = "equivalentLengthForm"
static func id(_ equivalentLength: EffectiveLength?) -> String {
let base = "equivalentLengthForm"
guard let equivalentLength else { return base }
return "\(base)_\(equivalentLength.id.uuidString.replacing("-", with: ""))"
}
let projectID: Project.ID
let dismiss: Bool
let type: EffectiveLength.EffectiveLengthType
let effectiveLength: EffectiveLength?
var id: String { Self.id(effectiveLength) }
init(
projectID: Project.ID,
@@ -26,13 +36,27 @@ struct EffectiveLengthForm: HTML, Sendable {
self.projectID = projectID
self.dismiss = dismiss
self.type = type
self.effectiveLength = nil
}
init(
effectiveLength: EffectiveLength
) {
self.dismiss = true
self.type = effectiveLength.type
self.projectID = effectiveLength.projectID
self.effectiveLength = effectiveLength
}
var body: some HTML {
ModalForm(id: Self.id, dismiss: dismiss) {
ModalForm(
id: id,
dismiss: dismiss
) {
h1(.class("text-2xl font-bold")) { "Effective Length" }
div(.id("formStep")) {
StepOne(projectID: projectID, effectiveLength: nil)
div(.id("formStep_\(id)")) {
StepOne(projectID: projectID, effectiveLength: effectiveLength)
}
}
}
@@ -52,7 +76,7 @@ struct EffectiveLengthForm: HTML, Sendable {
form(
.class("space-y-4"),
.hx.post(route),
.hx.target("#formStep"),
.hx.target("#formStep_\(EffectiveLengthForm.id(effectiveLength))"),
.hx.swap(.innerHTML)
) {
if let id = effectiveLength?.id {
@@ -87,7 +111,7 @@ struct EffectiveLengthForm: HTML, Sendable {
form(
.class("space-y-4"),
.hx.post(route),
.hx.target("#formStep"),
.hx.target("#formStep_\(EffectiveLengthForm.id(effectiveLength))"),
.hx.swap(.innerHTML)
) {
if let id = effectiveLength?.id {
@@ -109,8 +133,14 @@ struct EffectiveLengthForm: HTML, Sendable {
SVG(.circlePlus)
}
}
div(.id("straightLengths")) {
StraightLengthField()
div(.id("straightLengths"), .class("space-y-4")) {
if let effectiveLength {
for length in effectiveLength.straightLengths {
StraightLengthField(value: length)
}
} else {
StraightLengthField()
}
}
Row {
@@ -127,16 +157,23 @@ struct EffectiveLengthForm: HTML, Sendable {
let stepTwo: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepTwo
var route: String {
let baseRoute = SiteRoute.View.router.path(
for: .project(.detail(projectID, .equivalentLength(.index)))
)
return "\(baseRoute)/stepThree"
if effectiveLength != nil {
return SiteRoute.View.router.path(
for: .project(.detail(projectID, .equivalentLength(.index))))
} else {
let baseRoute = SiteRoute.View.router.path(
for: .project(.detail(projectID, .equivalentLength(.index)))
)
return "\(baseRoute)/stepThree"
}
}
var body: some HTML {
form(
.class("space-y-4"),
.hx.post(route),
effectiveLength == nil
? .hx.post(route)
: .hx.patch(route),
.hx.target("body"),
.hx.swap(.outerHTML)
) {
@@ -163,8 +200,23 @@ struct EffectiveLengthForm: HTML, Sendable {
SVG(.circlePlus)
}
}
div(.class("grid grid-cols-5 gap-2")) {
Label("Group")
Label("Letter")
Label("Length")
Label("Quantity")
.attributes(.class("col-span-2"))
}
div(.id("groups"), .class("space-y-4")) {
GroupField(style: stepTwo.type)
if let effectiveLength {
for group in effectiveLength.groups {
GroupField(style: effectiveLength.type, group: group)
}
} else {
GroupField(style: stepTwo.type)
}
}
Row {
div {}
@@ -188,30 +240,38 @@ struct StraightLengthField: HTML, Sendable {
name: "straightLengths",
placeholder: "Length"
)
.attributes(.type(.number), .min("0"), .autofocus, .required)
.attributes(.type(.number), .min("0"), .autofocus, .required, .value(value))
TrashButton()
.attributes(.data("remove", value: "true"))
}
.attributes(.hx.ext("remove"))
.attributes(.hx.ext("remove"), .class("space-x-4"))
}
}
struct GroupField: HTML, Sendable {
let style: EffectiveLength.EffectiveLengthType
let group: EffectiveLength.Group?
init(style: EffectiveLength.EffectiveLengthType, group: EffectiveLength.Group? = nil) {
self.style = style
self.group = group
}
var body: some HTML {
Row {
div(.class("grid grid-cols-5 gap-2")) {
GroupSelect(style: style)
Input(name: "group[letter]", placeholder: "Letter")
.attributes(.type(.text), .autofocus, .required)
.attributes(.type(.text), .autofocus, .required, .value(group?.letter))
Input(name: "group[length]", placeholder: "Length")
.attributes(.type(.number), .min("0"), .required)
.attributes(.type(.number), .min("0"), .required, .value(group?.value))
Input(name: "group[quantity]", placeholder: "Quantity")
.attributes(.type(.number), .min("1"), .value("1"), .required)
TrashButton()
.attributes(.data("remove", value: "true"))
.attributes(.type(.number), .min("1"), .value("1"), .required, .value(group?.quantity ?? 1))
div(.class("flex justify-end")) {
TrashButton()
.attributes(.data("remove", value: "true"), .class("mx-2"))
}
}
.attributes(.class("space-x-2"), .hx.ext("remove"))
}

View File

@@ -3,32 +3,56 @@ import ElementaryHTMX
import ManualDCore
import Styleguide
// TODO: Group into grids of supply / return.
struct EffectiveLengthsView: HTML, Sendable {
let projectID: Project.ID
let effectiveLengths: [EffectiveLength]
var supplies: [EffectiveLength] {
effectiveLengths.filter({ $0.type == .supply })
.sorted { $0.totalEquivalentLength > $1.totalEquivalentLength }
}
var returns: [EffectiveLength] {
effectiveLengths.filter({ $0.type == .return })
.sorted { $0.totalEquivalentLength > $1.totalEquivalentLength }
}
var body: some HTML {
div(
.class("m-4")
.class("m-4 space-y-4")
) {
Row {
h1(.class("text-2xl font-bold")) { "Equivalent Lengths" }
PlusButton()
.attributes(.showModal(id: EffectiveLengthForm.id))
.attributes(.showModal(id: EffectiveLengthForm.id(nil)))
}
.attributes(.class("pb-6"))
div(
.id("effectiveLengths"),
.class("space-y-6")
) {
for row in effectiveLengths {
EffectiveLengthView(effectiveLength: row)
EffectiveLengthForm(projectID: projectID, dismiss: true)
div {
h2(.class("text-xl font-bold pb-4")) { "Supplies" }
div(.class("grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4")) {
for (n, row) in supplies.enumerated() {
EffectiveLengthView(effectiveLength: row)
.attributes(.class(n == 0 ? "border-primary" : "border-gray-200"))
}
}
}
div {
h2(.class("text-xl font-bold pb-4")) { "Returns" }
div(.class("grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 space-x-4 space-y-4")) {
for (n, row) in returns.enumerated() {
EffectiveLengthView(effectiveLength: row)
.attributes(.class(n == 0 ? "border-secondary" : "border-gray-200"))
}
}
}
EffectiveLengthForm(projectID: projectID, dismiss: true)
}
}
@@ -42,52 +66,84 @@ struct EffectiveLengthsView: HTML, Sendable {
}
var groupsTotal: Double {
effectiveLength.groups.reduce(into: 0) {
$0 += ($1.value * Double($1.quantity))
}
effectiveLength.groups.totalEquivalentLength
}
var id: String {
return "effectiveLenghtCard_\(effectiveLength.id.uuidString.replacing("-", with: ""))"
}
var body: some HTML<HTMLTag.div> {
div(
.class(
"""
border border-gray-200 rounded-lg shadow-lg p-4
"""
)
.class("card h-full bg-base-100 shadow-sm border rounded-lg"),
.id(id)
) {
Row {
span(.class("text-xl font-bold")) { effectiveLength.name }
}
Row {
div(.class("card-body")) {
Row {
h2 { effectiveLength.name }
div(
.class("space-x-4")
) {
span(.class("text-sm italic")) {
"Total"
}
.attributes(.class("text-primary"), when: effectiveLength.type == .supply)
.attributes(.class("text-secondary"), when: effectiveLength.type == .return)
Number(self.effectiveLength.totalEquivalentLength, digits: 0)
.attributes(.class("badge badge-outline text-lg"))
.attributes(
.class("badge-primary"), when: effectiveLength.type == .supply
)
.attributes(
.class("badge-secondary"), when: effectiveLength.type == .return
)
}
}
.attributes(.class("card-title pb-6"))
Label("Straight Lengths")
}
for length in effectiveLength.straightLengths {
Row {
div {}
Number(length)
}
}
Row {
Label("Groups")
Label("Equivalent Length")
Label("Quantity")
}
.attributes(.class("border-b border-gray-200"))
for group in effectiveLength.groups {
Row {
span { "\(group.group)-\(group.letter)" }
Number(group.value)
Number(group.quantity)
for length in effectiveLength.straightLengths {
div(.class("flex justify-end")) {
Number(length)
}
}
Row {
Label("Groups")
Label("Equivalent Length")
Label("Quantity")
}
.attributes(.class("border-b border-gray-200"))
for group in effectiveLength.groups {
Row {
span { "\(group.group)-\(group.letter)" }
Number(group.value)
Number(group.quantity)
}
}
div(.class("card-actions justify-end pt-6 space-y-4 mt-auto")) {
// TODO: Delete.
TrashButton()
.attributes(
.hx.delete(
route: .project(
.detail(
effectiveLength.projectID, .equivalentLength(.delete(id: effectiveLength.id)))
)),
.hx.confirm("Are you sure?"),
.hx.target("#\(id)"),
.hx.swap(.outerHTML)
)
EditButton()
.attributes(.showModal(id: EffectiveLengthForm.id(effectiveLength)))
}
EffectiveLengthForm(effectiveLength: effectiveLength)
}
Row {
Label("Total")
Number(Double(straightLengthsTotal) + groupsTotal, digits: 0)
.attributes(.class("text-xl font-bold"))
}
.attributes(.class("border-b border-t border-gray-200"))
}
}
}