feat: Begins vendor views
This commit is contained in:
@@ -1,3 +1,11 @@
|
|||||||
|
:root {
|
||||||
|
--primary: #ff66ff;
|
||||||
|
--secondary: #00ffcc;
|
||||||
|
--dark-bg: #14141f;
|
||||||
|
--bg: #1e1e2e;
|
||||||
|
--hover-bg: #444;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -5,10 +13,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #1e1e2e;
|
background-color: var(--bg);
|
||||||
color: #ff66ff;
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p { font-size: 1.25em; }
|
||||||
|
h1 { font-size: 2.5em; }
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
@@ -16,13 +27,39 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
background-color: #14141f;
|
background-color: var(--dark-bg);
|
||||||
color: #ff66ff;
|
color: var(--primary);
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
border-bottom: 1px solid grey;
|
border-bottom: 1px solid grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
color: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
font-size: 1.25em;
|
||||||
|
background-color: var(--primary);
|
||||||
|
color: var(--secondary);
|
||||||
|
border: 1px solid var(--secondary);
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
font-size: 1.25em;
|
||||||
|
background-color: var(--secondary);
|
||||||
|
color: var(--primary);
|
||||||
|
border: 1px solid var(--primary);
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.col-1 {width: 8.33%;}
|
.col-1 {width: 8.33%;}
|
||||||
.col-2 {width: 16.66%;}
|
.col-2 {width: 16.66%;}
|
||||||
.col-3 {width: 25%;}
|
.col-3 {width: 25%;}
|
||||||
@@ -51,10 +88,7 @@ header {
|
|||||||
float: left;
|
float: left;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
margin-left: 10px;
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 50px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
@@ -63,43 +97,14 @@ form {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
form label {
|
#user-form input {
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form input {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
padding: 50px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form label {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form input {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-form, .employee-form {
|
|
||||||
padding: 80px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-form input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.employee-form input {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
table, th, td {
|
table, th, td {
|
||||||
@@ -112,7 +117,7 @@ td, th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table th {
|
table th {
|
||||||
color: #00ffcc;
|
color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=submit] {
|
input[type=submit] {
|
||||||
@@ -147,31 +152,6 @@ input[type=text]:focus, input[type=password]:focus, input[type=email]:focus {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-edit img {
|
|
||||||
position: fixed;
|
|
||||||
right: 30px;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn img:hover {
|
|
||||||
background-color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-delete img {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-delete img:hover {
|
|
||||||
background-color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle, .toggle img {
|
.toggle, .toggle img {
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
@@ -182,16 +162,10 @@ a.toggle, a img.toggle {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.toggle img:hover {
|
.toggle img:hover {
|
||||||
background-color: #555;
|
background-color: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.htmx-swapping td {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.5s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidepanel {
|
.sidepanel {
|
||||||
height: 275px;
|
height: 275px;
|
||||||
width: 0;
|
width: 0;
|
||||||
@@ -242,18 +216,7 @@ tr.htmx-swapping td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.openbtn:hover {
|
.openbtn:hover {
|
||||||
background-color: #444;
|
background-color: var(--hover-bg);
|
||||||
}
|
|
||||||
|
|
||||||
.form-content {
|
|
||||||
transition: 0.5s;
|
|
||||||
overflow: auto;
|
|
||||||
z-index: 1;
|
|
||||||
position: fixed;
|
|
||||||
top: 60px;
|
|
||||||
left: 0;
|
|
||||||
background-color: #14141f;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.closebtn {
|
.closebtn {
|
||||||
@@ -264,15 +227,6 @@ tr.htmx-swapping td {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-content .closebtn {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 25px;
|
|
||||||
font-size: 36px;
|
|
||||||
margin-left: 50px;
|
|
||||||
color: grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-add {
|
.btn-add {
|
||||||
color: grey;
|
color: grey;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
@@ -280,37 +234,23 @@ tr.htmx-swapping td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-add:hover {
|
.btn-add:hover {
|
||||||
background-color: #444;
|
background-color: var(--hover-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
background-color: inherit;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
background-color: #444;
|
background-color: var(--hover-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.danger {
|
.danger {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vendor-branches {
|
|
||||||
width: 350px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vendor-branches ul li a {
|
|
||||||
position: relative;
|
|
||||||
top: 2px;
|
|
||||||
right: 0;
|
|
||||||
margin-left: 10px;
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vendor-branches ul li {
|
|
||||||
transition: 0.3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.branch-row {
|
.branch-row {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
@@ -333,16 +273,6 @@ tr.htmx-swapping td {
|
|||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-row button {
|
|
||||||
border: none;
|
|
||||||
text-decoration: none;
|
|
||||||
color: grey;
|
|
||||||
background-color: inherit;
|
|
||||||
font-size: 1.3em;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-detail {
|
.btn-detail {
|
||||||
border: none;
|
border: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -351,18 +281,9 @@ tr.htmx-swapping td {
|
|||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.po-detail table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border: none;
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.po-detail td {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
color: #00ffcc;
|
color: var(--secondary);
|
||||||
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.float {
|
.float {
|
||||||
@@ -389,18 +310,13 @@ tr.htmx-swapping td {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.float.htmx-swapping {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.5s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.float table {
|
.float table {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 15px;
|
top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-row {
|
.btn-row {
|
||||||
margin: 5px 40px;
|
margin: 20px 20px;
|
||||||
padding: 10px 0px;
|
padding: 10px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,6 +325,8 @@ tr.htmx-swapping td {
|
|||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.danger {
|
button.danger {
|
||||||
@@ -436,7 +354,7 @@ button.edit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.row label {
|
.row label {
|
||||||
display: inline;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row .label {
|
.row .label {
|
||||||
@@ -485,11 +403,14 @@ button.edit {
|
|||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-detail, .btn-add, .btn-close {
|
tr.htmx-swapping td {
|
||||||
border: none;
|
opacity: 0;
|
||||||
color: grey;
|
transition: opacity 0.5s ease-out;
|
||||||
text-decoration: none;
|
}
|
||||||
background-color: inherit;
|
|
||||||
|
.float.htmx-swapping {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.5s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-detail {
|
.btn-detail {
|
||||||
@@ -501,12 +422,10 @@ button:hover {
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-detail:hover {
|
|
||||||
background-color: #444;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-close {
|
.btn-close {
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
|
background-color: inherit;
|
||||||
|
border: none;
|
||||||
|
color: grey;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,125 +77,3 @@ struct UserViewController: RouteCollection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// import Dependencies
|
|
||||||
// import Fluent
|
|
||||||
// import Vapor
|
|
||||||
//
|
|
||||||
// struct UserViewController: RouteCollection {
|
|
||||||
//
|
|
||||||
// @Dependency(\.users) var users
|
|
||||||
//
|
|
||||||
// private let api = UserApiController()
|
|
||||||
//
|
|
||||||
// func boot(routes: any RoutesBuilder) throws {
|
|
||||||
// let users = routes.protected.grouped("users")
|
|
||||||
// users.get(use: index(req:))
|
|
||||||
// users.post(use: create(req:))
|
|
||||||
// users.group(":id") {
|
|
||||||
// $0.get(use: details(req:))
|
|
||||||
// $0.delete(use: delete(req:))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Sendable
|
|
||||||
// func index(req: Request) async throws -> View {
|
|
||||||
// try await renderIndex(req)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Sendable
|
|
||||||
// private func renderIndex(_ req: Request, _ user: User.DTO? = nil) async throws -> View {
|
|
||||||
// let users = try await api.getSortedUsers(req: req)
|
|
||||||
// return try await req.view.render("users/index", UsersCTX(user: user, users: users))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Sendable
|
|
||||||
// func create(req: Request) async throws -> View {
|
|
||||||
// let user = try await api.create(req: req)
|
|
||||||
// return try await req.view.render("users/table-row", user)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Sendable
|
|
||||||
// func details(req: Request) async throws -> View {
|
|
||||||
// let user = try await users.get(req.ensureIDPathComponent())
|
|
||||||
// // Check if the page has been rendered before.
|
|
||||||
// guard req.isHtmxRequest else {
|
|
||||||
// // Not an htmx-request, so render the whole page with the details.
|
|
||||||
// return try await renderIndex(req, user)
|
|
||||||
// }
|
|
||||||
// // An htmx-request header was present, so just return the details,
|
|
||||||
// return try await req.view.render("users/detail", ["user": user])
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Sendable
|
|
||||||
// func delete(req: Request) async throws -> View {
|
|
||||||
// _ = try await api.delete(req: req)
|
|
||||||
// return try await req.view.render("users/table", ["users": api.getSortedUsers(req: req)])
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// struct UserFormCTX: Content {
|
|
||||||
// let htmxForm: HtmxFormCTX<Context>
|
|
||||||
//
|
|
||||||
// struct Context: Content {
|
|
||||||
// let showConfirmPassword: Bool
|
|
||||||
// let showEmailInput: Bool
|
|
||||||
// let buttonLabel: String
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// static func signIn(next: String?) -> Self {
|
|
||||||
// .init(
|
|
||||||
// htmxForm: .init(
|
|
||||||
// formClass: "user-form",
|
|
||||||
// formId: "user-form",
|
|
||||||
// htmxTargetUrl: .post("/login\((next != nil && next != "/") ? "?next=\(next!)" : "")"),
|
|
||||||
// htmxTarget: "user-table",
|
|
||||||
// htmxPushUrl: true,
|
|
||||||
// htmxResetAfterRequest: true,
|
|
||||||
// htmxSwapOob: nil,
|
|
||||||
// htmxSwap: .afterbegin,
|
|
||||||
// context: .init(showConfirmPassword: false, showEmailInput: false, buttonLabel: "Sign In")
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// static func create() -> Self {
|
|
||||||
// .init(
|
|
||||||
// htmxForm: .init(
|
|
||||||
// formClass: "user-form",
|
|
||||||
// formId: "user-form",
|
|
||||||
// htmxTargetUrl: .post("/users"),
|
|
||||||
// htmxTarget: "#user-table",
|
|
||||||
// htmxPushUrl: false,
|
|
||||||
// htmxResetAfterRequest: true,
|
|
||||||
// htmxSwapOob: nil,
|
|
||||||
// htmxSwap: nil,
|
|
||||||
// context: .init(showConfirmPassword: true, showEmailInput: true, buttonLabel: "Create")
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private struct UsersCTX: Content {
|
|
||||||
// let user: User.DTO?
|
|
||||||
// let users: [User.DTO]
|
|
||||||
// let form: UserFormCTX
|
|
||||||
//
|
|
||||||
// init(
|
|
||||||
// user: User.DTO? = nil,
|
|
||||||
// users: [User.DTO],
|
|
||||||
// form: UserFormCTX? = nil
|
|
||||||
// ) {
|
|
||||||
// self.user = user
|
|
||||||
// self.users = users
|
|
||||||
// self.form = form ?? .create()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private extension UserApiController {
|
|
||||||
//
|
|
||||||
// func getSortedUsers(req: Request) async throws -> [User.DTO] {
|
|
||||||
// try await index(req: req)
|
|
||||||
// .sorted { ($0.username ?? "") < ($1.username ?? "") }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import DatabaseClientLive
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
|
// Taken from discussions page on `swift-dependencies`.
|
||||||
|
|
||||||
|
// TODO: Pass dependencies to set into this middleware.
|
||||||
struct DependenciesMiddleware: AsyncMiddleware {
|
struct DependenciesMiddleware: AsyncMiddleware {
|
||||||
|
|
||||||
private let values: DependencyValues.Continuation
|
private let values: DependencyValues.Continuation
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import ElementaryHTMX
|
|||||||
struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
|
struct MainPage<Inner: HTML>: SendableHTMLDocument where Inner: Sendable {
|
||||||
|
|
||||||
var title: String { "Purchase Orders" }
|
var title: String { "Purchase Orders" }
|
||||||
|
var lang: String { "en" }
|
||||||
|
|
||||||
let inner: Inner
|
let inner: Inner
|
||||||
let displayNav: Bool
|
let displayNav: Bool
|
||||||
@@ -56,7 +57,7 @@ struct RouteHeaderView: HTML {
|
|||||||
div(.class("container"), .style("padding: 20px 20px;")) {
|
div(.class("container"), .style("padding: 20px 20px;")) {
|
||||||
h1 { title }
|
h1 { title }
|
||||||
br()
|
br()
|
||||||
p { description }
|
p(.class("secondary")) { i { description } }
|
||||||
br()
|
br()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,20 +19,20 @@ struct UserDetail: HTML, Sendable {
|
|||||||
) {
|
) {
|
||||||
div(.class("row")) {
|
div(.class("row")) {
|
||||||
makeLabel(for: "username", value: "Username:")
|
makeLabel(for: "username", value: "Username:")
|
||||||
input(.class("col-5"), .type(.text), .id("username"), .name("username"), .value(user.username), .required)
|
input(.class("col-4"), .type(.text), .id("username"), .name("username"), .value(user.username), .required)
|
||||||
makeLabel(for: "email", value: "Email:")
|
makeLabel(for: "email", value: "Email:")
|
||||||
input(.class("col-5"), .type(.email), .id("email"), .name("email"), .value(user.email), .required)
|
input(.class("col-4"), .type(.email), .id("email"), .name("email"), .value(user.email), .required)
|
||||||
}
|
}
|
||||||
div(.class("row")) {
|
div(.class("row")) {
|
||||||
span(.class("label col-1")) { "Created:" }
|
span(.class("label col-2")) { "Created:" }
|
||||||
span(.class("date col-4")) { dateFormatter.formattedDate(user.createdAt) }
|
span(.class("date col-4")) { dateFormatter.formattedDate(user.createdAt) }
|
||||||
span(.class("label col-1")) { "Updated:" }
|
span(.class("label col-2")) { "Updated:" }
|
||||||
span(.class("date col-4")) { dateFormatter.formattedDate(user.updatedAt) }
|
span(.class("date col-4")) { dateFormatter.formattedDate(user.updatedAt) }
|
||||||
}
|
}
|
||||||
div(.class("btn-row user-buttons")) {
|
div(.class("btn-row user-buttons")) {
|
||||||
button(
|
button(
|
||||||
.type(.submit),
|
.type(.submit),
|
||||||
.style("background-color: blue; color: white;")
|
.class("btn-secondary")
|
||||||
) { "Update" }
|
) { "Update" }
|
||||||
Button.danger { "Delete" }
|
Button.danger { "Delete" }
|
||||||
.attributes(
|
.attributes(
|
||||||
@@ -40,6 +40,7 @@ struct UserDetail: HTML, Sendable {
|
|||||||
.hx.trigger(.event(.click)),
|
.hx.trigger(.event(.click)),
|
||||||
.hx.swap(.outerHTML),
|
.hx.swap(.outerHTML),
|
||||||
.hx.target("#user_\(user.id)"),
|
.hx.target("#user_\(user.id)"),
|
||||||
|
.hx.confirm("Are you sure you want to delete this user?"),
|
||||||
.custom(name: "hx-on::after-request", value: "toggleContent('float'); window.location.href='/users';")
|
.custom(name: "hx-on::after-request", value: "toggleContent('float'); window.location.href='/users';")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -52,7 +53,7 @@ struct UserDetail: HTML, Sendable {
|
|||||||
for name: String,
|
for name: String,
|
||||||
value: String
|
value: String
|
||||||
) -> some HTML {
|
) -> some HTML {
|
||||||
label(.for(name), .class("col-1")) { span(.class("label")) { value } }
|
label(.for(name), .class("col-2")) { span(.class("label")) { value } }
|
||||||
}
|
}
|
||||||
|
|
||||||
func row(_ label: String, _ value: String) -> some HTML<HTMLTag.tr> {
|
func row(_ label: String, _ value: String) -> some HTML<HTMLTag.tr> {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Elementary
|
import Elementary
|
||||||
import ElementaryHTMX
|
import ElementaryHTMX
|
||||||
|
|
||||||
|
// Form used to login or create a new user.
|
||||||
struct UserForm: HTML, Sendable {
|
struct UserForm: HTML, Sendable {
|
||||||
let context: Context
|
let context: Context
|
||||||
|
|
||||||
@@ -27,15 +28,19 @@ struct UserForm: HTML, Sendable {
|
|||||||
value: "if(event.detail.successful) this.reset(); toggleContent('float');"
|
value: "if(event.detail.successful) this.reset(); toggleContent('float');"
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
div(.class("row")) {
|
||||||
input(.type(.text), .id("username"), .name("username"), .placeholder("Username"), .autofocus, .required)
|
input(.type(.text), .id("username"), .name("username"), .placeholder("Username"), .autofocus, .required)
|
||||||
br()
|
|
||||||
if context.showEmailInput {
|
|
||||||
input(.type(.email), .id("email"), .name("email"), .placeholder("Email"), .required)
|
|
||||||
br()
|
|
||||||
}
|
}
|
||||||
|
if context.showEmailInput {
|
||||||
|
div(.class("row")) {
|
||||||
|
input(.type(.email), .id("email"), .name("email"), .placeholder("Email"), .required)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div(.class("row")) {
|
||||||
input(.type(.password), .id("password"), .name("password"), .placeholder("Password"), .required)
|
input(.type(.password), .id("password"), .name("password"), .placeholder("Password"), .required)
|
||||||
br()
|
}
|
||||||
if context.showConfirmPassword {
|
if context.showConfirmPassword {
|
||||||
|
div(.class("row")) {
|
||||||
input(
|
input(
|
||||||
.type(.password),
|
.type(.password),
|
||||||
.id("confirmPassword"),
|
.id("confirmPassword"),
|
||||||
@@ -43,9 +48,11 @@ struct UserForm: HTML, Sendable {
|
|||||||
.placeholder("Confirm Password"),
|
.placeholder("Confirm Password"),
|
||||||
.required
|
.required
|
||||||
)
|
)
|
||||||
br()
|
|
||||||
}
|
}
|
||||||
input(.type(.submit), .value(context.buttonLabel))
|
}
|
||||||
|
div(.class("row")) {
|
||||||
|
button(.type(.submit), .class("btn-primary")) { context.buttonLabel }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,15 +44,12 @@ struct UserTable: HTML {
|
|||||||
td { user.username }
|
td { user.username }
|
||||||
td { user.email }
|
td { user.email }
|
||||||
td {
|
td {
|
||||||
button(
|
Button.detail().attributes(
|
||||||
.hx.get("/users/\(user.id.uuidString)"),
|
.hx.get("/users/\(user.id.uuidString)"),
|
||||||
.hx.target("#float"),
|
.hx.target("#float"),
|
||||||
.hx.swap(.outerHTML),
|
.hx.swap(.outerHTML),
|
||||||
.hx.pushURL(true),
|
.hx.pushURL(true)
|
||||||
.class("btn-detail")
|
)
|
||||||
) {
|
|
||||||
"〉"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ struct ToggleFormButton: HTML {
|
|||||||
enum Button {
|
enum Button {
|
||||||
|
|
||||||
static func add() -> some HTML<HTMLTag.button> {
|
static func add() -> some HTML<HTMLTag.button> {
|
||||||
button(.class("btn-add")) { "+" }
|
button(.class("btn btn-add")) { "+" }
|
||||||
}
|
}
|
||||||
|
|
||||||
static func danger<C: HTML>(@HTMLBuilder body: () -> C) -> some HTML<HTMLTag.button> {
|
static func danger<C: HTML>(@HTMLBuilder body: () -> C) -> some HTML<HTMLTag.button> {
|
||||||
@@ -28,8 +28,14 @@ enum Button {
|
|||||||
button(.class("btn-update")) { "Update" }
|
button(.class("btn-update")) { "Update" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func detail() -> some HTML<HTMLTag.button> {
|
||||||
|
button(.class("btn-detail")) {
|
||||||
|
"〉"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func makeOnClick(_ id: String, _ resetURL: String?) -> String {
|
private static func makeOnClick(_ id: String, _ resetURL: String?) -> String {
|
||||||
var output = "toggleContent('\(id)');"
|
let output = "toggleContent('\(id)');"
|
||||||
if let resetURL {
|
if let resetURL {
|
||||||
return "\(output) window.location.href='\(resetURL)';"
|
return "\(output) window.location.href='\(resetURL)';"
|
||||||
}
|
}
|
||||||
15
Sources/App/Views/Vendors/VendorDetail.swift
Normal file
15
Sources/App/Views/Vendors/VendorDetail.swift
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import Elementary
|
||||||
|
import ElementaryHTMX
|
||||||
|
import SharedModels
|
||||||
|
|
||||||
|
struct VendorDetail: HTML {
|
||||||
|
let vendor: Vendor?
|
||||||
|
|
||||||
|
var content: some HTML {
|
||||||
|
div(.class("container")) {
|
||||||
|
VendorForm(vendor: vendor)
|
||||||
|
// TODO: Branch table + form.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
37
Sources/App/Views/Vendors/VendorForm.swift
Normal file
37
Sources/App/Views/Vendors/VendorForm.swift
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import Elementary
|
||||||
|
import ElementaryHTMX
|
||||||
|
import SharedModels
|
||||||
|
|
||||||
|
struct VendorForm: HTML {
|
||||||
|
let vendor: Vendor?
|
||||||
|
|
||||||
|
var content: some HTML<HTMLTag.form> {
|
||||||
|
form(
|
||||||
|
.id("vendor-form"),
|
||||||
|
vendor != nil ? .hx.put(targetURL) : .hx.post(targetURL),
|
||||||
|
.hx.target("this"),
|
||||||
|
.hx.swap(.outerHTML)
|
||||||
|
) {
|
||||||
|
div(.class("row")) {
|
||||||
|
input(
|
||||||
|
.id("vendor-name"),
|
||||||
|
.name("name"),
|
||||||
|
.value(vendor?.name ?? ""),
|
||||||
|
.placeholder("Vendor Name"),
|
||||||
|
.required
|
||||||
|
)
|
||||||
|
button(.type(.submit), .class("btn-primary")) { buttonLabel }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var buttonLabel: String {
|
||||||
|
guard vendor != nil else { return "Update" }
|
||||||
|
return "Create"
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetURL: String {
|
||||||
|
guard let vendor else { return "/vendors" }
|
||||||
|
return "/vendors/\(vendor.id)"
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Sources/App/Views/Vendors/VendorTable.swift
Normal file
34
Sources/App/Views/Vendors/VendorTable.swift
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import Elementary
|
||||||
|
import ElementaryHTMX
|
||||||
|
import SharedModels
|
||||||
|
|
||||||
|
struct VendorTable: HTML {
|
||||||
|
let vendors: [Vendor]
|
||||||
|
|
||||||
|
var content: some HTML {
|
||||||
|
table {
|
||||||
|
thead {
|
||||||
|
th { "Name" }
|
||||||
|
th {}
|
||||||
|
th { Button.add() }
|
||||||
|
}
|
||||||
|
tbody(.id("vendor-table")) {
|
||||||
|
for vendor in vendors {
|
||||||
|
Row(vendor: vendor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Row: HTML {
|
||||||
|
let vendor: Vendor
|
||||||
|
|
||||||
|
var content: some HTML<HTMLTag.tr> {
|
||||||
|
tr(.id("vendor_\(vendor.id)")) {
|
||||||
|
td { vendor.name.capitalized }
|
||||||
|
td { "(\(vendor.branches?.count ?? 0)) Branches" }
|
||||||
|
td {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user