feat: add manual interval creation and inline entry editing

- Service: CreateInterval() validates same-day, end>start, day not closed
- Service: Update() now rejects edits on closed-day entries (ErrDayAlreadyClosed)
- Handler: POST /api/entries creates a completed interval with explicit times
- API client: entries.createInterval(startMs, endMs, note)
- Utils: parseTimeInput / toTimeInput helpers for HH:MM <-> unix ms
- Today page: '+ Add interval' form (time pickers + optional note)
- Today page: pencil button on each entry opens inline edit row (start/end/note)
- Tests: TestCreateInterval, TestCreateIntervalEndBeforeStart,
         TestCreateIntervalCrossesMidnight, TestUpdateRejectsClosedDay
This commit is contained in:
2026-04-30 17:45:02 +02:00
parent 245edf1534
commit 9d6233b116
6 changed files with 393 additions and 25 deletions

View File

@@ -19,12 +19,47 @@ func NewEntryHandler(svc *service.EntryService) *EntryHandler {
func (h *EntryHandler) Routes(r chi.Router) {
r.Post("/entries/start", h.Start)
r.Post("/entries", h.CreateInterval)
r.Post("/entries/{id}/stop", h.StopByID)
r.Get("/entries", h.List)
r.Put("/entries/{id}", h.Update)
r.Delete("/entries/{id}", h.Delete)
}
// CreateInterval POST /api/entries — create a completed interval with explicit times
func (h *EntryHandler) CreateInterval(w http.ResponseWriter, r *http.Request) {
var body struct {
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
Note string `json:"note"`
}
if err := decodeJSON(r, &body); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON")
return
}
if body.StartTime == 0 || body.EndTime == 0 {
writeError(w, http.StatusBadRequest, "start_time and end_time are required")
return
}
entry, err := h.svc.CreateInterval(r.Context(), service.CreateIntervalInput{
StartTime: body.StartTime,
EndTime: body.EndTime,
Note: body.Note,
})
if err != nil {
switch {
case errors.Is(err, service.ErrCrossesMidnight):
writeError(w, http.StatusUnprocessableEntity, err.Error())
case errors.Is(err, service.ErrDayAlreadyClosed):
writeError(w, http.StatusConflict, err.Error())
default:
writeError(w, http.StatusInternalServerError, err.Error())
}
return
}
writeJSON(w, http.StatusCreated, entry)
}
// Start POST /api/entries/start
func (h *EntryHandler) Start(w http.ResponseWriter, r *http.Request) {
var body struct {