diff --git a/Public/css/main.css b/Public/css/main.css
index dcdc4fd..eb0eee4 100644
--- a/Public/css/main.css
+++ b/Public/css/main.css
@@ -118,16 +118,25 @@ input[type=text], input[type=password] {
padding: 5px;
}
-.btn-delete {
- display: inline-block;
- color: red;
- text-align: center;
+.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 {
diff --git a/Public/images/pencil.svg b/Public/images/pencil.svg
new file mode 100644
index 0000000..2d1dc2b
--- /dev/null
+++ b/Public/images/pencil.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/Resources/Views/employee-form.leaf b/Resources/Views/employee-form.leaf
new file mode 100644
index 0000000..94e40ae
--- /dev/null
+++ b/Resources/Views/employee-form.leaf
@@ -0,0 +1,45 @@
+
+
Employees
Employees are who purchase orders can be generated for.
-
+ #extend("employee-form", form)
#extend("employee-table")
diff --git a/Resources/Views/user-table.leaf b/Resources/Views/user-table.leaf
new file mode 100644
index 0000000..9861469
--- /dev/null
+++ b/Resources/Views/user-table.leaf
@@ -0,0 +1,12 @@
+
+
+ | Username |
+ Email |
+
+ #for(user in users):
+
+ | #(user.username) |
+ #(user.email) |
+
+ #endfor
+
diff --git a/Resources/Views/users.leaf b/Resources/Views/users.leaf
index 6ed475f..d11ba43 100644
--- a/Resources/Views/users.leaf
+++ b/Resources/Views/users.leaf
@@ -1,19 +1,9 @@
Users
+
Users are people that can login and generate puchase orders for employees.
-
-
- | Username |
- Email |
-
- #for(user in users):
-
- | #(user.username) |
- #(user.email) |
-
- #endfor
-
+ #extend("user-table")
diff --git a/Sources/App/Controllers/EmployeeViewController.swift b/Sources/App/Controllers/EmployeeViewController.swift
new file mode 100644
index 0000000..345d564
--- /dev/null
+++ b/Sources/App/Controllers/EmployeeViewController.swift
@@ -0,0 +1,111 @@
+import Fluent
+import Leaf
+import Vapor
+
+struct EmployeeViewController: RouteCollection {
+
+ private let api = ApiController()
+
+ func boot(routes: any RoutesBuilder) throws {
+ let employees = routes.protected.grouped("employees")
+
+ // MARK: Protected routes.
+
+ employees.get(use: employees(req:))
+ employees.get("form", use: employeeForm(req:))
+ employees.post(use: postEmployeeForm(req:))
+ employees.group(":employeeID") {
+ $0.get(use: editEmployee(req:))
+ $0.delete(use: deleteEmployee(req:))
+ $0.put(use: updateEmployee(req:))
+ $0.post("toggle-active", use: toggleActiveEmployee(req:))
+ }
+ }
+
+ @Sendable
+ func employees(req: Request) async throws -> View {
+ return try await req.view.render("employees", EmployeesCTX(api: api, req: req))
+ }
+
+ @Sendable
+ func postEmployeeForm(req: Request) async throws -> View {
+ _ = try await api.createEmployee(req: req)
+ let employees = try await api.getSortedEmployees(req: req)
+ return try await req.view.render("employee-table", ["employees": employees])
+ }
+
+ @Sendable
+ func toggleActiveEmployee(req: Request) async throws -> View {
+ guard let employee = try await Employee.find(req.parameters.get("employeeID"), on: req.db) else {
+ throw Abort(.notFound)
+ }
+ employee.active.toggle()
+ try await employee.save(on: req.db)
+ let employees = try await api.getSortedEmployees(req: req)
+ return try await req.view.render("employee-table", ["employees": employees])
+ }
+
+ @Sendable
+ func deleteEmployee(req: Request) async throws -> View {
+ _ = try await api.deleteEmployee(req: req)
+ let employees = try await api.getSortedEmployees(req: req)
+ return try await req.view.render("employee-table", ["employees": employees])
+ }
+
+ @Sendable
+ func editEmployee(req: Request) async throws -> View {
+ guard let employee = try await Employee.find(req.parameters.get("employeeID"), on: req.db) else {
+ throw Abort(.notFound)
+ }
+ return try await req.view.render("employee-form", EmployeeFormCTX(employee: employee.toDTO()))
+ }
+
+ @Sendable
+ func updateEmployee(req: Request) async throws -> View {
+ _ = try await api.updateEmployee(req: req)
+ return try await req.view.render("employees", EmployeesCTX(oob: true, api: api, req: req))
+ }
+
+ @Sendable
+ func employeeForm(req: Request) async throws -> View {
+ try await req.view.render("employee-form", EmployeeFormCTX())
+ }
+
+}
+
+private struct EmployeesCTX: Content {
+ let oob: Bool
+ let employees: [Employee.DTO]
+ let form: EmployeeFormCTX
+
+ init(
+ oob: Bool = false,
+ employee: Employee? = nil,
+ api: ApiController,
+ req: Request
+ ) async throws {
+ self.oob = oob
+ self.employees = try await api.getSortedEmployees(req: req)
+ self.form = .init(employee: employee.map { $0.toDTO() })
+ }
+}
+
+private struct EmployeeFormCTX: Content {
+ let employee: Employee.DTO?
+ let oob: Bool
+
+ init(employee: Employee.DTO? = nil) {
+ self.employee = employee
+ self.oob = employee != nil
+ }
+}
+
+private extension ApiController {
+
+ func getSortedEmployees(req: Request) async throws -> [Employee.DTO] {
+ var employees = try await employeesIndex(req: req)
+ employees.sort { ($0.active ?? false) && !($1.active ?? false) }
+ employees.sort { ($0.lastName ?? "") < ($1.lastName ?? "") }
+ return employees
+ }
+}
diff --git a/Sources/App/Controllers/ViewController.swift b/Sources/App/Controllers/ViewController.swift
index 41bfac4..eea11db 100644
--- a/Sources/App/Controllers/ViewController.swift
+++ b/Sources/App/Controllers/ViewController.swift
@@ -7,9 +7,8 @@ struct ViewController: RouteCollection {
private let api = ApiController()
func boot(routes: any RoutesBuilder) throws {
- let protected = routes.grouped(User.credentialsAuthenticator(), User.redirectMiddleware(path: "login"))
+ let protected = routes.protected
let login = routes.grouped("login")
- let employees = protected.grouped("employees")
// MARK: - Non-protected routes.
@@ -23,12 +22,7 @@ struct ViewController: RouteCollection {
protected.get("home", use: home(req:))
protected.get("users", use: users(req:))
- employees.get(use: employees(req:))
- employees.post(use: postEmployeeForm(req:))
- employees.group(":employeeID") {
- $0.delete(use: deleteEmployee(req:))
- $0.post("toggle-active", use: toggleActiveEmployee(req:))
- }
+ try routes.register(collection: EmployeeViewController())
}
@Sendable
@@ -66,6 +60,8 @@ struct ViewController: RouteCollection {
return try await req.view.render("login")
}
+ // TODO: Add route parameters for active route / tab.
+
@Sendable
func home(req: Request) async throws -> View {
try await req.view.render("home")
@@ -77,50 +73,9 @@ struct ViewController: RouteCollection {
return try await req.view.render("users", ["users": users])
}
- @Sendable
- func employees(req: Request) async throws -> View {
- let employees = try await api.getSortedEmployees(req: req)
- return try await req.view.render("employees", ["employees": employees])
- }
-
- @Sendable
- func postEmployeeForm(req: Request) async throws -> View {
- _ = try await api.createEmployee(req: req)
- let employees = try await api.getSortedEmployees(req: req)
- return try await req.view.render("employee-table", ["employees": employees])
- }
-
- @Sendable
- func toggleActiveEmployee(req: Request) async throws -> View {
- guard let employee = try await Employee.find(req.parameters.get("employeeID"), on: req.db) else {
- throw Abort(.notFound)
- }
- employee.active.toggle()
- try await employee.save(on: req.db)
- let employees = try await api.getSortedEmployees(req: req)
- return try await req.view.render("employee-table", ["employees": employees])
- }
-
- @Sendable
- func deleteEmployee(req: Request) async throws -> View {
- _ = try await api.deleteEmployee(req: req)
- let employees = try await api.getSortedEmployees(req: req)
- return try await req.view.render("employee-table", ["employees": employees])
- }
-
}
private struct UserForm: Content {
let username: String
let password: String
}
-
-private extension ApiController {
-
- func getSortedEmployees(req: Request) async throws -> [Employee.DTO] {
- var employees = try await employeesIndex(req: req)
- employees.sort { ($0.active ?? false) && !($1.active ?? false) }
- employees.sort { ($0.lastName ?? "") < ($1.lastName ?? "") }
- return employees
- }
-}
diff --git a/Sources/App/Extensions/RouteBuilder+protected.swift b/Sources/App/Extensions/RouteBuilder+protected.swift
new file mode 100644
index 0000000..4ed49c0
--- /dev/null
+++ b/Sources/App/Extensions/RouteBuilder+protected.swift
@@ -0,0 +1,10 @@
+import Vapor
+
+extension RoutesBuilder {
+
+ // Used to ensure views are protected, redirects users to the login page if they're
+ // not authenticated.
+ var protected: any RoutesBuilder {
+ grouped(User.credentialsAuthenticator(), User.redirectMiddleware(path: "login"))
+ }
+}
diff --git a/Sources/App/Models/Employee.swift b/Sources/App/Models/Employee.swift
index b1065e6..fee9e2e 100644
--- a/Sources/App/Models/Employee.swift
+++ b/Sources/App/Models/Employee.swift
@@ -2,6 +2,8 @@ import Fluent
import struct Foundation.UUID
import Vapor
+// TODO: Add soft-delete??
+
/// The employee database model.
///
/// An employee is someone that PO's can be generated for. They can be either a field