Working on response/contest time management
This commit is contained in:
parent
560ba79ab8
commit
96485fc9f3
11 changed files with 149 additions and 96 deletions
2
dist/main.bundle.js
vendored
2
dist/main.bundle.js
vendored
File diff suppressed because one or more lines are too long
9
dist/styles.css
vendored
9
dist/styles.css
vendored
|
@ -15447,4 +15447,13 @@ ul.karmen-related-elements {
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.sticky-offset {
|
||||
top: 56px;
|
||||
}
|
||||
|
||||
.oef-anchor-selection:target {
|
||||
background: yellow;
|
||||
}
|
||||
|
||||
|
||||
/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzdHlsZXMuY3NzIiwic291cmNlUm9vdCI6IiJ9*/
|
|
@ -110,8 +110,15 @@ func (a *Answer) Update(args map[string]string, w http.ResponseWriter, r *http.R
|
|||
}
|
||||
}
|
||||
|
||||
func (a *Answer) Delete(args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) {
|
||||
return nil, nil
|
||||
func (model *Answer) Delete(args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) {
|
||||
answer, err := model.Read(args, w, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := DB().Unscoped().Delete(answer.(*Answer)).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return answer.(*Answer), nil
|
||||
}
|
||||
|
||||
func CreateAnswer(answer *Answer) (*Answer, error) {
|
||||
|
|
|
@ -24,6 +24,7 @@ type Contest struct {
|
|||
Date time.Time
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Duration int // minutes
|
||||
|
||||
NumQuestions int
|
||||
NumAnswersPerQuestion int
|
||||
|
|
|
@ -107,8 +107,15 @@ func (q *Question) Update(args map[string]string, w http.ResponseWriter, r *http
|
|||
}
|
||||
}
|
||||
|
||||
func (q *Question) Delete(args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) {
|
||||
return nil, nil
|
||||
func (model *Question) Delete(args map[string]string, w http.ResponseWriter, r *http.Request) (interface{}, error) {
|
||||
question, err := model.Read(args, w, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := DB().Unscoped().Delete(question.(*Question)).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return question.(*Question), nil
|
||||
}
|
||||
|
||||
func CreateQuestion(question *Question) (*Question, error) {
|
||||
|
|
|
@ -2,9 +2,12 @@ package orm
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.andreafazzi.eu/andrea/oef/renderer"
|
||||
"github.com/jinzhu/gorm"
|
||||
|
@ -31,6 +34,10 @@ type Response struct {
|
|||
QuestionsOrder string
|
||||
AnswersIDs string
|
||||
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
TimeLeft time.Duration
|
||||
|
||||
Score int `gorm:"-"`
|
||||
|
||||
Questions []*Question
|
||||
|
@ -164,6 +171,22 @@ func (model *Response) Update(args map[string]string, w http.ResponseWriter, r *
|
|||
|
||||
response := result.(*Response)
|
||||
|
||||
// Write StartTime for the first time if user is a participant
|
||||
|
||||
if isParticipant(r) && !response.Contest.StartTime.IsZero() && !response.Contest.EndTime.IsZero() {
|
||||
if response.StartTime.IsZero() {
|
||||
if err := DB().Model(&response).Update("start_time", time.Now()).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := DB().Model(&response).Update("end_time", time.Now().Add(time.Duration(response.Contest.Duration)*time.Minute)).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Println("StartTime/EndTime", response.StartTime, response.EndTime)
|
||||
}
|
||||
response.TimeLeft = response.EndTime.Sub(time.Now())
|
||||
log.Println("TimeLeft:", response.TimeLeft)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
} else {
|
||||
response, err := model.Read(args, w, r)
|
||||
|
|
|
@ -30,6 +30,7 @@ var (
|
|||
"prettyTime": prettyTime,
|
||||
"prettyDateTime": prettyDateTime,
|
||||
"zeroTime": zeroTime,
|
||||
"minutes": minutes,
|
||||
"modelPath": modelPath,
|
||||
"dict": dict,
|
||||
"yaml": yaml,
|
||||
|
@ -308,6 +309,10 @@ func zeroTime(t *time.Time) bool {
|
|||
return *t == time.Time{}
|
||||
}
|
||||
|
||||
func minutes(d time.Duration) int {
|
||||
return int(d.Minutes())
|
||||
}
|
||||
|
||||
func modelPath(model string, action string, id uint) string {
|
||||
var q template.URL
|
||||
|
||||
|
|
|
@ -97,3 +97,12 @@ ul.karmen-related-elements {
|
|||
.uppercase:valid {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.sticky-offset {
|
||||
top: 56px;
|
||||
}
|
||||
|
||||
.oef-anchor-selection:target {
|
||||
background: yellow;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,10 @@
|
|||
{{$options := ` { name: "EndTime",id: "contest_end_time",label: "Alle ore",type: "time" } `}}
|
||||
{{template "input" dict "options" ($options|yaml) "value" (.Data|field "EndTime"|convertTime) "update" $update}}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{$options := ` { name: "Duration",id: "contest_duration",label: "Durata",type: "number" } `}}
|
||||
{{template "input" dict "options" ($options|yaml) "value" (.Data|field "Duration") "update" $update}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
{{define "base"}}
|
||||
|
||||
{{$isAdmin := .Claims|isAdmin}}
|
||||
{{$isSubscriber := .Claims|isSubscriber}}
|
||||
{{$isSchool := .Claims|isSchool}}
|
||||
{{$isParticipant := .Claims|isParticipant}}
|
||||
|
||||
{{- define "base" -}}
|
||||
{{- $isAdmin := .Claims|isAdmin -}}
|
||||
{{- $isSubscriber := .Claims|isSubscriber -}}
|
||||
{{- $isSchool := .Claims|isSchool -}}
|
||||
{{- $isParticipant := .Claims|isParticipant -}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
|
@ -14,17 +12,13 @@
|
|||
<link rel="stylesheet" href="/styles.css" />
|
||||
<title>Olimpiadi di Economia e Finanza - Piattaforma di gara</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg fixed-top navbar-dark bg-primary">
|
||||
|
||||
{{$homeURL := ""}}
|
||||
{{if $isAdmin}}{{$homeURL = all "Contest"}}{{end}}
|
||||
{{if $isSubscriber}}{{$homeURL = "#"}}{{end}}
|
||||
{{if $isParticipant}}{{$homeURL = "#"}}{{end}}
|
||||
{{if $isSchool}}{{$homeURL = "#"}}{{end}}
|
||||
|
||||
{{- $homeURL := "" -}}
|
||||
{{- if $isAdmin}}{{$homeURL = all "Contest"}}{{end}}
|
||||
{{- if $isSubscriber}}{{$homeURL = "#"}}{{end}}
|
||||
{{- if $isParticipant}}{{$homeURL = "#"}}{{end}}
|
||||
{{- if $isSchool}}{{$homeURL = "#"}}{{end -}}
|
||||
<a class="navbar-brand" href="{{$homeURL}}">
|
||||
<span class="fa fa-landmark"></span>
|
||||
OEF 2020
|
||||
|
@ -33,34 +27,30 @@
|
|||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div id="navbar" class="collapse navbar-collapse">
|
||||
|
||||
<ul class="navbar-nav mr-auto">
|
||||
{{if (.Claims|isAdmin)}}
|
||||
{{- if (.Claims|isAdmin) -}}
|
||||
<a class="nav-item nav-link {{.Options|active "Contest"}}" href="{{all "Contest"}}">Gare</a>
|
||||
<a class="nav-item nav-link {{.Options|active "Question"}}" href="{{all "Question"}}">Domande</a>
|
||||
<a class="nav-item nav-link {{.Options|active "Answer"}}" href="{{all "Answer"}}">Risposte</a>
|
||||
<a class="nav-item nav-link {{.Options|active "School"}}" href="{{all "School"}}">Scuole</a>
|
||||
<a class="nav-item nav-link {{.Options|active "Participant"}}" href="{{all "Participant"}}">Partecipanti</a>
|
||||
<a class="nav-item nav-link {{.Options|active "Response"}}" href="{{all "Response"}}">Prove</a>
|
||||
{{end}}
|
||||
{{if $isSchool}}
|
||||
{{- end -}}
|
||||
{{- if $isSchool -}}
|
||||
<a class="nav-item nav-link {{.Options|active "School"}}" href="{{.Claims|userId|show "School"}}">Scuola</a>
|
||||
<a class="nav-item nav-link {{.Options|active "Participant"}}" href="{{all "Participant"}}">Partecipanti</a>
|
||||
{{end}}
|
||||
|
||||
{{- end -}}
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{{if .Claims|isSubscriber}}
|
||||
{{- if .Claims|isSubscriber -}}
|
||||
<li><a class="nav-link" href="/logout">Esci</a></li>
|
||||
{{else}}
|
||||
{{- else -}}
|
||||
<li><a class="nav-link" href="/logout">Disconnetti {{.Claims|username}}</a></li>
|
||||
{{end}}
|
||||
{{- end -}}
|
||||
</ul>
|
||||
</div><!--/.nav-collapse -->
|
||||
</nav>
|
||||
|
||||
{{if .FlashMessages}}
|
||||
{{- if .FlashMessages -}}
|
||||
{{range $message := .FlashMessages}}
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<strong>Attenzione!</strong> {{$message}}
|
||||
|
@ -69,12 +59,10 @@
|
|||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{- end -}}
|
||||
<div class="base-template">
|
||||
{{ template "content" . }}
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="karmen-modal-remove" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
|
@ -94,9 +82,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/main.bundle.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
|
|
@ -1,65 +1,67 @@
|
|||
{{ define "content" }}
|
||||
|
||||
{{$isAdmin := .Claims|isAdmin}}
|
||||
{{$isParticipant := .Claims|isParticipant}}
|
||||
{{$update := .Options.Get "update"}}
|
||||
|
||||
<div class="container">
|
||||
{{if $isAdmin}}
|
||||
|
||||
{{if $update}}
|
||||
|
||||
{{- define "content" -}}
|
||||
{{- $isAdmin := .Claims|isAdmin -}}
|
||||
{{- $isParticipant := .Claims|isParticipant -}}
|
||||
{{- $update := .Options.Get "update" -}}
|
||||
<div class="container-fluid">
|
||||
{{- if $isAdmin -}}
|
||||
{{- if $update -}}
|
||||
{{template "breadcrumb" toSlice "Prove" (all "Response") (.Data|string) (.Data.ID|show "Response") "Aggiorna" "current"}}
|
||||
{{else}}
|
||||
{{- else -}}
|
||||
{{template "breadcrumb" toSlice "Prove" (all "Response") "Aggiungi" "current"}}
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
|
||||
{{if $isAdmin}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- if $isAdmin -}}
|
||||
{{template "add_update_header" dict "update" $update "addTitle" "Rispondi al questionario" "updateTitle" (printf "Aggiorna %s" (.Data|string))}}
|
||||
{{else}}
|
||||
{{- else -}}
|
||||
{{template "add_update_header" dict "update" $update "addTitle" "Rispondi al questionario" "updateTitle" (.Data|string)}}
|
||||
{{end}}
|
||||
{{$form := "form_add_update"}}
|
||||
<form
|
||||
class="needs-validation"
|
||||
action="{{if $update}}{{.Data.ID|update "Response"}}{{else}}{{create "Response"}}{{end}}"
|
||||
method="POST"
|
||||
role="form"
|
||||
id={{$form}}>
|
||||
|
||||
{{range $id,$question := .Data.Questions}}
|
||||
<div class="form-group p-3">
|
||||
<p class="lead">{{$question.Text}}</p>
|
||||
{{range $answer := $question.Answers}}
|
||||
<div class="form-check">
|
||||
{{$checked := false}}
|
||||
{{if isResponseIn $answer.ID $.Data.AnswersIDs}}
|
||||
{{$checked = true}}
|
||||
{{end}}
|
||||
<input class="form-check-input" type="radio" name="SingleResponses.{{$id}}" id="answer_{{$answer.ID}}" value="{{$answer.ID}}" required {{if $checked}}checked{{end}}>
|
||||
<label class="form-check-label {{if and $isAdmin $answer.Correct}}text-success{{end}}" for="answer_{{$answer.ID}}">
|
||||
{{$answer}}
|
||||
</label>
|
||||
{{- end -}}
|
||||
{{- $form := "form_add_update" -}}
|
||||
<div class="row">
|
||||
<div class="col-2 border-right">
|
||||
<div class="sticky-top sticky-offset">
|
||||
<nav class="nav flex-column">
|
||||
{{range $n, $question := .Data.Questions -}}
|
||||
<a class="nav-link" href="#question_{{$n}}">Domanda {{$n|incr}}</a>
|
||||
{{end -}}
|
||||
</nav>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if $isAdmin}}
|
||||
|
||||
{{$options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Response" } `}}
|
||||
{{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{$options := ` { saveTitle: "Invia le risposte", model: "Response" } `}}
|
||||
{{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}
|
||||
|
||||
{{end}}
|
||||
|
||||
</form>
|
||||
|
||||
<div class="col">
|
||||
<form
|
||||
class="needs-validation"
|
||||
action="{{if $update}}{{.Data.ID|update "Response"}}{{else}}{{create "Response"}}{{end}}"
|
||||
method="POST"
|
||||
role="form"
|
||||
id={{$form}}>
|
||||
{{range $id,$question := .Data.Questions -}}
|
||||
<div class="oef-anchor-selection" id="question_{{$id}}">
|
||||
<div class="form-group p-3">
|
||||
<p class="lead">{{$id|incr}}. {{$question.Text}}</p>
|
||||
{{range $answer := $question.Answers -}}
|
||||
<div class="form-check">
|
||||
{{$checked := false}}
|
||||
{{if isResponseIn $answer.ID $.Data.AnswersIDs}}
|
||||
{{$checked = true}}
|
||||
{{end}}
|
||||
<input class="form-check-input" type="radio" name="SingleResponses.{{$id}}" id="answer_{{$answer.ID}}" value="{{$answer.ID}}" required {{if $checked}}checked{{end}}>
|
||||
<label class="form-check-label {{if and $isAdmin $answer.Correct}}text-success{{end}}" for="answer_{{$answer.ID}}">
|
||||
{{$answer}}
|
||||
</label>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
{{end}}
|
||||
{{- if $isAdmin -}}
|
||||
{{- $options := ` { cancelTitle: "Annulla", saveTitle: "Salva", model: "Response" } ` -}}
|
||||
{{- template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update -}}
|
||||
{{- else -}}
|
||||
{{- $options := ` { saveTitle: "Invia le risposte", model: "Response" } ` -}}
|
||||
{{template "submit_cancel_buttons" dict "options" ($options|yaml) "id" (.Data|field "ID") "update" $update}}
|
||||
{{end -}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
|
Loading…
Reference in a new issue