first commit
This commit is contained in:
commit
708722865d
14 changed files with 672 additions and 0 deletions
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
#repo specific files
|
||||
tms.exe
|
||||
training.db
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
module tms
|
||||
|
||||
go 1.22.0
|
||||
|
||||
require github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||
2
go.sum
Normal file
2
go.sum
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
316
main.go
Normal file
316
main.go
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
//go:embed templates/*
|
||||
var templatesFS embed.FS
|
||||
|
||||
//go:embed schema.sql
|
||||
var schemaSQL string
|
||||
|
||||
type Trainer struct {
|
||||
ID int
|
||||
Name string
|
||||
Email string
|
||||
Areas []TrainingArea
|
||||
}
|
||||
|
||||
type Training struct {
|
||||
ID int
|
||||
Title string
|
||||
TrainingType string
|
||||
Mode string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Status string
|
||||
Trainers []Trainer
|
||||
Area TrainingArea
|
||||
}
|
||||
|
||||
type TrainingArea struct {
|
||||
ID int
|
||||
Name string
|
||||
Trainers []Trainer
|
||||
}
|
||||
|
||||
func (ta TrainingArea) IsTrainerAssigned(trainerID int) bool {
|
||||
if ta.Trainers == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, trainer := range ta.Trainers {
|
||||
if trainer.ID == trainerID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var db *sql.DB
|
||||
|
||||
func initDB() {
|
||||
var err error
|
||||
db, err = sql.Open("sqlite3", "training.db")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(schemaSQL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var tmpl *template.Template
|
||||
|
||||
func initTemplates() {
|
||||
var err error
|
||||
tmpl, err = template.ParseFS(templatesFS, "templates/*.html")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error parsing templates: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
func homeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
err := tmpl.ExecuteTemplate(w, "home.html", nil)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func trainingAreaListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
areas, err := fetchTrainingAreasWithTrainers()
|
||||
if err != nil {
|
||||
http.Error(w, "Database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
trainers, err := fetchTrainers()
|
||||
if err != nil {
|
||||
http.Error(w, "Database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data := struct {
|
||||
Areas []TrainingArea
|
||||
Trainers []Trainer
|
||||
}{
|
||||
Areas: areas,
|
||||
Trainers: trainers,
|
||||
}
|
||||
|
||||
err = tmpl.ExecuteTemplate(w, "training_area_list.html", data)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func fetchTrainingAreasWithTrainers() ([]TrainingArea, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT ta.id, ta.name, t.id, t.name, t.email
|
||||
FROM training_areas ta
|
||||
LEFT JOIN trainer_training_areas tta ON ta.id = tta.training_area_id
|
||||
LEFT JOIN trainers t ON tta.trainer_id = t.id
|
||||
ORDER BY ta.id, t.name`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
areaMap := make(map[int]*TrainingArea)
|
||||
|
||||
for rows.Next() {
|
||||
var areaID int
|
||||
var areaName string
|
||||
var trainerID sql.NullInt64
|
||||
var trainerName sql.NullString
|
||||
var trainerEmail sql.NullString
|
||||
|
||||
err := rows.Scan(&areaID, &areaName, &trainerID, &trainerName, &trainerEmail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, exists := areaMap[areaID]
|
||||
if !exists {
|
||||
area := &TrainingArea{ID: areaID, Name: areaName}
|
||||
areaMap[areaID] = area
|
||||
}
|
||||
|
||||
if trainerID.Valid {
|
||||
areaMap[areaID].Trainers = append(areaMap[areaID].Trainers, Trainer{
|
||||
ID: int(trainerID.Int64),
|
||||
Name: trainerName.String,
|
||||
Email: trainerEmail.String,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var areas []TrainingArea
|
||||
for _, area := range areaMap {
|
||||
areas = append(areas, *area)
|
||||
}
|
||||
|
||||
return areas, nil
|
||||
}
|
||||
|
||||
func assignTrainerToAreaHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost {
|
||||
trainerID := r.FormValue("trainer_id")
|
||||
areaID := r.FormValue("training_area_id")
|
||||
|
||||
if trainerID == "" || areaID == "" {
|
||||
http.Error(w, "Trainer and Training Area are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, err := db.Exec("INSERT OR IGNORE INTO trainer_training_areas (trainer_id, training_area_id) VALUES (?, ?)", trainerID, areaID)
|
||||
if err != nil {
|
||||
http.Error(w, "Database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/training-areas", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func addTrainingAreaHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost {
|
||||
name := r.FormValue("name")
|
||||
if name == "" {
|
||||
http.Error(w, "Name is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, err := db.Exec("INSERT INTO training_areas (name) VALUES (?)", name)
|
||||
if err != nil {
|
||||
http.Error(w, "Database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/training-areas", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
err := tmpl.ExecuteTemplate(w, "add_training_area.html", nil)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchTrainings() ([]Training, error) {
|
||||
rows, err := db.Query("SELECT id, title, training_type, mode, start, end, status FROM trainings")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var trainings []Training
|
||||
for rows.Next() {
|
||||
var t Training
|
||||
var start, end string
|
||||
err := rows.Scan(&t.ID, &t.Title, &t.TrainingType, &t.Mode, &start, &end, &t.Status)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Start, _ = time.Parse("2006-01-02 15:04:05", start)
|
||||
t.End, _ = time.Parse("2006-01-02 15:04:05", end)
|
||||
trainings = append(trainings, t)
|
||||
}
|
||||
return trainings, nil
|
||||
}
|
||||
|
||||
func trainingListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
trainings, err := fetchTrainings()
|
||||
if err != nil {
|
||||
http.Error(w, "Database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = tmpl.ExecuteTemplate(w, "training_list.html", trainings)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchTrainers() ([]Trainer, error) {
|
||||
rows, err := db.Query("SELECT id, name, email FROM trainers")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var trainers []Trainer
|
||||
for rows.Next() {
|
||||
var t Trainer
|
||||
err := rows.Scan(&t.ID, &t.Name, &t.Email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trainers = append(trainers, t)
|
||||
}
|
||||
return trainers, nil
|
||||
}
|
||||
|
||||
func trainerListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
trainers, err := fetchTrainers()
|
||||
if err != nil {
|
||||
http.Error(w, "Database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = tmpl.ExecuteTemplate(w, "trainer_list.html", trainers)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func addTrainerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost {
|
||||
name := r.FormValue("name")
|
||||
email := r.FormValue("email")
|
||||
if name == "" || email == "" {
|
||||
http.Error(w, "Name and Email are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, err := db.Exec("INSERT INTO trainers (name, email) VALUES (?, ?)", name, email)
|
||||
if err != nil {
|
||||
http.Error(w, "Database error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, "/trainers", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
err := tmpl.ExecuteTemplate(w, "add_trainer.html", nil)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error in templating page %v", err), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
initDB()
|
||||
initTemplates()
|
||||
http.HandleFunc("/", homeHandler)
|
||||
http.HandleFunc("/trainings", trainingListHandler)
|
||||
http.HandleFunc("/trainers", trainerListHandler)
|
||||
http.HandleFunc("/trainers/add", addTrainerHandler)
|
||||
http.HandleFunc("/training-areas", trainingAreaListHandler)
|
||||
http.HandleFunc("/training-areas/add", addTrainingAreaHandler)
|
||||
http.HandleFunc("/training-areas/assign-trainer", assignTrainerToAreaHandler)
|
||||
|
||||
fmt.Println("Server running on http://localhost:8080")
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
44
schema.sql
Normal file
44
schema.sql
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
CREATE TABLE IF NOT EXISTS trainings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
training_type TEXT NOT NULL,
|
||||
mode TEXT NOT NULL,
|
||||
start DATETIME NOT NULL,
|
||||
end DATETIME NOT NULL,
|
||||
status TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS trainers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS training_trainers (
|
||||
training_id INTEGER NOT NULL,
|
||||
trainer_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (training_id) REFERENCES trainings(id),
|
||||
FOREIGN KEY (trainer_id) REFERENCES trainers(id),
|
||||
PRIMARY KEY (training_id, trainer_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS training_areas (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS trainer_training_areas (
|
||||
trainer_id INTEGER,
|
||||
training_area_id INTEGER,
|
||||
PRIMARY KEY (trainer_id, training_area_id),
|
||||
FOREIGN KEY (trainer_id) REFERENCES trainers(id),
|
||||
FOREIGN KEY (training_area_id) REFERENCES training_areas(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS training_training_areas (
|
||||
training_id INTEGER,
|
||||
training_area_id INTEGER,
|
||||
PRIMARY KEY (training_id, training_area_id),
|
||||
FOREIGN KEY (training_id) REFERENCES trainings(id),
|
||||
FOREIGN KEY (training_area_id) REFERENCES training_areas(id)
|
||||
);
|
||||
19
templates/add_trainer.html
Normal file
19
templates/add_trainer.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
{{template "head.html" .}} <!-- Include header template -->
|
||||
|
||||
<body>
|
||||
{{template "header.html" .}}
|
||||
<h1>Add Trainer</h1>
|
||||
<a href="/">Back to Home</a>
|
||||
<form action="/trainers/add" method="POST">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name" required><br><br>
|
||||
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email" required><br><br>
|
||||
|
||||
<button type="submit">Add Trainer</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
18
templates/add_training_area.html
Normal file
18
templates/add_training_area.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
{{template "head.html" .}} <!-- Include header template -->
|
||||
|
||||
<body>
|
||||
{{template "header.html" .}}
|
||||
<h1>Add New Training Area</h1>
|
||||
<form action="/training-areas/add" method="post">
|
||||
<label for="name">Training Area Name:</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
<br>
|
||||
<a href="/training-areas">Back to Training Areas</a>
|
||||
<br><br>
|
||||
<a href="/">Back to Home</a>
|
||||
</body>
|
||||
</html>
|
||||
101
templates/css.html
Normal file
101
templates/css.html
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<!-- css.html -->
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #06041f;
|
||||
}
|
||||
|
||||
/* Header settings */
|
||||
|
||||
header {
|
||||
background-color: #06041f;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
header img {
|
||||
width: 150px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
color: #47d7ac;
|
||||
}
|
||||
|
||||
/* home */
|
||||
.tiles ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.tiles li {
|
||||
background-color: #ffffff;
|
||||
border: 2px solid #47d7ac;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
width: 180px;
|
||||
text-align: center;
|
||||
margin: 10px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.tiles li:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.tiles a {
|
||||
text-decoration: none;
|
||||
color: #06041f;
|
||||
font-size: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tiles .tile-icon {
|
||||
font-size: 40px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #47d7ac;
|
||||
}
|
||||
|
||||
td {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #06041f;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #47d7ac;
|
||||
color: #fff;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #35a895;
|
||||
}
|
||||
</style>
|
||||
6
templates/head.html
Normal file
6
templates/head.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<!-- header.html -->
|
||||
<head>
|
||||
<title>TMS - Nagarro Training Solutions</title>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
|
||||
{{template "css.html" .}}
|
||||
</head>
|
||||
4
templates/header.html
Normal file
4
templates/header.html
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<header>
|
||||
<img src="logo.png" alt="Training Management Logo">
|
||||
<h1>Nagarro Training Solutions</h1>
|
||||
</header>
|
||||
33
templates/home.html
Normal file
33
templates/home.html
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
{{template "head.html" .}} <!-- Include header template -->
|
||||
|
||||
<body>
|
||||
{{template "header.html" .}}
|
||||
<h1>Welcome to the Training Management System</h1>
|
||||
<div class="tiles">
|
||||
<ul>
|
||||
<li>
|
||||
<div class="tile-icon"><i class="fas fa-chalkboard-teacher"></i></div> <!-- View Trainings icon -->
|
||||
<a href="/trainings">View Trainings</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="tile-icon"><i class="fas fa-users"></i></div> <!-- View Trainers icon -->
|
||||
<a href="/trainers">View Trainers</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="tile-icon"><i class="fas fa-user-plus"></i></div> <!-- Add Trainer icon -->
|
||||
<a href="/trainers/add">Add Trainer</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="tile-icon"><i class="fas fa-clipboard-list"></i></div> <!-- View Training Areas icon -->
|
||||
<a href="/training-areas">View Training Areas</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="tile-icon"><i class="fas fa-map-marker-alt"></i></div> <!-- Add Training Area icon -->
|
||||
<a href="/training-areas/add">Add Training Area</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
24
templates/trainer_list.html
Normal file
24
templates/trainer_list.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
{{template "head.html" .}} <!-- Include header template -->
|
||||
|
||||
<body>
|
||||
{{template "header.html" .}}
|
||||
|
||||
<h1>Trainers</h1>
|
||||
<a href="/">Back to Home</a>
|
||||
<a href="/trainers/add">Add Trainer</a>
|
||||
<table border="1">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
</tr>
|
||||
{{ range . }}
|
||||
<tr>
|
||||
<td>{{ .Name }}</td>
|
||||
<td>{{ .Email }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
41
templates/training_area_list.html
Normal file
41
templates/training_area_list.html
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
{{template "head.html" .}} <!-- Include header template -->
|
||||
|
||||
<body>
|
||||
{{template "header.html" .}}
|
||||
|
||||
<h1>Training Areas</h1>
|
||||
<a href="/training-areas/add">Add Training Area</a>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Training Area</th>
|
||||
{{range $.Trainers}}
|
||||
<th>{{.Name}}</th>
|
||||
{{end}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range $i, $currentArea := .Areas}}
|
||||
<tr>
|
||||
<td>{{.Name}}</td>
|
||||
{{range $.Trainers}}
|
||||
<td>
|
||||
<form action="/training-areas/assign-trainer" method="post" style="display:inline;">
|
||||
<input type="hidden" name="training_area_id" value="{{$currentArea.ID}}">
|
||||
<input type="hidden" name="trainer_id" value="{{.ID}}">
|
||||
<input type="checkbox" name="assigned" value="true" {{if $currentArea.IsTrainerAssigned .ID}}checked{{end}} onchange="this.form.submit()">
|
||||
</form>
|
||||
</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a href="/">Back to Home</a>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
30
templates/training_list.html
Normal file
30
templates/training_list.html
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
{{template "head.html" .}} <!-- Include header template -->
|
||||
|
||||
<body>
|
||||
{{template "header.html" .}}
|
||||
<h1>Trainings</h1>
|
||||
<a href="/">Back to Home</a>
|
||||
<table border="1">
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Type</th>
|
||||
<th>Mode</th>
|
||||
<th>Start</th>
|
||||
<th>End</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
{{ range . }}
|
||||
<tr>
|
||||
<td>{{ .Title }}</td>
|
||||
<td>{{ .TrainingType }}</td>
|
||||
<td>{{ .Mode }}</td>
|
||||
<td>{{ .Start }}</td>
|
||||
<td>{{ .End }}</td>
|
||||
<td>{{ .Status }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue