Compare commits
11 Commits
online_que
...
5049db064c
Author | SHA1 | Date | |
---|---|---|---|
5049db064c | |||
aded71394d | |||
d054b3644b | |||
b78e30d109 | |||
52cd5756d8 | |||
1e6d83de1d | |||
4de7d36385 | |||
35d565ec7d | |||
6359caf3e9 | |||
8622f81f89 | |||
4e0c8ec1ac |
2
.gitignore
vendored
2
.gitignore
vendored
@ -0,0 +1,2 @@
|
||||
tmp
|
||||
test.sql
|
||||
|
@ -1,21 +1,23 @@
|
||||
USE sicherheitsunterweisung;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
DROP TABLE IF EXISTS instructors;
|
||||
DROP TABLE IF EXISTS briefings;
|
||||
DROP TABLE IF EXISTS participants;
|
||||
DROP TABLE IF EXISTS questions;
|
||||
DROP TABLE IF EXISTS given_answers;
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
CREATE TABLE instructors (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
first_name VARCHAR(32) NOT NULL,
|
||||
last_name VARCHAR(32) NOT NULL,
|
||||
personnel_id INT NOT NULL,
|
||||
id INT NOT NULL,
|
||||
first_name VARCHAR(32) NOT NULL,
|
||||
last_name VARCHAR(32) NOT NULL,
|
||||
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
CREATE TABLE briefings (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
id INT AUTO_INCREMENT,
|
||||
date DATE NOT NULL,
|
||||
time TIME NOT NULL,
|
||||
location VARCHAR(32) NOT NULL,
|
||||
@ -28,7 +30,7 @@ CREATE TABLE briefings (
|
||||
);
|
||||
|
||||
CREATE TABLE participants (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
id INT AUTO_INCREMENT,
|
||||
first_name VARCHAR(32) NOT NULL,
|
||||
last_name VARCHAR(32) NOT NULL,
|
||||
company VARCHAR(32) NOT NULL,
|
||||
@ -37,7 +39,7 @@ CREATE TABLE participants (
|
||||
);
|
||||
|
||||
CREATE TABLE questions (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
id INT AUTO_INCREMENT,
|
||||
question VARCHAR(256) NOT NULL,
|
||||
answer_1 VARCHAR(64) NOT NULL,
|
||||
answer_2 VARCHAR(64) NOT NULL,
|
||||
@ -61,17 +63,17 @@ CREATE TABLE given_answers (
|
||||
);
|
||||
|
||||
INSERT INTO instructors
|
||||
(first_name, last_name, personnel_id)
|
||||
(id, first_name, last_name)
|
||||
VALUES
|
||||
( 'Jason', 'Streifling', '123456' ),
|
||||
( 'Tim', 'Taler', '123457' ),
|
||||
( 'Georg', 'aus dem Jungel', '123458' );
|
||||
( '123456', 'Jason', 'Streifling' ),
|
||||
( '123457', 'Tim', 'Taler' ),
|
||||
( '123458', 'Georg', 'aus dem Jungel' );
|
||||
|
||||
INSERT INTO briefings (
|
||||
date, time, location, as_of, instructor_id
|
||||
date, time, location, document_name, as_of, instructor_id
|
||||
) VALUES
|
||||
( '2023-10-16', '17:00:00', 'Werk Langenhagen', 'ICS-2021-LGH', '2021-02-01', '1' ),
|
||||
( '2023-10-16', '17:05:00', 'Werk Langenhagen', 'ICS-2021-LGH', '2021-02-01', '2' );
|
||||
( '2023-10-16', '17:00:00', 'Werk Langenhagen', 'ICS-2021-LGH', '2021-02-01', '123456' ),
|
||||
( '2023-10-16', '17:05:00', 'Werk Langenhagen', 'ICS-2021-LGH', '2021-02-01', '123457' );
|
||||
|
||||
INSERT INTO participants (
|
||||
first_name, last_name, company
|
||||
@ -84,7 +86,8 @@ INSERT INTO questions (
|
||||
) VALUES
|
||||
( 'Was ist 1+1?', '1', '2', '3', '4', '2' ),
|
||||
( 'Was ist 1+2?', '1', '2', '3', '4', '3' ),
|
||||
( 'Was ist 2+2?', '1', '2', '3', '4', '4' );
|
||||
( 'Was ist 2+2?', '1', '2', '3', '4', '4' ),
|
||||
( 'Was ist 0+1?', '1', '2', '3', '4', '1' );
|
||||
|
||||
INSERT INTO given_answers (
|
||||
briefing_id, participant_id, question_id, given_answer
|
||||
@ -92,6 +95,8 @@ INSERT INTO given_answers (
|
||||
( '1', '1', '1', '2' ),
|
||||
( '1', '1', '2', '3' ),
|
||||
( '1', '1', '3', '3' ),
|
||||
( '1', '1', '4', '1' ),
|
||||
( '2', '2', '1', '2' ),
|
||||
( '2', '2', '2', '3' ),
|
||||
( '2', '2', '3', '4' );
|
||||
( '2', '2', '3', '4' ),
|
||||
( '2', '2', '4', '1' );
|
||||
|
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.21.1
|
||||
|
||||
require (
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/google/uuid v1.3.1
|
||||
golang.org/x/term v0.13.0
|
||||
)
|
||||
|
||||
|
2
go.sum
2
go.sum
@ -1,5 +1,7 @@
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
|
56
main.go
56
main.go
@ -1,33 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"streifling.com/jason/sicherheitsunterweisung/packages/data"
|
||||
"streifling.com/jason/sicherheitsunterweisung/packages/db"
|
||||
"streifling.com/jason/sicherheitsunterweisung/packages/server"
|
||||
"streifling.com/jason/sicherheitsunterweisung/packages/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logins := make([]string, 0)
|
||||
func handleParticipants(mux *http.ServeMux, db *db.DB, cp <-chan *types.Participant, s *types.Session) {
|
||||
for participant := range cp {
|
||||
mux.HandleFunc("/submit-participant/"+fmt.Sprint(s.ID)+"/"+fmt.Sprint(participant.Login)+"/", server.HandleParticipant(s, participant, &s.Questions, db))
|
||||
for i := range s.Questions {
|
||||
mux.HandleFunc("/submit-answer/"+fmt.Sprint(s.ID)+"/"+fmt.Sprint(participant.Login)+"/"+fmt.Sprint(i+1)+"/", server.HandleAnswer(s, db, participant, &s.Questions, int64(i+1)))
|
||||
}
|
||||
mux.HandleFunc("/retry/"+fmt.Sprint(s.ID)+"/"+fmt.Sprint(participant.Login)+"/", server.HandleRetry(s, participant, &s.Questions))
|
||||
}
|
||||
}
|
||||
|
||||
db, err := data.OpenDB("sicherheitsunterweisung")
|
||||
func handleSessions(mux *http.ServeMux, db *db.DB, cs <-chan *types.Session, ss *[]*types.Session) {
|
||||
for session := range cs {
|
||||
(*ss) = append(*ss, session)
|
||||
participantChan := make(chan *types.Participant)
|
||||
questionIDs := make([]string, 4)
|
||||
|
||||
for i := 0; i < len(questionIDs); i++ {
|
||||
questionIDs[i] = fmt.Sprint(i + 1)
|
||||
}
|
||||
|
||||
var err error
|
||||
session.Questions, err = db.GetQuestions(questionIDs)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
mux.HandleFunc("/new-briefing/", server.HandleNewBriefing(session))
|
||||
mux.HandleFunc("/new-participant/"+fmt.Sprint(session.ID)+"/", server.HandleNewParticipant(session, participantChan))
|
||||
mux.HandleFunc("/submit-form/"+fmt.Sprint(session.ID)+"/", server.HandleBriefingForm(session, db))
|
||||
|
||||
go handleParticipants(mux, db, participantChan, session)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
db, err := db.Open("sicherheitsunterweisung")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
sessions := make([]*types.Session, 0)
|
||||
sessionChan := make(chan *types.Session)
|
||||
|
||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static/"))))
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
template.Must(template.ParseFiles("templates/index.html", "templates/login.html")).Execute(w, nil)
|
||||
})
|
||||
mux.HandleFunc("/search/", server.DisplaySearchResults(db))
|
||||
mux.HandleFunc("/new-briefing/", server.DisplayInstructorForm())
|
||||
mux.HandleFunc("/add-participant/", server.AddParticipant(&logins))
|
||||
mux.HandleFunc("/submit-form/", server.SubmitBriefingForm(db, &logins))
|
||||
mux.HandleFunc("/internal-login/", server.DisplayTable(db))
|
||||
mux.HandleFunc("/external-login/", server.DisplayParticipantForm(&logins))
|
||||
mux.HandleFunc("/internal-login/", server.HandleInternalLogin(&sessions, sessionChan, db))
|
||||
mux.HandleFunc("/external-login/", server.HandleExternalLogin(&sessions))
|
||||
mux.HandleFunc("/search/", server.HandleSearch(db))
|
||||
|
||||
go handleSessions(mux, db, sessionChan, &sessions)
|
||||
|
||||
log.Fatalln(http.ListenAndServe(":8080", mux))
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
package data
|
||||
|
||||
import "streifling.com/jason/sicherheitsunterweisung/packages/types"
|
||||
|
||||
func InitQuestions() []types.Question {
|
||||
Q := make([]types.Question, 0)
|
||||
|
||||
Q = append(Q, types.Question{
|
||||
Text: "Wie viel ist 1+1?",
|
||||
Answers: []types.Answer{
|
||||
{ID: 0, Text: "1"},
|
||||
{ID: 1, Text: "2"},
|
||||
{ID: 2, Text: "3"},
|
||||
{ID: 3, Text: "4"},
|
||||
},
|
||||
Correct: 1,
|
||||
})
|
||||
|
||||
Q = append(Q, types.Question{
|
||||
Text: "Wie viel ist 2+2?",
|
||||
Answers: []types.Answer{
|
||||
{ID: 0, Text: "1"},
|
||||
{ID: 1, Text: "2"},
|
||||
{ID: 2, Text: "3"},
|
||||
{ID: 3, Text: "4"},
|
||||
},
|
||||
Correct: 3,
|
||||
})
|
||||
|
||||
Q = append(Q, types.Question{
|
||||
Text: "Wie viel ist 1+2?",
|
||||
Answers: []types.Answer{
|
||||
{ID: 0, Text: "1"},
|
||||
{ID: 1, Text: "2"},
|
||||
{ID: 2, Text: "3"},
|
||||
{ID: 3, Text: "4"},
|
||||
},
|
||||
Correct: 2,
|
||||
})
|
||||
|
||||
return Q
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package data
|
||||
package db
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -8,10 +8,9 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"streifling.com/jason/sicherheitsunterweisung/packages/types"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"golang.org/x/term"
|
||||
"streifling.com/jason/sicherheitsunterweisung/packages/types"
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
@ -60,7 +59,7 @@ func getCredentials() (string, string, error) {
|
||||
return user, pass, nil
|
||||
}
|
||||
|
||||
func OpenDB(dbName string) (*DB, error) {
|
||||
func Open(dbName string) (*DB, error) {
|
||||
var err error
|
||||
db := new(DB)
|
||||
|
||||
@ -121,7 +120,7 @@ func (db *DB) WriteParticipant(p *types.Participant) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) WriteGivenAnswer(b *types.Briefing, p *types.Participant, q *types.Question, g *types.GivenAnswer) error {
|
||||
func (db *DB) WriteGivenAnswer(b *types.Briefing, p *types.Participant, q *types.Question, g int) error {
|
||||
_, err := db.Exec(`
|
||||
INSERT INTO given_answers
|
||||
(briefing_id, participant_id, question_id, given_answer)
|
||||
@ -148,14 +147,14 @@ func (db *DB) WriteAllDataOfBriefing(b *types.Briefing, sp *[]*types.Participant
|
||||
|
||||
for _, p := range *sp {
|
||||
for i, q := range *sq {
|
||||
db.WriteGivenAnswer(b, p, q, (*sg)[i])
|
||||
db.WriteGivenAnswer(b, p, q, i)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) GetAllOverviewTableData() (*[]*types.OverviewTableData, error) {
|
||||
func (db *DB) GetAllOverviewTableData() ([]*types.OverviewTableData, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT
|
||||
i.first_name,
|
||||
@ -173,8 +172,12 @@ func (db *DB) GetAllOverviewTableData() (*[]*types.OverviewTableData, error) {
|
||||
ON b.id = g.briefing_id
|
||||
INNER JOIN participants AS p
|
||||
ON p.id = g.participant_id
|
||||
INNER JOIN questions AS q
|
||||
ON q.id = g.question_id
|
||||
INNER JOIN instructors AS i
|
||||
ON i.id = b.instructor_id
|
||||
WHERE
|
||||
q.id = 1
|
||||
ORDER BY
|
||||
b.id DESC,
|
||||
p.id
|
||||
@ -207,7 +210,7 @@ func (db *DB) GetAllOverviewTableData() (*[]*types.OverviewTableData, error) {
|
||||
data = append(data, otd)
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetOverviewTableDataByName(n string) (*[]*types.OverviewTableData, error) {
|
||||
@ -231,6 +234,7 @@ func (db *DB) GetOverviewTableDataByName(n string) (*[]*types.OverviewTableData,
|
||||
INNER JOIN instructors AS i
|
||||
ON i.id = b.instructor_id
|
||||
WHERE
|
||||
q.id = 1 AND
|
||||
i.first_name LIKE ? OR
|
||||
i.last_name LIKE ? OR
|
||||
p.first_name LIKE ? OR
|
||||
@ -287,23 +291,91 @@ func (db *DB) GetLastID(table string) (int, error) {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetInstructors() (*[]*types.Instructor, error) {
|
||||
func (db *DB) GetInstructors() ([]*types.Instructor, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT *
|
||||
FROM instructors
|
||||
ORDER BY
|
||||
last_name,
|
||||
first_name
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("*DB.GetInstructors: db.Query(): %v\n", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
instructors := make([]*types.Instructor, 0)
|
||||
for rows.Next() {
|
||||
instructor := new(types.Instructor)
|
||||
if err = rows.Scan(instructor); err != nil {
|
||||
if err = rows.Scan(&instructor.ID, &instructor.FirstName, &instructor.LastName); err != nil {
|
||||
return nil, fmt.Errorf("*DB.GetInstructors: rows.Scan(): %v\n", err)
|
||||
}
|
||||
instructors = append(instructors, instructor)
|
||||
}
|
||||
|
||||
return &instructors, nil
|
||||
return instructors, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetQuestions(nums []string) ([]types.Question, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT *
|
||||
FROM questions
|
||||
WHERE id IN (` + strings.Join(nums, ", ") + `)
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("*DB.GetQuestions: db.Query(): %v\n", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
questions := make([]types.Question, 0)
|
||||
for rows.Next() {
|
||||
q := new(types.Question)
|
||||
a1 := new(types.Answer)
|
||||
a2 := new(types.Answer)
|
||||
a3 := new(types.Answer)
|
||||
a4 := new(types.Answer)
|
||||
|
||||
a1.ID = 1
|
||||
a2.ID = 2
|
||||
a3.ID = 3
|
||||
a4.ID = 4
|
||||
|
||||
if err := rows.Scan(&q.ID, &q.Text, &a1.Text, &a2.Text, &a3.Text, &a4.Text, &q.Correct); err != nil {
|
||||
return nil, fmt.Errorf("*DB.GetQuestions: rows.Scan(): %v\n", err)
|
||||
}
|
||||
|
||||
q.Answers = append(q.Answers, *a1)
|
||||
q.Answers = append(q.Answers, *a2)
|
||||
q.Answers = append(q.Answers, *a3)
|
||||
q.Answers = append(q.Answers, *a4)
|
||||
|
||||
questions = append(questions, *q)
|
||||
}
|
||||
|
||||
return questions, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetGivenAnswers(bid, pid int64, sq []types.Question) ([]int, error) {
|
||||
answers := make([]int, 0)
|
||||
query := `
|
||||
SELECT given_answer
|
||||
FROM given_answers
|
||||
WHERE
|
||||
briefing_id = ? AND
|
||||
participant_id = ? AND
|
||||
question_id = ?
|
||||
`
|
||||
|
||||
for _, q := range sq {
|
||||
var answer int
|
||||
|
||||
row := db.QueryRow(query, bid, pid, q.ID)
|
||||
if err := row.Scan(&answer); err != nil {
|
||||
return nil, fmt.Errorf("*DB.GetGivenAnswers: row.Scan(): %v\n", err)
|
||||
}
|
||||
|
||||
answers = append(answers, answer)
|
||||
}
|
||||
|
||||
return answers, nil
|
||||
}
|
@ -5,32 +5,50 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"streifling.com/jason/sicherheitsunterweisung/packages/data"
|
||||
"github.com/google/uuid"
|
||||
"streifling.com/jason/sicherheitsunterweisung/packages/db"
|
||||
"streifling.com/jason/sicherheitsunterweisung/packages/types"
|
||||
)
|
||||
|
||||
// type questionData struct {
|
||||
// ID int64
|
||||
// Q types.Question
|
||||
// I int
|
||||
// J int
|
||||
// }
|
||||
func displayTable(w http.ResponseWriter, db *db.DB) {
|
||||
bs, err := db.GetAllOverviewTableData()
|
||||
if err != nil {
|
||||
http.Error(w, "displayTable: *DB.GetAllOverviewTableData(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
}
|
||||
template.Must(template.ParseFiles("templates/table.html")).ExecuteTemplate(w, "content", bs)
|
||||
}
|
||||
|
||||
func DisplayTable(db *data.DB) http.HandlerFunc {
|
||||
func HandleInternalLogin(ss *[]*types.Session, cs chan<- *types.Session, db *db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
bs, err := db.GetAllOverviewTableData()
|
||||
instructors, err := db.GetInstructors()
|
||||
if err != nil {
|
||||
http.Error(w, "DisplayTable: *DB.GetAllOverviewTableData(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
http.Error(w, "HandleInternalLogin: db.GetInstructors(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
log.Panicln(err)
|
||||
}
|
||||
template.Must(template.ParseFiles("templates/table.html")).ExecuteTemplate(w, "content", bs)
|
||||
|
||||
for _, i := range instructors {
|
||||
if r.PostFormValue("login") == fmt.Sprint(i.ID) {
|
||||
session := new(types.Session)
|
||||
session.ID = uuid.New()
|
||||
session.Briefing = new(types.Briefing)
|
||||
session.Briefing.InstructorID = i.ID
|
||||
(*ss) = append((*ss), session)
|
||||
cs <- session
|
||||
|
||||
displayTable(w, db)
|
||||
return
|
||||
}
|
||||
}
|
||||
template.Must(template.ParseFiles("templates/login.html")).ExecuteTemplate(w, "content", nil)
|
||||
}
|
||||
}
|
||||
|
||||
func DisplaySearchResults(db *data.DB) http.HandlerFunc {
|
||||
func HandleSearch(db *db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
bs, err := db.GetOverviewTableDataByName(r.PostFormValue("search"))
|
||||
if err != nil {
|
||||
@ -40,65 +58,88 @@ func DisplaySearchResults(db *data.DB) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func DisplayInstructorForm() http.HandlerFunc {
|
||||
func HandleNewBriefing(s *types.Session) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "content", nil)
|
||||
type httpData struct {
|
||||
SessionID uuid.UUID
|
||||
}
|
||||
|
||||
data := new(httpData)
|
||||
data.SessionID = s.ID
|
||||
|
||||
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "content", data)
|
||||
}
|
||||
}
|
||||
|
||||
func generateUUID() (string, error) {
|
||||
func generateLogin() (string, error) {
|
||||
bs := make([]byte, 4)
|
||||
|
||||
if _, err := rand.Read(bs); err != nil {
|
||||
return "", fmt.Errorf("GenerateUUID: rand.Read(bs): %v\n", err)
|
||||
return "", fmt.Errorf("generateLogin: rand.Read(bs): %v\n", err)
|
||||
}
|
||||
|
||||
return hex.EncodeToString(bs), nil
|
||||
}
|
||||
|
||||
func AddParticipant(sl *[]string) http.HandlerFunc {
|
||||
func HandleNewParticipant(s *types.Session, cp chan<- *types.Participant) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
login, err := generateUUID()
|
||||
if err != nil {
|
||||
http.Error(w, "AddParticipant: generateUUID(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
type httpData struct {
|
||||
SessionID uuid.UUID
|
||||
Login string
|
||||
}
|
||||
|
||||
(*sl) = append(*sl, login)
|
||||
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "new", login)
|
||||
data := new(httpData)
|
||||
var err error
|
||||
|
||||
p := new(types.Participant)
|
||||
p.Login, err = generateLogin()
|
||||
if err != nil {
|
||||
http.Error(w, "AddParticipant: generateLogin(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
}
|
||||
s.Participants = append(s.Participants, p)
|
||||
cp <- p
|
||||
|
||||
data.SessionID = s.ID
|
||||
data.Login = p.Login
|
||||
if err != nil {
|
||||
http.Error(w, "AddParticipant: generateLogin(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "new", data)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Hier weiter machen, irgendwie die b.ID herausgeben,
|
||||
// am besten hier auch die p.IDs rausgeben, damit diese später verknüpft werden können
|
||||
func SubmitBriefingForm(db *data.DB, sl *[]string) http.HandlerFunc {
|
||||
func HandleBriefingForm(s *types.Session, db *db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now()
|
||||
briefing := new(types.Briefing)
|
||||
var err error
|
||||
|
||||
// TODO: Dropdownmenü
|
||||
// instructorFirstName := r.PostFormValue("instructor-first")
|
||||
// instructorLastName := r.PostFormValue("instructor-last")
|
||||
s.Briefing.Date = now.Format("2006-01-02")
|
||||
s.Briefing.Time = now.Format("15:04:05")
|
||||
s.Briefing.Location = r.PostFormValue("location")
|
||||
s.Briefing.DocumentName = r.PostFormValue("document")
|
||||
s.Briefing.AsOf = r.PostFormValue("as-of")
|
||||
|
||||
briefing.Date = now.Format("2006-01-02")
|
||||
briefing.Time = now.Format("15:04:05")
|
||||
briefing.Location = r.PostFormValue("location")
|
||||
briefing.DocumentName = r.PostFormValue("document") // TODO: in HTML einfügen
|
||||
briefing.AsOf = r.PostFormValue("state") // TODO: Umbenennen
|
||||
// briefing.InstructorID = r.PostFormValue("instructor-id") // TODO: aus Dropdown holen
|
||||
err = db.WriteBriefing(s.Briefing)
|
||||
if err != nil {
|
||||
http.Error(w, "SubmitBriefingForm: db.WriteBriefing(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
db.WriteBriefing(briefing)
|
||||
displayTable(w, db)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make it only serve one purpose
|
||||
func loginIsCorrect(l string, logins *[]string) bool {
|
||||
for i, v := range *logins {
|
||||
if l == v {
|
||||
(*logins) = append((*logins)[:i], (*logins)[i+1:]...)
|
||||
return true
|
||||
func findCorrectLogin(l string, ss *[]*types.Session) (*types.Session, *types.Participant, bool) {
|
||||
for _, session := range *ss {
|
||||
for _, p := range session.Participants {
|
||||
if l == p.Login {
|
||||
return session, p, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
func newParticipant(l string) (*types.Participant, error) {
|
||||
@ -113,81 +154,176 @@ func newParticipant(l string) (*types.Participant, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func DisplayParticipantForm(sl *[]string) http.HandlerFunc {
|
||||
func HandleExternalLogin(ss *[]*types.Session) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if loginIsCorrect(r.PostFormValue("login"), sl) {
|
||||
uuid, err := generateUUID()
|
||||
if err != nil {
|
||||
http.Error(w, "DisplayParticipantForm: generateUUID(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
}
|
||||
type httpData struct {
|
||||
SessionID uuid.UUID
|
||||
Login string
|
||||
}
|
||||
|
||||
template.Must(template.ParseFiles("templates/participant.html")).ExecuteTemplate(w, "content", uuid)
|
||||
session, participant, loginCorrect := findCorrectLogin(r.PostFormValue("login"), ss)
|
||||
if loginCorrect {
|
||||
data := new(httpData)
|
||||
data.SessionID = session.ID
|
||||
data.Login = participant.Login
|
||||
|
||||
template.Must(template.ParseFiles("templates/participant.html")).ExecuteTemplate(w, "content", data)
|
||||
} else {
|
||||
template.Must(template.ParseFiles("templates/login.html")).ExecuteTemplate(w, "content", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func readAnswer(r *http.Request, p *types.Participant, i int) error {
|
||||
// v, err := strconv.Atoi(r.PostFormValue("answer"))
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("readAnswer: strconv.Atoi(): %v\n", err)
|
||||
// }
|
||||
//
|
||||
// p.Questions[i].Chosen = v
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// func DisplayQuestion(i int, p *types.Participant) http.HandlerFunc {
|
||||
// return func(w http.ResponseWriter, r *http.Request) {
|
||||
// if i == 0 {
|
||||
// p.FirstName = r.PostFormValue("participant-first-" + fmt.Sprintf("%d", p.ID))
|
||||
// p.LastName = r.PostFormValue("participant-last-" + fmt.Sprintf("%d", p.ID))
|
||||
// p.Company = r.PostFormValue("participant-company-" + fmt.Sprintf("%d", p.ID))
|
||||
// } else {
|
||||
// if err := readAnswer(r, p, i-1); err != nil {
|
||||
// http.Error(w, "DisplayQuestion: readAnswer(r, p, i): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// data := new(questionData)
|
||||
// data.ID = p.ID
|
||||
// data.Q = p.Questions[i]
|
||||
// data.I = i
|
||||
// data.J = i + 1
|
||||
//
|
||||
// template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func DisplayTestResults(b *types.Briefing, p *types.Participant) http.HandlerFunc {
|
||||
// return func(w http.ResponseWriter, r *http.Request) {
|
||||
// numQuestions := len(p.Questions)
|
||||
// wrongAnswers := make([]int, 0)
|
||||
// fmt.Println(wrongAnswers)
|
||||
//
|
||||
// if err := readAnswer(r, p, numQuestions-1); err != nil {
|
||||
// http.Error(w, "DisplayTestResults: readAnswer(r, p, i): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
// }
|
||||
//
|
||||
// for i, q := range p.Questions {
|
||||
// if q.Chosen != q.Correct {
|
||||
// wrongAnswers = append(wrongAnswers, i)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if wrongAnswers == nil {
|
||||
// b.Participants = append(b.Participants, p)
|
||||
// } else {
|
||||
// data := new(questionData)
|
||||
// data.ID = p.ID
|
||||
// data.Q = p.Questions[0]
|
||||
// data.I = 0
|
||||
// data.J = data.I + 1
|
||||
// template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
|
||||
// }
|
||||
//
|
||||
// template.Must(template.ParseFiles("templates/results.html")).ExecuteTemplate(w, "content", nil)
|
||||
// }
|
||||
// }
|
||||
func handleGivenAnswer(s *types.Session, p *types.Participant, i int64, r *http.Request, db *db.DB) error {
|
||||
answer, err := strconv.Atoi(r.PostFormValue("answer"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("handleGivenAnswer: strconv.Atoi(): %v\n", err)
|
||||
}
|
||||
|
||||
if err := db.WriteGivenAnswer(s.Briefing, p, &s.Questions[i], answer); err != nil {
|
||||
return fmt.Errorf("handleGivenAnswer: db.WriteGivenAnswer(): %v\n", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func HandleParticipant(s *types.Session, p *types.Participant, sq *[]types.Question, db *db.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
type httpData struct {
|
||||
SessionID uuid.UUID
|
||||
Login string
|
||||
Question types.Question
|
||||
QuestionID int64
|
||||
}
|
||||
|
||||
p.FirstName = r.PostFormValue("first-" + fmt.Sprint(p.Login))
|
||||
p.LastName = r.PostFormValue("last-" + fmt.Sprint(p.Login))
|
||||
p.Company = r.PostFormValue("company-" + fmt.Sprint(p.Login))
|
||||
|
||||
err := db.WriteParticipant(p)
|
||||
if err != nil {
|
||||
http.Error(w, "DisplayQuestion: db.WriteParticipant(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
data := new(httpData)
|
||||
data.SessionID = s.ID
|
||||
data.Login = p.Login
|
||||
data.Question = (*sq)[0]
|
||||
data.QuestionID = 1
|
||||
|
||||
template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleAnswer(s *types.Session, db *db.DB, p *types.Participant, sq *[]types.Question, i int64) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println(i, len(*sq))
|
||||
if i < int64(len(*sq)) {
|
||||
type httpData struct {
|
||||
SessionID uuid.UUID
|
||||
Login string
|
||||
Question types.Question
|
||||
QuestionID int64
|
||||
}
|
||||
|
||||
if err := handleGivenAnswer(s, p, i-1, r, db); err != nil {
|
||||
http.Error(w, "DisplayQuestion: handleGivenAnswer(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
data := new(httpData)
|
||||
data.SessionID = s.ID
|
||||
data.Login = p.Login
|
||||
data.Question = (*sq)[i]
|
||||
data.QuestionID = i + 1
|
||||
|
||||
template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
|
||||
} else {
|
||||
type answer struct {
|
||||
Text string
|
||||
Correct bool
|
||||
Chosen bool
|
||||
}
|
||||
|
||||
type question struct {
|
||||
Text string
|
||||
Answers []answer
|
||||
}
|
||||
|
||||
type httpData struct {
|
||||
SessionID uuid.UUID
|
||||
Login string
|
||||
Questions []question
|
||||
Incorrect int
|
||||
}
|
||||
|
||||
if err := handleGivenAnswer(s, p, i-1, r, db); err != nil {
|
||||
http.Error(w, "DisplayTestResults: handleGivenAnswer(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
givenAnswers, err := db.GetGivenAnswers(s.Briefing.ID, p.ID, s.Questions)
|
||||
if err != nil {
|
||||
http.Error(w, "DisplayTestResults: db.GetGivenAnswers(): "+fmt.Sprint(err), http.StatusInternalServerError)
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
data := new(httpData)
|
||||
data.SessionID = s.ID
|
||||
data.Login = p.Login
|
||||
data.Incorrect = 0
|
||||
|
||||
data.Questions = make([]question, 0)
|
||||
for i, q := range s.Questions {
|
||||
question := new(question)
|
||||
question.Text = q.Text
|
||||
|
||||
question.Answers = make([]answer, 0)
|
||||
for j, a := range q.Answers {
|
||||
answer := new(answer)
|
||||
answer.Text = a.Text
|
||||
|
||||
if j+1 == q.Correct {
|
||||
answer.Correct = true
|
||||
} else {
|
||||
answer.Correct = false
|
||||
}
|
||||
|
||||
if j+1 == givenAnswers[i] {
|
||||
answer.Chosen = true
|
||||
} else {
|
||||
answer.Chosen = false
|
||||
}
|
||||
question.Answers = append(question.Answers, *answer)
|
||||
}
|
||||
data.Questions = append(data.Questions, *question)
|
||||
|
||||
if givenAnswers[i] != q.Correct {
|
||||
data.Incorrect++
|
||||
}
|
||||
}
|
||||
|
||||
template.Must(template.ParseFiles("templates/results.html")).ExecuteTemplate(w, "content", data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HandleRetry(s *types.Session, p *types.Participant, sq *[]types.Question) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
type httpData struct {
|
||||
SessionID uuid.UUID
|
||||
Login string
|
||||
Question types.Question
|
||||
QuestionID int64
|
||||
}
|
||||
|
||||
data := new(httpData)
|
||||
data.SessionID = s.ID
|
||||
data.Login = p.Login
|
||||
data.Question = (*sq)[0]
|
||||
data.QuestionID = 1
|
||||
|
||||
template.Must(template.ParseFiles("templates/question.html")).ExecuteTemplate(w, "content", data)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package types
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type Person struct {
|
||||
ID int64
|
||||
FirstName string
|
||||
@ -11,6 +13,7 @@ type Instructor Person
|
||||
type Participant struct {
|
||||
Person
|
||||
Company string
|
||||
Login string
|
||||
}
|
||||
|
||||
type Briefing struct {
|
||||
@ -54,3 +57,10 @@ type OverviewTableData struct {
|
||||
ParticipantLastName string
|
||||
ParticipantCompany string
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
ID uuid.UUID
|
||||
*Briefing
|
||||
Participants []*Participant
|
||||
Questions []Question
|
||||
}
|
||||
|
7
static/css/style.css
Normal file
7
static/css/style.css
Normal file
@ -0,0 +1,7 @@
|
||||
.correct {
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.incorrect {
|
||||
color: #ff0000;
|
||||
}
|
@ -1,38 +1,37 @@
|
||||
{{ define "add-button" }}
|
||||
<button type="button" hx-post="/add-participant/" hx-target="this" hx-swap="outerHTML">
|
||||
Neuer Teilnehmer
|
||||
</button>
|
||||
{{ define "add-buttons" }}
|
||||
<div id="briefing-buttons">
|
||||
<button type="button" hx-post="/new-participant/{{ .SessionID }}/" hx-target="#briefing-buttons" hx-swap="outerHTML">
|
||||
Neuer Teilnehmer
|
||||
</button>
|
||||
|
||||
<button type="submit" hx-post="/submit-form/{{ .SessionID }}/" hx-target="#content" hx-swap="innerHTML">
|
||||
Fertig
|
||||
</button>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ define "new" }}
|
||||
<span>{{ . }}</span>
|
||||
{{ template "add-button" . }}
|
||||
{{ template "add-buttons" . }}
|
||||
<p>{{ .Login }}</p>
|
||||
{{ end }}
|
||||
|
||||
{{ define "content" }}
|
||||
<form>
|
||||
<div id="instructor">
|
||||
<label for="instructor-first-input">Unterweiser Vorname</label>
|
||||
<input type="text" name="instructor-first" id="instructor-first-input" />
|
||||
|
||||
<label for="instructor-last-input">Unterweiser Nachname</label>
|
||||
<input type="text" name="instructor-last" id="instructor-last-input" />
|
||||
<div>
|
||||
<label for="location">Ort</label>
|
||||
<input id="location" name="location" required type="text" />
|
||||
</div>
|
||||
|
||||
<div id="location">
|
||||
<label for="location-input">Ort</label>
|
||||
<input type="text" name="location" id="location-input" />
|
||||
<div>
|
||||
<label for="document">Dokument</label>
|
||||
<input id="document" name="document" required type="text" />
|
||||
</div>
|
||||
|
||||
<div id="state">
|
||||
<label for="state-input">Stand vom</label>
|
||||
<input type="date" name="state" id="state-input" />
|
||||
<div>
|
||||
<label for="as-of">Stand vom</label>
|
||||
<input id="as-of" name="as-of" required type="date" />
|
||||
</div>
|
||||
|
||||
{{ template "add-button" . }}
|
||||
|
||||
<button type="submit" hx-post="/submit-form/" hx-target="#content" hx-swap="innerHTML">
|
||||
OK
|
||||
</button>
|
||||
{{ template "add-buttons" . }}
|
||||
</form>
|
||||
{{ end }}
|
||||
|
@ -1,16 +1,17 @@
|
||||
{{ define "content" }}
|
||||
<h2>Login</h2>
|
||||
<h2>Anmeldung</h2>
|
||||
|
||||
<form>
|
||||
<label for="login-input">Code</label>
|
||||
<input type="text" name="login" id="login-input" />
|
||||
<input autocomplete="off" id="login-input" name="login" placeholder="Code" required type="text" />
|
||||
|
||||
<button type="submit" hx-post="/external-login/" hx-target="#content">
|
||||
Anmelden
|
||||
</button>
|
||||
<div>
|
||||
<button type="submit" hx-post="/internal-login/" hx-target="#content">
|
||||
Intern
|
||||
</button>
|
||||
|
||||
<button type="submit" hx-post="/internal-login/" hx-target="#content">
|
||||
Intern
|
||||
</button>
|
||||
<button type="submit" hx-post="/external-login/" hx-target="#content">
|
||||
Gast
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
|
@ -1,15 +1,15 @@
|
||||
{{ define "content" }}
|
||||
<form id="participant-{{ . }}">
|
||||
<label for="participant-first-input-{{ . }}">Vorname</label>
|
||||
<input type="text" name="participant-first-{{ . }}" id="participant-first-input-{{ . }}" />
|
||||
<form>
|
||||
<label for="first-{{ .Login }}">Vorname</label>
|
||||
<input type="text" name="first-{{ .Login }}" id="first-{{ .Login }}" />
|
||||
|
||||
<label for="participant-last-input-{{ . }}">Nachname</label>
|
||||
<input type="text" name="participant-last-{{ . }}" id="participant-last-input-{{ . }}" />
|
||||
<label for="last-{{ .Login }}">Nachname</label>
|
||||
<input type="text" name="last-{{ .Login }}" id="last-{{ .Login }}" />
|
||||
|
||||
<label for="participant-company-input-{{ . }}">Firma</label>
|
||||
<input type="text" name="participant-company-{{ . }}" id="participant-company-input-{{ . }}" />
|
||||
<label for="company-{{ .Login }}">Firma</label>
|
||||
<input type="text" name="company-{{ .Login }}" id="company-{{ .Login }}" />
|
||||
|
||||
<button type="button" hx-post="/display-question-{{ . }}-0/" hx-target="#content">
|
||||
<button type="button" hx-post="/submit-participant/{{ .SessionID }}/{{ .Login }}/" hx-target="#content">
|
||||
Fertig
|
||||
</button>
|
||||
</form>
|
||||
|
@ -1,21 +1,21 @@
|
||||
{{ define "answers" }}
|
||||
{{ range .Q.Answers }}
|
||||
{{define "answers"}}
|
||||
{{range .Question.Answers}}
|
||||
<div>
|
||||
<input type="radio" name="answer" id="answer-{{ .ID }}" value="{{ .ID }}" />
|
||||
<label for="answer-{{ .ID }}">{{ .Text }}</label>
|
||||
<input type="radio" name="answer" id="answer-{{.ID}}" value="{{.ID}}" />
|
||||
<label for="answer-{{.ID}}">{{.Text}}</label>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ define "content" }}
|
||||
<h2>Frage {{ .I }}</h2>
|
||||
<p>{{ .Q.Text }}</p>
|
||||
{{define "content"}}
|
||||
<h2>Frage {{.QuestionID}}</h2>
|
||||
<p>{{.Question.Text}}</p>
|
||||
|
||||
<form>
|
||||
{{ template "answers" . }}
|
||||
{{template "answers" .}}
|
||||
|
||||
<button type="submit" hx-post="/display-question-{{ .ID }}-{{ .J }}/" hx-target="#content" hx-swap="innerHTML">
|
||||
<button hx-post="/submit-answer/{{.SessionID}}/{{.Login}}/{{.QuestionID}}/" hx-target="#content" type="submit">
|
||||
Weiter
|
||||
</button>
|
||||
</form>
|
||||
{{ end }}
|
||||
{{end}}
|
||||
|
@ -1,11 +1,18 @@
|
||||
{{ define "passed" }}
|
||||
{{ end }}
|
||||
{{define "answers"}}
|
||||
{{range .Answers}}
|
||||
<p class="{{if and .Chosen .Correct}} correct {{else if and .Chosen (not .Correct)}} incorrect {{end}}">
|
||||
{{.Text}}
|
||||
</p>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{ define "failed" }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "content" }}
|
||||
{{ q := range .Participant.Questions }}
|
||||
<p>{{ . }}{{ q.Text }}</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{define "content"}}
|
||||
<p>{{.Incorrect}} Fehler</p>
|
||||
{{range .Questions}}
|
||||
<p>{{.Text}}</p>
|
||||
{{template "answers" .}}
|
||||
{{end}}
|
||||
{{if gt .Incorrect 0}}
|
||||
<button hx-post="/retry/{{.SessionID}}/{{.Login}}/" hx-target="#content" type="submit">Wiederholen</button>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
@ -1,17 +1,16 @@
|
||||
{{ define "rows" }}
|
||||
{{ range . }}
|
||||
<tr>
|
||||
<td>{{ .FirstName }}</td>
|
||||
<td>{{ .LastName }}</td>
|
||||
<td>{{ .Date }}</td>
|
||||
<td>{{ .Time }}</td>
|
||||
<td>{{ .State }}</td>
|
||||
<td>{{ .Location }}</td>
|
||||
{{ range .Participants }}
|
||||
<td>{{ .FirstName }}</td>
|
||||
<td>{{ .LastName }}</td>
|
||||
<td>{{ .Company }}</td>
|
||||
{{ end }}
|
||||
<td>{{ .InstructorFirstName }}</td>
|
||||
<td>{{ .InstructorLastName }}</td>
|
||||
<td>{{ .BriefingDate }}</td>
|
||||
<td>{{ .BriefingTime }}</td>
|
||||
<td>{{ .BriefingLocation }}</td>
|
||||
<td>{{ .BriefingDocumentName }}</td>
|
||||
<td>{{ .BriefingAsOf }}</td>
|
||||
<td>{{ .ParticipantFirstName }}</td>
|
||||
<td>{{ .ParticipantLastName }}</td>
|
||||
<td>{{ .ParticipantCompany }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@ -35,8 +34,9 @@
|
||||
<th colspan="2">Unterweiser</th>
|
||||
<th>Datum</th>
|
||||
<th>Uhrzeit</th>
|
||||
<th>Stand</th>
|
||||
<th>Ort</th>
|
||||
<th>Dokument</th>
|
||||
<th>Stand</th>
|
||||
<th colspan="2">Teilnehmer</th>
|
||||
<th>Firma</th>
|
||||
</tr>
|
||||
|
@ -1 +0,0 @@
|
||||
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
Reference in New Issue
Block a user