feat: Adds update path to equivalent length form / database / view routes.
This commit is contained in:
@@ -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"))
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user