Compare commits
	
		
			45 Commits
		
	
	
		
			fdf68adb0d
			...
			online_que
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5019432b24 | |||
| 39d8108521 | |||
| 82ced65513 | |||
| b605217625 | |||
| db070776b1 | |||
| 3e9cfb49eb | |||
| b42f739581 | |||
| c22647edd9 | |||
| 9bdc6e9f43 | |||
| b17fa1edc7 | |||
| c69bfdfab2 | |||
| c38d3131c6 | |||
| e4d2f9ae3e | |||
| 523fee3ff2 | |||
| 230d79c675 | |||
| 664c24974b | |||
| 7144489afb | |||
| 76f1fe9588 | |||
| 726c8b6dcb | |||
| 608879d008 | |||
| 616df72041 | |||
| 1597d38d34 | |||
| 8dbb5f946d | |||
| 7c7cb5959d | |||
| 2beb90a345 | |||
| f80dca4b10 | |||
| bf05bc0be7 | |||
| f6a073fc39 | |||
| 82870e100f | |||
| 2b119f6752 | |||
| c04932383e | |||
| 8ea0c2964a | |||
| 519dc82023 | |||
| 324a1c54d6 | |||
| 15675d5e6c | |||
| 8ae3019b9c | |||
| b13eba8008 | |||
| da77201a93 | |||
| 9acc6711fc | |||
| f570950425 | |||
| 5749739761 | |||
| 1bcfbfd325 | |||
| fcb509c9fe | |||
| 1c39b1e471 | |||
| 71bf8978ac | 
							
								
								
									
										44
									
								
								.air.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								.air.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| root = "." | ||||
| testdata_dir = "testdata" | ||||
| tmp_dir = "tmp" | ||||
|  | ||||
| [build] | ||||
|   args_bin = [] | ||||
|   bin = "./tmp/main" | ||||
|   cmd = "go build -o ./tmp/main ." | ||||
|   delay = 0 | ||||
|   exclude_dir = ["assets", "tmp", "vendor", "testdata"] | ||||
|   exclude_file = [] | ||||
|   exclude_regex = ["_test.go"] | ||||
|   exclude_unchanged = false | ||||
|   follow_symlink = false | ||||
|   full_bin = "" | ||||
|   include_dir = [] | ||||
|   include_ext = ["go", "tpl", "tmpl", "html"] | ||||
|   include_file = [] | ||||
|   kill_delay = "0s" | ||||
|   log = "build-errors.log" | ||||
|   poll = false | ||||
|   poll_interval = 0 | ||||
|   rerun = false | ||||
|   rerun_delay = 500 | ||||
|   send_interrupt = false | ||||
|   stop_on_error = false | ||||
|  | ||||
| [color] | ||||
|   app = "" | ||||
|   build = "yellow" | ||||
|   main = "magenta" | ||||
|   runner = "green" | ||||
|   watcher = "cyan" | ||||
|  | ||||
| [log] | ||||
|   main_only = false | ||||
|   time = false | ||||
|  | ||||
| [misc] | ||||
|   clean_on_exit = false | ||||
|  | ||||
| [screen] | ||||
|   clear_on_rebuild = false | ||||
|   keep_scroll = true | ||||
							
								
								
									
										0
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
								
								
									
										97
									
								
								create_tables.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								create_tables.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| USE sicherheitsunterweisung; | ||||
| 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; | ||||
|  | ||||
| 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, | ||||
|  | ||||
|   PRIMARY KEY(id) | ||||
| ); | ||||
|  | ||||
| CREATE TABLE briefings ( | ||||
|   id            INT         NOT NULL AUTO_INCREMENT, | ||||
|   date          DATE        NOT NULL, | ||||
|   time          TIME        NOT NULL, | ||||
|   location      VARCHAR(32) NOT NULL, | ||||
|   document_name VARCHAR(16) NOT NULL, | ||||
|   as_of         DATE        NOT NULL, | ||||
|   instructor_id INT         NOT NULL, | ||||
|  | ||||
|   PRIMARY KEY(id), | ||||
|   FOREIGN KEY(instructor_id) REFERENCES instructors(id) | ||||
| ); | ||||
|  | ||||
| CREATE TABLE participants ( | ||||
|   id         INT         NOT NULL AUTO_INCREMENT, | ||||
|   first_name VARCHAR(32) NOT NULL, | ||||
|   last_name  VARCHAR(32) NOT NULL, | ||||
|   company    VARCHAR(32) NOT NULL, | ||||
|  | ||||
|   PRIMARY KEY(id) | ||||
| ); | ||||
|  | ||||
| CREATE TABLE questions ( | ||||
|   id             INT          NOT NULL AUTO_INCREMENT, | ||||
|   question       VARCHAR(256) NOT NULL, | ||||
|   answer_1       VARCHAR(64)  NOT NULL, | ||||
|   answer_2       VARCHAR(64)  NOT NULL, | ||||
|   answer_3       VARCHAR(64)  NOT NULL, | ||||
|   answer_4       VARCHAR(64)  NOT NULL, | ||||
|   correct_answer INT          NOT NULL, | ||||
|  | ||||
|   PRIMARY KEY(id) | ||||
| ); | ||||
|  | ||||
| CREATE TABLE given_answers ( | ||||
|   briefing_id    INT NOT NULL, | ||||
|   participant_id INT NOT NULL, | ||||
|   question_id    INT NOT NULL, | ||||
|   given_answer   INT NOT NULL, | ||||
|  | ||||
|   PRIMARY KEY(briefing_id, participant_id, question_id), | ||||
|   FOREIGN KEY(briefing_id)    REFERENCES briefings(id), | ||||
|   FOREIGN KEY(participant_id) REFERENCES participants(id), | ||||
|   FOREIGN KEY(question_id)    REFERENCES questions(id) | ||||
| ); | ||||
|  | ||||
| INSERT INTO instructors | ||||
|   (first_name, last_name, personnel_id) | ||||
| VALUES | ||||
|   ( 'Jason', 'Streifling',     '123456' ), | ||||
|   ( 'Tim',   'Taler',          '123457' ), | ||||
|   ( 'Georg', 'aus dem Jungel', '123458' ); | ||||
|  | ||||
| INSERT INTO briefings ( | ||||
|   date, time, location, 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' ); | ||||
|  | ||||
| INSERT INTO participants ( | ||||
|   first_name, last_name, company | ||||
| ) VALUES | ||||
|   ( 'Peter', 'Enis',  'Körber'     ), | ||||
|   ( 'Dürüm', 'Döner', 'MP Technic' ); | ||||
|  | ||||
| INSERT INTO questions ( | ||||
|   question, answer_1, answer_2, answer_3, answer_4, correct_answer | ||||
| ) 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' ); | ||||
|  | ||||
| INSERT INTO given_answers ( | ||||
|   briefing_id, participant_id, question_id, given_answer | ||||
| ) VALUES | ||||
|   ( '1', '1', '1', '2' ), | ||||
|   ( '1', '1', '2', '3' ), | ||||
|   ( '1', '1', '3', '3' ), | ||||
|   ( '2', '2', '1', '2' ), | ||||
|   ( '2', '2', '2', '3' ), | ||||
|   ( '2', '2', '3', '4' ); | ||||
							
								
								
									
										52
									
								
								db.go
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								db.go
									
									
									
									
									
								
							| @@ -1,52 +0,0 @@ | ||||
| package sicherheitsunterweisung | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"database/sql" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
|  | ||||
| 	"github.com/go-sql-driver/mysql" | ||||
| 	"golang.org/x/term" | ||||
| ) | ||||
|  | ||||
| func getCredentials() (string, string, error) { | ||||
| 	fmt.Printf("DB Benutzer: ") | ||||
| 	user, err := bufio.NewReader(os.Stdin).ReadString('\n') | ||||
| 	if err != nil { | ||||
| 		return "", "", fmt.Errorf("getCredentials: bufio.NewReader(os.Stdin).ReadString('\n'): %v", err) | ||||
| 	} | ||||
|  | ||||
| 	fmt.Printf("DB Passwort: ") | ||||
| 	bytePass, err := term.ReadPassword(int(syscall.Stdin)) | ||||
| 	if err != nil { | ||||
| 		return "", "", fmt.Errorf("getCredentials: term.ReadPassword(int(syscall.Stdin)): %v", err) | ||||
| 	} | ||||
| 	fmt.Println() | ||||
| 	pass := string(bytePass) | ||||
|  | ||||
| 	return strings.TrimSpace(user), strings.TrimSpace(pass), nil | ||||
| } | ||||
|  | ||||
| func OpenDB(dbName string) (*sql.DB, error) { | ||||
| 	var err error | ||||
|  | ||||
| 	cfg := mysql.NewConfig() | ||||
| 	cfg.DBName = dbName | ||||
| 	cfg.User, cfg.Passwd, err = getCredentials() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("OpenDB: getCredentials(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	db, err := sql.Open("mysql", cfg.FormatDSN()) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("newDB: sql.Open(\"mysql\", cfg.FormatDSN()): %v\n", err) | ||||
| 	} | ||||
| 	if err := db.Ping(); err != nil { | ||||
| 		return nil, fmt.Errorf("newDB: db.Ping(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	return db, nil | ||||
| } | ||||
							
								
								
									
										68
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								main.go
									
									
									
									
									
								
							| @@ -1,73 +1,33 @@ | ||||
| package sicherheitsunterweisung | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"streifling.com/jason/sicherheitsunterweisung/packages/data" | ||||
| 	"streifling.com/jason/sicherheitsunterweisung/packages/server" | ||||
| ) | ||||
|  | ||||
| type Person struct { | ||||
| 	FirstName string | ||||
| 	LastName  string | ||||
| } | ||||
|  | ||||
| type Instructor Person | ||||
|  | ||||
| type Participant struct { | ||||
| 	ID int64 | ||||
| 	Person | ||||
| 	Company string | ||||
| } | ||||
|  | ||||
| type Briefing struct { | ||||
| 	Instructor | ||||
| 	Date         string | ||||
| 	Time         string | ||||
| 	State        string | ||||
| 	Location     string | ||||
| 	Participants []Participant | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	var i, j int64 | ||||
| 	var b Briefing | ||||
| 	logins := make([]string, 0) | ||||
|  | ||||
| 	i, j = 1, 1 | ||||
| 	mux := http.NewServeMux() | ||||
|  | ||||
| 	db, err := OpenDB("sicherheitsunterweisung") | ||||
| 	db, err := data.OpenDB("sicherheitsunterweisung") | ||||
| 	if err != nil { | ||||
| 		log.Fatalln(err) | ||||
| 	} | ||||
|  | ||||
| 	mux := http.NewServeMux() | ||||
| 	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")).Execute(w, i) | ||||
| 	}) | ||||
| 	mux.HandleFunc("/add-participant/", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		i++ | ||||
| 		template.Must(template.ParseFiles("templates/index.html", "templates/participant.html")).ExecuteTemplate(w, "participant", i) | ||||
| 	}) | ||||
| 	mux.HandleFunc("/submit/", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		b.Instructor.FirstName = r.PostFormValue("instructor-first") | ||||
| 		b.Instructor.LastName = r.PostFormValue("instructor-last") | ||||
| 		b.Date = r.PostFormValue("date") | ||||
| 		b.Time = r.PostFormValue("time") | ||||
| 		b.State = r.PostFormValue("state") | ||||
| 		b.Location = r.PostFormValue("location") | ||||
|  | ||||
| 		for ; j <= i; j++ { | ||||
| 			b.Participants = append(b.Participants, Participant{ | ||||
| 				ID: j, | ||||
| 				Person: Person{ | ||||
| 					FirstName: r.PostFormValue("participant-first-" + fmt.Sprint(j)), | ||||
| 					LastName:  r.PostFormValue(("participant-last-" + fmt.Sprint(j))), | ||||
| 				}, | ||||
| 				Company: r.PostFormValue(("participant-company-" + fmt.Sprint(j))), | ||||
| 			}) | ||||
| 		} | ||||
| 		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)) | ||||
|  | ||||
| 	log.Fatalln(http.ListenAndServe(":8080", mux)) | ||||
| } | ||||
|   | ||||
							
								
								
									
										309
									
								
								packages/data/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								packages/data/db.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,309 @@ | ||||
| package data | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"database/sql" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
|  | ||||
| 	"streifling.com/jason/sicherheitsunterweisung/packages/types" | ||||
|  | ||||
| 	"github.com/go-sql-driver/mysql" | ||||
| 	"golang.org/x/term" | ||||
| ) | ||||
|  | ||||
| type DB struct { | ||||
| 	*sql.DB | ||||
| 	Name string | ||||
| } | ||||
|  | ||||
| func getUsername() (string, error) { | ||||
| 	user := os.Getenv("DB_USER") | ||||
| 	if user == "" { | ||||
| 		var err error | ||||
| 		fmt.Printf("DB Benutzer: ") | ||||
| 		user, err = bufio.NewReader(os.Stdin).ReadString('\n') | ||||
| 		if err != nil { | ||||
| 			return "", fmt.Errorf("getUsername: bufio.NewReader(os.Stdin).ReadString('\n'): %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 	return strings.TrimSpace(user), nil | ||||
| } | ||||
|  | ||||
| func getPassword() (string, error) { | ||||
| 	pass := os.Getenv("DB_PASS") | ||||
| 	if pass == "" { | ||||
| 		fmt.Printf("DB Passwort: ") | ||||
| 		bytePass, err := term.ReadPassword(int(syscall.Stdin)) | ||||
| 		if err != nil { | ||||
| 			return "", fmt.Errorf("getCredentials: term.ReadPassword(int(syscall.Stdin)): %v", err) | ||||
| 		} | ||||
| 		fmt.Println() | ||||
| 		pass = strings.TrimSpace(string(bytePass)) | ||||
| 	} | ||||
| 	return pass, nil | ||||
| } | ||||
|  | ||||
| func getCredentials() (string, string, error) { | ||||
| 	user, err := getUsername() | ||||
| 	if err != nil { | ||||
| 		return "", "", fmt.Errorf("getCredentials: getUsername(): %v", err) | ||||
| 	} | ||||
|  | ||||
| 	pass, err := getPassword() | ||||
| 	if err != nil { | ||||
| 		return "", "", fmt.Errorf("getCredentials: getPassword(): %v", err) | ||||
| 	} | ||||
|  | ||||
| 	return user, pass, nil | ||||
| } | ||||
|  | ||||
| func OpenDB(dbName string) (*DB, error) { | ||||
| 	var err error | ||||
| 	db := new(DB) | ||||
|  | ||||
| 	cfg := mysql.NewConfig() | ||||
| 	cfg.DBName = dbName | ||||
| 	cfg.User, cfg.Passwd, err = getCredentials() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("Open: getCredentials(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	db.Name = dbName | ||||
| 	db.DB, err = sql.Open("mysql", cfg.FormatDSN()) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("Open: sql.Open(\"mysql\", cfg.FormatDSN()): %v\n", err) | ||||
| 	} | ||||
| 	if err := db.Ping(); err != nil { | ||||
| 		return nil, fmt.Errorf("Open: db.Ping(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	return db, nil | ||||
| } | ||||
|  | ||||
| func (db *DB) WriteBriefing(b *types.Briefing) error { | ||||
| 	result, err := db.Exec(` | ||||
|   INSERT INTO briefings | ||||
|   	(date, time, location, document_name, as_of, instructor_id) | ||||
|   VALUES | ||||
|   	(?, ?, ?, ?, ?, ?) | ||||
|   `, b.Date, b.Time, b.Location, b.DocumentName, b.AsOf, b.InstructorID) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("*DB.writeBriefing: db.Exec(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	b.ID, err = result.LastInsertId() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("*DB.writeBriefing: result.LastInsertId(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (db *DB) WriteParticipant(p *types.Participant) error { | ||||
| 	result, err := db.Exec(` | ||||
|   INSERT INTO participants | ||||
|   	(first_name, last_name, company) | ||||
|   VALUES | ||||
|   	(?, ?, ?) | ||||
|   `, p.FirstName, p.LastName, p.Company) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("*DB.writeParticipants: db.Exec(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	p.ID, err = result.LastInsertId() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("*DB.writeParticipants: result.LastInsertId(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (db *DB) WriteGivenAnswer(b *types.Briefing, p *types.Participant, q *types.Question, g *types.GivenAnswer) error { | ||||
| 	_, err := db.Exec(` | ||||
|   INSERT INTO given_answers | ||||
|   	(briefing_id, participant_id, question_id, given_answer) | ||||
|   VALUES | ||||
|   	(?, ?, ?, ?) | ||||
|   `, b.ID, p.ID, q.ID, g) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("*DB.writeGivenAnswers: db.Exec(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (db *DB) WriteAllDataOfBriefing(b *types.Briefing, sp *[]*types.Participant, sq *[]*types.Question, sg *[]*types.GivenAnswer) error { | ||||
| 	if err := db.WriteBriefing(b); err != nil { | ||||
| 		return fmt.Errorf("*DB.WriteAllDataOfBriefing: db.writeBriefing(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	for _, p := range *sp { | ||||
| 		if err := db.WriteParticipant(p); err != nil { | ||||
| 			return fmt.Errorf("*DB.WriteAllDataOfBriefing: db.writeParticipants(): %v\n", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, p := range *sp { | ||||
| 		for i, q := range *sq { | ||||
| 			db.WriteGivenAnswer(b, p, q, (*sg)[i]) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (db *DB) GetAllOverviewTableData() (*[]*types.OverviewTableData, error) { | ||||
| 	rows, err := db.Query(` | ||||
|   SELECT | ||||
|     i.first_name, | ||||
|     i.last_name, | ||||
|     b.date, | ||||
|     b.time, | ||||
|     b.location, | ||||
|     b.document_name, | ||||
|     b.as_of, | ||||
|     p.first_name, | ||||
|     p.last_name, | ||||
|     p.company | ||||
|   FROM given_answers AS g | ||||
|     INNER JOIN briefings AS b | ||||
|       ON b.id = g.briefing_id | ||||
|     INNER JOIN participants AS p | ||||
|       ON p.id = g.participant_id | ||||
|     INNER JOIN instructors AS i | ||||
|       ON i.id = b.instructor_id | ||||
|   ORDER BY | ||||
|     b.id DESC, | ||||
|     p.id | ||||
|   `) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("*DB.ReadAllBriefings: db.Query(): %v\n", err) | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
|  | ||||
| 	data := make([]*types.OverviewTableData, 0) | ||||
| 	for rows.Next() { | ||||
| 		otd := new(types.OverviewTableData) | ||||
|  | ||||
| 		err := rows.Scan( | ||||
| 			&otd.InstructorFirstName, | ||||
| 			&otd.InstructorLastName, | ||||
| 			&otd.BriefingDate, | ||||
| 			&otd.BriefingTime, | ||||
| 			&otd.BriefingLocation, | ||||
| 			&otd.BriefingDocumentName, | ||||
| 			&otd.BriefingAsOf, | ||||
| 			&otd.ParticipantFirstName, | ||||
| 			&otd.ParticipantLastName, | ||||
| 			&otd.ParticipantCompany, | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("*DB.ReadAllBriefings: rows.Scan(): %v\n", err) | ||||
| 		} | ||||
|  | ||||
| 		data = append(data, otd) | ||||
| 	} | ||||
|  | ||||
| 	return &data, nil | ||||
| } | ||||
|  | ||||
| func (db *DB) GetOverviewTableDataByName(n string) (*[]*types.OverviewTableData, error) { | ||||
| 	rows, err := db.Query(` | ||||
|   SELECT | ||||
|     i.first_name, | ||||
|     i.last_name, | ||||
|     b.date, | ||||
|     b.time, | ||||
|     b.location, | ||||
|     b.document_name, | ||||
|     b.as_of, | ||||
|     p.first_name, | ||||
|     p.last_name, | ||||
|     p.company | ||||
|   FROM given_answers AS g | ||||
|     INNER JOIN briefings AS b | ||||
|       ON b.id = g.briefing_id | ||||
|     INNER JOIN participants AS p | ||||
|       ON p.id = g.participant_id | ||||
|     INNER JOIN instructors AS i | ||||
|       ON i.id = b.instructor_id | ||||
|   WHERE | ||||
|     i.first_name LIKE ? OR | ||||
|     i.last_name LIKE ? OR | ||||
|     p.first_name LIKE ? OR | ||||
|     p.last_name LIKE ? | ||||
|   ORDER BY | ||||
|     b.id DESC, | ||||
|     p.id | ||||
|   `, "%"+n+"%", "%"+n+"%", "%"+n+"%", "%"+n+"%") | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("*DB.GetOverviewTableDataByName: db.Query(): %v\n", err) | ||||
| 	} | ||||
| 	defer rows.Close() | ||||
|  | ||||
| 	data := make([]*types.OverviewTableData, 0) | ||||
| 	for rows.Next() { | ||||
| 		otd := new(types.OverviewTableData) | ||||
|  | ||||
| 		err := rows.Scan( | ||||
| 			&otd.InstructorFirstName, | ||||
| 			&otd.InstructorLastName, | ||||
| 			&otd.BriefingDate, | ||||
| 			&otd.BriefingTime, | ||||
| 			&otd.BriefingLocation, | ||||
| 			&otd.BriefingDocumentName, | ||||
| 			&otd.BriefingAsOf, | ||||
| 			&otd.ParticipantFirstName, | ||||
| 			&otd.ParticipantLastName, | ||||
| 			&otd.ParticipantCompany, | ||||
| 		) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("*DB.ReadAllBriefings: rows.Scan(): %v\n", err) | ||||
| 		} | ||||
|  | ||||
| 		data = append(data, otd) | ||||
| 	} | ||||
|  | ||||
| 	return &data, nil | ||||
| } | ||||
|  | ||||
| func (db *DB) GetLastID(table string) (int, error) { | ||||
| 	var id int | ||||
|  | ||||
| 	row := db.QueryRow(` | ||||
|   SELECT id | ||||
|   FROM ? | ||||
|   ORDER BY id DESC | ||||
|     LIMIT 0, 1 | ||||
|   `, table) | ||||
|  | ||||
| 	if err := row.Scan(&id); err != nil { | ||||
| 		return -1, fmt.Errorf("*DB.GetLastID: row.Scan(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	return id, nil | ||||
| } | ||||
|  | ||||
| func (db *DB) GetInstructors() (*[]*types.Instructor, error) { | ||||
| 	rows, err := db.Query(` | ||||
|   SELECT * | ||||
|   FROM instructors | ||||
|   `) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("*DB.GetInstructors: db.Query(): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	instructors := make([]*types.Instructor, 0) | ||||
| 	for rows.Next() { | ||||
| 		instructor := new(types.Instructor) | ||||
| 		if err = rows.Scan(instructor); err != nil { | ||||
| 			return nil, fmt.Errorf("*DB.GetInstructors: rows.Scan(): %v\n", err) | ||||
| 		} | ||||
| 		instructors = append(instructors, instructor) | ||||
| 	} | ||||
|  | ||||
| 	return &instructors, nil | ||||
| } | ||||
							
								
								
									
										42
									
								
								packages/data/questionaires.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								packages/data/questionaires.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| 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 | ||||
| } | ||||
							
								
								
									
										193
									
								
								packages/server/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								packages/server/server.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,193 @@ | ||||
| package server | ||||
|  | ||||
| import ( | ||||
| 	"crypto/rand" | ||||
| 	"encoding/hex" | ||||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"streifling.com/jason/sicherheitsunterweisung/packages/data" | ||||
| 	"streifling.com/jason/sicherheitsunterweisung/packages/types" | ||||
| ) | ||||
|  | ||||
| // type questionData struct { | ||||
| // 	ID int64 | ||||
| // 	Q  types.Question | ||||
| // 	I  int | ||||
| // 	J  int | ||||
| // } | ||||
|  | ||||
| func DisplayTable(db *data.DB) http.HandlerFunc { | ||||
| 	return func(w http.ResponseWriter, r *http.Request) { | ||||
| 		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 DisplaySearchResults(db *data.DB) http.HandlerFunc { | ||||
| 	return func(w http.ResponseWriter, r *http.Request) { | ||||
| 		bs, err := db.GetOverviewTableDataByName(r.PostFormValue("search")) | ||||
| 		if err != nil { | ||||
| 			http.Error(w, "DisplayResults: db.ReadByName(r.PostFormValue()): "+fmt.Sprint(err), http.StatusInternalServerError) | ||||
| 		} | ||||
| 		template.Must(template.ParseFiles("templates/table.html")).ExecuteTemplate(w, "rows", bs) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func DisplayInstructorForm() http.HandlerFunc { | ||||
| 	return func(w http.ResponseWriter, r *http.Request) { | ||||
| 		template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "content", nil) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func generateUUID() (string, error) { | ||||
| 	bs := make([]byte, 4) | ||||
|  | ||||
| 	if _, err := rand.Read(bs); err != nil { | ||||
| 		return "", fmt.Errorf("GenerateUUID: rand.Read(bs): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	return hex.EncodeToString(bs), nil | ||||
| } | ||||
|  | ||||
| func AddParticipant(sl *[]string) 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) | ||||
| 		} | ||||
|  | ||||
| 		(*sl) = append(*sl, login) | ||||
| 		template.Must(template.ParseFiles("templates/briefing.html")).ExecuteTemplate(w, "new", login) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 	return func(w http.ResponseWriter, r *http.Request) { | ||||
| 		now := time.Now() | ||||
| 		briefing := new(types.Briefing) | ||||
|  | ||||
| 		// TODO: Dropdownmenü | ||||
| 		// instructorFirstName := r.PostFormValue("instructor-first") | ||||
| 		// instructorLastName := r.PostFormValue("instructor-last") | ||||
|  | ||||
| 		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 | ||||
|  | ||||
| 		db.WriteBriefing(briefing) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func newParticipant(l string) (*types.Participant, error) { | ||||
| 	var err error | ||||
| 	p := new(types.Participant) | ||||
|  | ||||
| 	p.ID, err = strconv.ParseInt(l, 10, 64) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("newParticipant: strconv.Atoi(idString): %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	return p, nil | ||||
| } | ||||
|  | ||||
| func DisplayParticipantForm(sl *[]string) 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) | ||||
| 			} | ||||
|  | ||||
| 			template.Must(template.ParseFiles("templates/participant.html")).ExecuteTemplate(w, "content", uuid) | ||||
| 		} 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) | ||||
| // 	} | ||||
| // } | ||||
							
								
								
									
										56
									
								
								packages/types/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								packages/types/types.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| package types | ||||
|  | ||||
| type Person struct { | ||||
| 	ID        int64 | ||||
| 	FirstName string | ||||
| 	LastName  string | ||||
| } | ||||
|  | ||||
| type Instructor Person | ||||
|  | ||||
| type Participant struct { | ||||
| 	Person | ||||
| 	Company string | ||||
| } | ||||
|  | ||||
| type Briefing struct { | ||||
| 	ID           int64 | ||||
| 	Date         string | ||||
| 	Time         string | ||||
| 	Location     string | ||||
| 	DocumentName string | ||||
| 	AsOf         string | ||||
| 	InstructorID int64 | ||||
| } | ||||
|  | ||||
| type Answer struct { | ||||
| 	ID   int64 | ||||
| 	Text string | ||||
| } | ||||
|  | ||||
| type Question struct { | ||||
| 	ID      int64 | ||||
| 	Text    string | ||||
| 	Answers []Answer | ||||
| 	Correct int | ||||
| } | ||||
|  | ||||
| type GivenAnswer struct { | ||||
| 	BriefingID    int64 | ||||
| 	ParticipantID int64 | ||||
| 	QuestionID    int64 | ||||
| 	GivenAnswer   int | ||||
| } | ||||
|  | ||||
| type OverviewTableData struct { | ||||
| 	InstructorFirstName  string | ||||
| 	InstructorLastName   string | ||||
| 	BriefingDate         string | ||||
| 	BriefingTime         string | ||||
| 	BriefingLocation     string | ||||
| 	BriefingDocumentName string | ||||
| 	BriefingAsOf         string | ||||
| 	ParticipantFirstName string | ||||
| 	ParticipantLastName  string | ||||
| 	ParticipantCompany   string | ||||
| } | ||||
							
								
								
									
										38
									
								
								templates/briefing.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								templates/briefing.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| {{ define "add-button" }} | ||||
| <button type="button" hx-post="/add-participant/" hx-target="this" hx-swap="outerHTML"> | ||||
|   Neuer Teilnehmer | ||||
| </button> | ||||
| {{ end }} | ||||
|  | ||||
| {{ define "new" }} | ||||
| <span>{{ . }}</span> | ||||
| {{ template "add-button" . }} | ||||
| {{ 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> | ||||
|  | ||||
|   <div id="location"> | ||||
|     <label for="location-input">Ort</label> | ||||
|     <input type="text" name="location" id="location-input" /> | ||||
|   </div> | ||||
|  | ||||
|   <div id="state"> | ||||
|     <label for="state-input">Stand vom</label> | ||||
|     <input type="date" name="state" id="state-input" /> | ||||
|   </div> | ||||
|  | ||||
|   {{ template "add-button" . }} | ||||
|  | ||||
|   <button type="submit" hx-post="/submit-form/" hx-target="#content" hx-swap="innerHTML"> | ||||
|     OK | ||||
|   </button> | ||||
| </form> | ||||
| {{ end }} | ||||
| @@ -11,58 +11,10 @@ | ||||
| <body> | ||||
|   <h1>Sicherheitsunterweisung</h1> | ||||
|  | ||||
|   <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 id="content"> | ||||
|     {{ template "content" . }} | ||||
|   </div> | ||||
|  | ||||
|     <div id="date"> | ||||
|       <label for="date-input">Datum</label> | ||||
|       <input type="date" name="date" id="date-input" /> | ||||
|     </div> | ||||
|  | ||||
|     <div id="time"> | ||||
|       <label for="time-input">Uhrzeit</label> | ||||
|       <input type="time" name="time" id="time-input" /> | ||||
|     </div> | ||||
|  | ||||
|     <div id="state"> | ||||
|       <label for="state-input">Stand vom</label> | ||||
|       <input type="date" name="state" id="state-input" /> | ||||
|     </div> | ||||
|  | ||||
|     <div id="location"> | ||||
|       <label for="location-input">Ort</label> | ||||
|       <input type="text" name="location" id="location-input" /> | ||||
|     </div> | ||||
|  | ||||
|     <div id="participants"> | ||||
|       <button type="button" hx-post="/add-participant/" hx-target="#participants" hx-swap="beforeend" | ||||
|         hx-trigger="click"> | ||||
|         + | ||||
|       </button> | ||||
|  | ||||
|       <div id="participant-{{ . }}"> | ||||
|         <label for="participant-first-input-{{ . }}">Vorname</label> | ||||
|         <input type="text" name="participant-first-{{ . }}" id="participant-first-input-{{ . }}" /> | ||||
|  | ||||
|         <label for="participant-last-input-{{ . }}">Nachname</label> | ||||
|         <input type="text" name="participant-last-{{ . }}" id="participant-last-input-{{ . }}" /> | ||||
|  | ||||
|         <label for="participant-company-input-{{ . }}">Firma</label> | ||||
|         <input type="text" name="participant-company-{{ . }}" id="participant-company-input-{{ . }}" /> | ||||
|       </div> | ||||
|     </div> | ||||
|  | ||||
|     <button type="submit" hx-post="/submit/"> | ||||
|       Senden | ||||
|     </button> | ||||
|   </form> | ||||
|  | ||||
|   <script src="/static/js/htmx.min.js" type="text/javascript"></script> | ||||
| </body> | ||||
|  | ||||
|   | ||||
							
								
								
									
										16
									
								
								templates/login.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								templates/login.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| {{ define "content" }} | ||||
| <h2>Login</h2> | ||||
|  | ||||
| <form> | ||||
|   <label for="login-input">Code</label> | ||||
|   <input type="text" name="login" id="login-input" /> | ||||
|  | ||||
|   <button type="submit" hx-post="/external-login/" hx-target="#content"> | ||||
|     Anmelden | ||||
|   </button> | ||||
|  | ||||
|   <button type="submit" hx-post="/internal-login/" hx-target="#content"> | ||||
|     Intern | ||||
|   </button> | ||||
| </form> | ||||
| {{ end }} | ||||
| @@ -1,5 +1,5 @@ | ||||
| {{ define "participant" }} | ||||
| <div id="participant-{{ . }}"> | ||||
| {{ define "content" }} | ||||
| <form id="participant-{{ . }}"> | ||||
|   <label for="participant-first-input-{{ . }}">Vorname</label> | ||||
|   <input type="text" name="participant-first-{{ . }}" id="participant-first-input-{{ . }}" /> | ||||
|  | ||||
| @@ -8,5 +8,9 @@ | ||||
|  | ||||
|   <label for="participant-company-input-{{ . }}">Firma</label> | ||||
|   <input type="text" name="participant-company-{{ . }}" id="participant-company-input-{{ . }}" /> | ||||
| </div> | ||||
|  | ||||
|   <button type="button" hx-post="/display-question-{{ . }}-0/" hx-target="#content"> | ||||
|     Fertig | ||||
|   </button> | ||||
| </form> | ||||
| {{ end }} | ||||
|   | ||||
							
								
								
									
										21
									
								
								templates/question.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								templates/question.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| {{ define "answers" }} | ||||
| {{ range .Q.Answers }} | ||||
| <div> | ||||
|   <input type="radio" name="answer" id="answer-{{ .ID }}" value="{{ .ID }}" /> | ||||
|   <label for="answer-{{ .ID }}">{{ .Text }}</label> | ||||
| </div> | ||||
| {{ end }} | ||||
| {{ end }} | ||||
|  | ||||
| {{ define "content" }} | ||||
| <h2>Frage {{ .I }}</h2> | ||||
| <p>{{ .Q.Text }}</p> | ||||
|  | ||||
| <form> | ||||
|   {{ template "answers" . }} | ||||
|  | ||||
|   <button type="submit" hx-post="/display-question-{{ .ID }}-{{ .J }}/" hx-target="#content" hx-swap="innerHTML"> | ||||
|     Weiter | ||||
|   </button> | ||||
| </form> | ||||
| {{ end }} | ||||
							
								
								
									
										11
									
								
								templates/results.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								templates/results.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| {{ define "passed" }} | ||||
| {{ end }} | ||||
|  | ||||
| {{ define "failed" }} | ||||
| {{ end }} | ||||
|  | ||||
| {{ define "content" }} | ||||
| {{ q := range .Participant.Questions }} | ||||
| <p>{{ . }}{{ q.Text }}</p> | ||||
| {{ end }} | ||||
| {{ end }} | ||||
							
								
								
									
										49
									
								
								templates/table.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								templates/table.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| {{ 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 }} | ||||
| </tr> | ||||
| {{ end }} | ||||
| {{ end }} | ||||
|  | ||||
| {{ define "content" }} | ||||
| <form> | ||||
|   <label for="search-input">Suche</label> | ||||
|   <input type="text" name="search" id="search-input" hx-post="/search/" hx-target="#results" hx-swap="innerHTML" | ||||
|     hx-trigger="keyup changed delay:200ms" /> | ||||
| </form> | ||||
|  | ||||
| <form> | ||||
|   <button type="submit" hx-post="/new-briefing/" hx-target="#content" hx-swap="innerHTML"> | ||||
|     Neue Unterweisung | ||||
|   </button> | ||||
| </form> | ||||
|  | ||||
| <table> | ||||
|   <thead> | ||||
|     <tr> | ||||
|       <th colspan="2">Unterweiser</th> | ||||
|       <th>Datum</th> | ||||
|       <th>Uhrzeit</th> | ||||
|       <th>Stand</th> | ||||
|       <th>Ort</th> | ||||
|       <th colspan="2">Teilnehmer</th> | ||||
|       <th>Firma</th> | ||||
|     </tr> | ||||
|   </thead> | ||||
|  | ||||
|   <tbody id="results"> | ||||
|     {{ template "rows" . }} | ||||
|   </tbody> | ||||
| </table> | ||||
| {{ end }} | ||||
							
								
								
									
										1
									
								
								tmp/build-errors.log
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tmp/build-errors.log
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| 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