diff options
-rw-r--r-- | db.go | 43 | ||||
-rw-r--r-- | model.go | 59 | ||||
-rw-r--r-- | page.go | 14 | ||||
-rw-r--r-- | templates/parts/entry.html | 20 | ||||
-rw-r--r-- | templates/parts/entryRows.html | 18 | ||||
-rw-r--r-- | tidsreg.go | 62 |
6 files changed, 137 insertions, 79 deletions
@@ -6,17 +6,17 @@ import ( ) type Database interface { - GetTracking() (*Task, error) - GetTasks() ([]*Task, error) - QueryTask(id *int) (*Task, error) - StartNewEntry(now time.Time, task *Task) (*Task, error) + GetTracking() (*Entry, error) + GetTasks() ([]*Entry, error) + QueryTask(id *int) (*Entry, error) + StartNewEntry(now time.Time, task *Entry) (*Entry, error) StopEntry(now time.Time) error - SaveEntry(task *Task) (*Task, error) + SaveEntry(task *Entry) (*Entry, error) } type InMemDb struct { - tasks []*Task - current *Task + tasks []*Entry + current *Entry } var ( @@ -26,17 +26,17 @@ var ( func NewInMemDb() Database { return &InMemDb { - tasks: []*Task{}, + tasks: []*Entry{}, current: nil, } } -func (db *InMemDb) GetTracking() (*Task, error) { +func (db *InMemDb) GetTracking() (*Entry, error) { return db.current, nil } -func (db *InMemDb) GetTasks() ([]*Task, error) { - res := make([]*Task, 0, len(db.tasks)) +func (db *InMemDb) GetTasks() ([]*Entry, error) { + res := make([]*Entry, 0, len(db.tasks)) for _, task := range db.tasks { res = append(res, task) } @@ -44,18 +44,15 @@ func (db *InMemDb) GetTasks() ([]*Task, error) { return res, nil } -func (db *InMemDb) QueryTask(id *int) (*Task, error) { - for _, task := range db.tasks { - if id != nil && task.Id == *id { - return task, nil - } +func (db *InMemDb) QueryTask(id *int) (*Entry, error) { + if id != nil && *id >= 0 || *id < len(db.tasks) { + return db.tasks[*id], nil } - return nil, ErrNotFound } -func (db *InMemDb) StartNewEntry(now time.Time, task *Task) (*Task, error) { - newTask := &Task { +func (db *InMemDb) StartNewEntry(now time.Time, task *Entry) (*Entry, error) { + newTask := &Entry { Id: len(db.tasks), From: task.From, To: nil, @@ -64,7 +61,8 @@ func (db *InMemDb) StartNewEntry(now time.Time, task *Task) (*Task, error) { } if newTask.From == nil { - newTask.From = &now + from := TimeFromStd(now) + newTask.From = &from } db.tasks = append(db.tasks, newTask) @@ -75,7 +73,8 @@ func (db *InMemDb) StartNewEntry(now time.Time, task *Task) (*Task, error) { func (db *InMemDb) StopEntry(now time.Time) error { if db.current != nil { - db.current.To = &now + to := TimeFromStd(now) + db.current.To = &to db.current = nil } else { return ErrNotRunning @@ -83,7 +82,7 @@ func (db *InMemDb) StopEntry(now time.Time) error { return nil } -func (db *InMemDb) SaveEntry(task *Task) (*Task, error) { +func (db *InMemDb) SaveEntry(task *Entry) (*Entry, error) { if task.Id < 0 || task.Id >= len(db.tasks) { copyTask := *task copyTask.Id = len(db.tasks) @@ -1,11 +1,62 @@ package main -import "time" +import ( + "errors" + "regexp" + "time" +) -type Task struct { +type Entry struct { Id int - From *time.Time - To *time.Time + Date time.Time + From *Time + To *Time Tag *string Comment string } + +type Date string + +type Time string + +var timeMatch = regexp.MustCompile("\\d\\d:\\d\\d") +var dateMatch = regexp.MustCompile("\\d\\d\\d\\d-\\d\\d-\\d\\d") + +var ( + ErrInvalidTime = errors.New("Invalid time string") + ErrInvalidDate = errors.New("Invalid date string") +) + +func TimeFromStd(time time.Time) Time { + return Time(time.Format("15:04")) +} + +func TimeFromString(str string) (*Time, error) { + if str == "" { + return nil, nil + } + + matched := timeMatch.MatchString(str) + if !matched { + return nil, ErrInvalidTime + } + time := Time(str) + return &time, nil +} + +func DateFromStd(time time.Time) Date { + return Date(time.Format("2006-01-02")) +} + +func DateFromString(str string) (*Date, error) { + if str == "" { + return nil, nil + } + + matched := dateMatch.MatchString(str) + if !matched { + return nil, ErrInvalidTime + } + date := Date(str) + return &date, nil +} @@ -5,14 +5,14 @@ type Service struct { } type EntryPage struct { - Task *Task + Entry *Entry Detached bool - Tracking *Task + Tracking *Entry } type RootPage struct { Entry *EntryPage - Tasks []*Task + Entries []*Entry } func NewService(db Database) *Service { @@ -21,20 +21,20 @@ func NewService(db Database) *Service { } } -func (srv *Service) GetEntryPage(detached *Task) (*EntryPage, error) { +func (srv *Service) GetEntryPage(detached *Entry) (*EntryPage, error) { tracking, err := srv.Db.GetTracking() if err != nil { return nil, err } page := &EntryPage { - Task: tracking, + Entry: tracking, Detached: false, Tracking: tracking, } if detached != nil { - page.Task = detached + page.Entry = detached page.Detached = true } @@ -54,6 +54,6 @@ func (srv *Service) GetRootPage() (*RootPage, error) { return &RootPage { Entry: entry, - Tasks: tasks, + Entries: tasks, }, nil } diff --git a/templates/parts/entry.html b/templates/parts/entry.html index f99fb90..9f05ffa 100644 --- a/templates/parts/entry.html +++ b/templates/parts/entry.html @@ -1,18 +1,18 @@ -<form id="entry-bar" autocomplete="off" class="status-{{if .Detached}}detached{{else}}{{if .Task}}started{{else}}stopped{{end}}{{end}}"> - <input style="display: none;" value="{{ if .Task }}{{ .Task.Id }}{{end}}" type="text" name="id" /> +<form id="entry-bar" autocomplete="off" class="status-{{if .Detached}}detached{{else}}{{if .Entry}}started{{else}}stopped{{end}}{{end}}"> + <input style="display: none;" value="{{ if .Entry }}{{ .Entry.Id }}{{end}}" type="text" name="id" /> {{ if .Detached }} - <span>{{ if gt .Task.Id -1 }}Redigerer opgave {{ .Task.Id }}{{else}}Redigerer ny opgave{{end}}{{ if .Tracking }}, med opgave i baggrunden!{{else}}.{{end}}</span><br> + <span>{{ if gt .Entry.Id -1 }}Redigerer opgave {{ .Entry.Id }}{{else}}Redigerer ny opgave{{end}}{{ if .Tracking }}, med opgave i baggrunden!{{else}}.{{end}}</span><br> {{end}} <div class="flex just-start"> <div class="entry-box"> <b>Interval</b> <div> <label for="fromTime">Fra: </label> - <input name="from" id="fromTime" type="time" class="form-control" {{if .Task}}value="{{formatTime .Task.From}}" required{{end}} aria-label="Time start"> + <input name="from" id="fromTime" type="time" class="form-control" {{if .Entry}}value="{{.Entry.From}}" required{{end}} aria-label="Time start"> </div> <div> <label for="toTime">Til: </label> - <input name="to" id="toTime" type="time" class="form-control" {{if not .Detached}}disabled{{end}} aria-label="Time stop"> + <input name="to" id="toTime" type="time" class="form-control" {{if .Entry}}value="{{if .Entry.To}}{{.Entry.To}}{{end}}" required{{end}} {{if not .Detached}}disabled{{end}} aria-label="Time stop"> </div> </div> <div class="entry-box"> @@ -30,16 +30,16 @@ </div> <div class="entry-box"> <b>Kommentar</b><br> - <textarea name="comment">{{if .Task}}{{.Task.Comment}}{{end}}</textarea> + <textarea name="comment">{{if .Entry}}{{.Entry.Comment}}{{end}}</textarea> </div> <div class="entry-box"> <b>Status</b><br> - {{ if not .Detached}}<i>{{ if .Task }}I gang{{ else }}Stoppet{{ end }}</i><br>{{end}} - {{ if .Task }}<span>1:34 timer</span>{{ end }} + {{ if not .Detached}}<i>{{ if .Entry }}I gang{{ else }}Stoppet{{ end }}</i><br>{{end}} + {{ if .Entry }}<span>1:34 timer</span>{{ end }} </div> </div> - {{ if .Task }} + {{ if .Entry }} <button hx-put="/save{{if .Detached}}?detached=true{{end}}" hx-trigger="click" @@ -56,7 +56,7 @@ hx-swap="outerHTML" >Tilbage</button> {{ else }} - {{ if .Task }} + {{ if .Entry }} <button hx-post="/stop" hx-trigger="click" diff --git a/templates/parts/entryRows.html b/templates/parts/entryRows.html index 1b364e3..fd14608 100644 --- a/templates/parts/entryRows.html +++ b/templates/parts/entryRows.html @@ -1,12 +1,14 @@ -<tbody hx-trigger="changedTasks from:body" hx-get="/entryRows"> - {{ range $task := .Tasks }} +<tbody hx-swap="outerHTML" hx-trigger="changedEntries from:body" hx-get="/entryRows"> + {{ range $entry := .Entries }} <tr> - <td>{{ $task.Id }}</td> - <td><input type="time" disabled value="{{formatTime $task.From }}" /></td> - <td><input type="time" disabled value="{{formatTime $task.To }}" /></td> - <td>{{ if $task.Tag}}{{ $task.Tag }}{{end}}</td> - <td>{{ $task.Comment }}</td> - <td></td> + <td>{{ $entry.Id }}</td> + <td><input type="time" disabled value="{{$entry.From }}" /></td> + <td><input type="time" disabled value="{{if $entry.To}}{{$entry.To }}{{end}}" /></td> + <td>{{ if $entry.Tag}}{{ $entry.Tag }}{{end}}</td> + <td>{{ $entry.Comment }}</td> + <td> + {{ if $entry.To }}<button hx-trigger="click" hx-swap="outerHTML" hx-target="#entry-bar" hx-get="/edit?id={{$entry.Id}}">e</button>{{end}} + </td> </tr> {{ end }} </tbody> @@ -16,32 +16,17 @@ type Server struct { srv *Service } -func funcMapFormatTime(t *time.Time) string { - if t == nil { - return "" - } - return t.Format("15:04") -} - -func parseStringTime(str string) (*time.Time, error) { - if str == "" { - return nil, nil - } - t, err := time.Parse("15:04", str) - return &t, err -} - -func parseTaskFromForm(r *http.Request) (*Task, error) { +func parseTaskFromForm(r *http.Request) (*Entry, error) { err := r.ParseForm() if err != nil { return nil, err } - from, err := parseStringTime(r.PostFormValue("from")) + from, err := TimeFromString(r.PostFormValue("from")) if err != nil { return nil, err } - to, err := parseStringTime(r.PostFormValue("to")) + to, err := TimeFromString(r.PostFormValue("to")) if err != nil { return nil, err } @@ -55,7 +40,7 @@ func parseTaskFromForm(r *http.Request) (*Task, error) { } } - task := &Task { + task := &Entry { Id: int(id), From: from, To: to, @@ -65,10 +50,7 @@ func parseTaskFromForm(r *http.Request) (*Task, error) { } func (s *Server) renderTemplate(w http.ResponseWriter, name string, page interface{}) { - tmpl := template.New("").Funcs(template.FuncMap { - "formatTime": funcMapFormatTime, - }) - tmpl, err := tmpl.ParseFiles("templates/index.html", "templates/parts/entry.html", "templates/parts/entryRows.html") + tmpl, err := template.ParseFiles("templates/index.html", "templates/parts/entry.html", "templates/parts/entryRows.html") if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return @@ -123,7 +105,7 @@ func (s *Server) postStart(w http.ResponseWriter, r *http.Request) { writeError(w, err.Error(), http.StatusInternalServerError) return } - w.Header().Add("HX-Trigger", "changedTasks") + w.Header().Add("HX-Trigger", "changedEntries") s.renderTemplate(w, "entry.html", page) } @@ -146,7 +128,7 @@ func (s *Server) postStop(w http.ResponseWriter, r *http.Request) { return } - w.Header().Add("HX-Trigger", "changedTasks") + w.Header().Add("HX-Trigger", "changedEntries") s.getTracking(w, r) } @@ -174,7 +156,7 @@ func (s *Server) putSave(w http.ResponseWriter, r *http.Request) { return } - var detached *Task = nil + var detached *Entry = nil if r.URL.Query().Get("detached") == "true" { detached = task } @@ -185,13 +167,13 @@ func (s *Server) putSave(w http.ResponseWriter, r *http.Request) { return } - w.Header().Add("HX-Trigger", "changedTasks") + w.Header().Add("HX-Trigger", "changedEntries") s.renderTemplate(w, "entry.html", page) } func (s *Server) getNew(w http.ResponseWriter, _ *http.Request) { - task := &Task { + task := &Entry { Id: -1, From: nil, To: nil, @@ -208,6 +190,29 @@ func (s *Server) getNew(w http.ResponseWriter, _ *http.Request) { s.renderTemplate(w, "entry.html", page) } +func (s *Server) getEdit(w http.ResponseWriter, r *http.Request) { + idLarge, err := strconv.ParseInt(r.URL.Query().Get("id"), 10, 32) + if err != nil { + writeError(w, err.Error(), http.StatusInternalServerError) + return + } + id := int(idLarge) + + task, err := s.srv.Db.QueryTask(&id) + if err != nil { + writeError(w, err.Error(), http.StatusInternalServerError) + return + } + + page, err := s.srv.GetEntryPage(task) + if err != nil { + writeError(w, err.Error(), http.StatusInternalServerError) + return + } + + s.renderTemplate(w, "entry.html", page) +} + func (s *Server) getEntries(w http.ResponseWriter, _ *http.Request) { page, err := s.srv.GetRootPage() if err != nil { @@ -233,6 +238,7 @@ func main() { r.HandleFunc("/stop", s.postStop).Methods("POST") r.HandleFunc("/save", s.putSave).Methods("PUT") r.HandleFunc("/entryRows", s.getEntries).Methods("GET") + r.HandleFunc("/edit", s.getEdit).Methods("GET") r.HandleFunc("/", s.rootHandle) http.Handle("/", r) |