add year logic
@@ -0,0 +1,15 @@
+{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "." + } + ] +}
@@ -9,17 +9,24 @@
"github.com/gin-gonic/gin" ) -func validateDate(month, day int) error { +func validateDate(month, day uint, year *uint) error { if month < 1 || month > 12 { return errors.New("invalid month: must be between 1 and 12") } if day < 1 || day > 31 { return errors.New("invalid day: must be between 1 and 31") + } + + var testYear uint + if year == nil { + testYear = 2023 + } else { + testYear = *year } // Construct a date and use time package to validate - dateStr := fmt.Sprintf("2023-%02d-%02d", month, day) + dateStr := fmt.Sprintf("%04d-%02d-%02d", testYear, month, day) _, err := time.Parse("2006-01-02", dateStr) if err != nil { return errors.New("invalid day for the given month")@@ -37,12 +44,13 @@ }
// Update existing record with new values if input.Day != 0 || input.Month != 0 { - if err := validateDate(input.Month, input.Day); err != nil { + if err := validateDate(input.Month, input.Day, input.Year); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } + occurrence.Day = input.Day occurrence.Month = input.Month - occurrence.Day = input.Day + occurrence.Year = input.Year } if input.Name != "" { occurrence.Name = input.Name@@ -68,7 +76,7 @@ updateOccurrence(c, input)
return } - if err := validateDate(input.Month, input.Day); err != nil { + if err := validateDate(input.Month, input.Day, input.Year); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return }
@@ -16,15 +16,17 @@ )
// Occurrence represents a scheduled event type Occurrence struct { - ID uint `gorm:"primaryKey" json:"id"` - Month int `json:"month"` - Day int `json:"day"` - Name string `json:"name"` - Description string `json:"description"` - Notify bool `json:"notify"` - Notified bool `json:"notified"` - CreatedAt time.Time `json:"-"` - UpdatedAt time.Time `json:"-"` + ID uint `json:"id" gorm:"primaryKey"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + + Day uint `json:"day"` + Month uint `json:"month"` + Year *uint `json:"year"` + Name string `json:"name"` + Description string `json:"description"` + Notify bool `json:"notify"` + Notified bool `json:"notified"` } const (
@@ -89,7 +89,7 @@ db.Where("notified = ? AND ((month = ? AND day >= ?) OR (month = ? AND day <= ?))",
false, today.Month(), today.Day(), endWindow.Month(), endWindow.Day()).Find(&occurrences) for _, occurrence := range occurrences { - occurrenceDate := time.Date(today.Year(), time.Month(occurrence.Month), occurrence.Day, 0, 0, 0, 0, time.Local) + occurrenceDate := time.Date(today.Year(), time.Month(occurrence.Month), int(occurrence.Day), 0, 0, 0, 0, time.Local) if occurrenceDate.Before(today) || occurrenceDate.After(endWindow) || !occurrence.Notify || occurrence.Notified { continue }
@@ -122,7 +122,7 @@ <table id="main-table">
<tr> <th data-field="name">Nome</th> <th data-field="description">Descrizione</th> - <th data-field="date">Data (gg/mm)</th> + <th data-field="date">Data (gg/mm[/yyyy])</th> <th data-field="notify">Notifica</th> <th data-field="notified">Inviata</th> <th data-field="actions">Azioni</th>@@ -131,7 +131,7 @@ {{ range .Occurrences }}
<tr id="occurrence-{{ .ID }}"> <td data-field="name" data-value="{{ .Name }}">{{ .Name }}</td> <td data-field="description" data-value="{{ .Description }}">{{ .Description }}</td> - <td data-field="date" data-value="{{ padZero .Day }}/{{ padZero .Month }}">{{ padZero .Day }}/{{ padZero .Month }}</td> + <td data-field="date" data-value="{{ padZero .Day }}/{{ padZero .Month }}{{if .Year }}/{{ .Year }}{{end}}">{{ padZero .Day }}/{{ padZero .Month }}{{if .Year }}/{{ .Year }} (+{{ calcYear $.CurrentYear .Year }}){{end}}</td> <td data-field="notify" data-value="{{ .Notify }}"><input type="checkbox" {{if .Notify}}checked{{end}} disabled></td> <td data-field="notified" data-value="{{ .Notified }}"><input type="checkbox" {{if .Notified}}checked{{end}} disabled></td> <td data-field="actions" class="actions">@@ -147,7 +147,7 @@ </table>
<div style="margin-top: 10px; text-align: center;"> <button id="add-row-button" class="cute-button" onclick="addNewOccurrenceRow()"> <i class="fas fa-plus"></i> Aggiungi</button> - <button id="save-row-button" class="cute-button hidden" onclick="saveOccurrence('0')"> + <button id="save-row-button" class="cute-button hidden" onclick="saveOccurrence(0)"> <i class="fas fa-save"></i> Salva </button> <button id="cancel-row-button" class="cute-button cute-button-red hidden" onclick="cancelNewOccurrence()">@@ -163,6 +163,8 @@ const cancelButton = document.getElementById('cancel-row-button');
const mainTable = document.getElementById('main-table'); const noneRow = document.getElementById('occurrence-none'); + const now = new Date(); + const currentYear = now.getFullYear(); const dataError = 'Controlla che i dati (e le date) siano corretti.'; let currentNext = null;@@ -180,10 +182,9 @@
const valueRows = rows.filter((row) => !row.classList.contains('editing')); // Sort rows by date - now = new Date(); valueRows.sort((a, b) => { - const dateA = parseDateString(now, getValueFromRow(a, 'date')); - const dateB = parseDateString(now, getValueFromRow(b, 'date')); + const dateA = parseDateString(getValueFromRow(a, 'date')); + const dateB = parseDateString(getValueFromRow(b, 'date')); return dateA - dateB; });@@ -219,20 +220,29 @@ function padNumber(input, n = 2) {
return input.toString().padStart(n, '0'); } - function padMax(input, max = 31) { - return padNumber(Math.max(1, Math.min(input, max))) + function padMax(input, max = 31, min=1) { + const n = Number(input); + if (isNaN(n)) { + console.log(max) + return max;} + return padNumber(Math.max(min, Math.min(n | 0, max))) + } + + function padYear(input, max, min) { + if (input == '') return ''; + return padMax(input, max, min) } - function handleInputKeyDown(event, id) { + function handleInputKeyDown(event, id, isNew) { if (event.key !== 'Enter') return; - saveOccurrence(id); + saveOccurrence(isNew ? 0 : id); } - function createRow(id, name, description, day, month, notify, notified) { + function createRow(id, name, description, day, month, year, notify, notified) { return ` <td data-field="name" data-value="${name}">${name}</td> <td data-field="description" data-value="${description}">${description}</td> - <td data-field="date" data-value="${padNumber(day)}/${padNumber(month)}">${padNumber(day)}/${padNumber(month)}</td> + <td data-field="date" data-value="${padNumber(day)}/${padNumber(month)}${year ? `/${padNumber(year, 4)}` : ''}">${padNumber(day)}/${padNumber(month)}${year ? `/${year} (+${currentYear - year})` : ''}</td> <td data-field="notify" data-value="${notify}"><input type="checkbox" ${notify ? 'checked' : ''} disabled></td> <td data-field="notified" data-value="${notified}"><input type="checkbox" ${notified ? 'checked' : ''} disabled></td> <td data-field="actions" class="actions">@@ -242,17 +252,19 @@ </td>
`; } - function createInputFields(id, name, description, day, month, notify, notified, isNew) { + function createInputFields(id, name, description, day, month, year, notify, notified, isNew) { const myName = name || ''; const myDescription = description || ''; const myDay = day || '01'; const myMonth = month || '01'; + const myYear = year || ''; return ` <td data-field="name" data-value="${myName}"><input class="big-input" type="text" value="${myName}" id="name-${id}" onkeydown="handleInputKeyDown(event, ${id})" autocomplete="off"></td> <td data-field="description" data-value="${myDescription}"><input class="big-input" type="text" value="${myDescription}" id="description-${id}" onkeydown="handleInputKeyDown(event, ${id})" autocomplete="off"></td> <td data-field="date" data-value="${myDay}/${myMonth}" class="date-inputs"> - <input type="number" value="${myDay}" id="day-${id}" class="small-input" min="1" max="31" onchange="this.value = padMax(this.value, this.max);" onclick="this.select()" onkeydown="handleInputKeyDown(event, ${id})"> / - <input type="number" value="${myMonth}" id="month-${id}" class="small-input" min="1" max="12" onchange="this.value = padMax(this.value, this.max);" onclick="this.select()" onkeydown="handleInputKeyDown(event, ${id})"> + <input type="number" step="1" value="${myDay}" id="day-${id}" class="small-input" min="1" max="31" onchange="this.value = padMax(this.value, this.max);" onclick="this.select()" onkeydown="handleInputKeyDown(event, ${id})"> / + <input type="number" step="1" value="${myMonth}" id="month-${id}" class="small-input" min="1" max="12" onchange="this.value = padMax(this.value, this.max);" onclick="this.select()" onkeydown="handleInputKeyDown(event, ${id})"> / + <input type="number" step="1" value="${myYear}" id="year-${id}" class="big-input" min="1000" max="${currentYear}" onchange="this.value = padYear(this.value, this.max, this.min);" onclick="this.select()" onkeydown="handleInputKeyDown(event, ${id}, ${isNew})"> </td> <td data-field="notify" data-value="${notify}"><input type="checkbox" id="notify-${id}" ${notify ? 'checked' : ''}></td> <td data-field="notified" data-value="${notified}"><input type="checkbox" id="notified-${id}" ${notified ? 'checked' : 'disabled'}></td>@@ -271,17 +283,17 @@ const cells = row.getElementsByTagName('td');
const name = getValueFromRow(row, 'name'); const description = getValueFromRow(row, 'description'); - const [day, month] = getValueFromRow(row, 'date').split('/'); - const notify = cells[3].getElementsByTagName('input')[0].checked; - const notified = cells[4].getElementsByTagName('input')[0].checked; + const [day, month, year] = getValueFromRow(row, 'date').split('/'); + const notify = getValueFromRow(row, 'notify'); + const notified = getValueFromRow(row, 'notified'); - row.innerHTML = createInputFields(id, name, description, day, month, notify, notified, false); + row.innerHTML = createInputFields(id, name, description, day, month, year, notify, notified, false); row.classList.add('editing'); } - function cancelEdit(id, name, description, day, month, notify) { + function cancelEdit(id, name, description, day, month, year, notify) { const row = document.getElementById(`occurrence-${id}`); - row.innerHTML = createRow(id, name, description, day, month, notify); + row.innerHTML = createRow(id, name, description, day, month, year, notify); } function saveOccurrence(id) {@@ -289,16 +301,18 @@ const name = document.getElementById(`name-${id}`).value;
const description = document.getElementById(`description-${id}`).value; const day = parseInt(document.getElementById(`day-${id}`).value); const month = parseInt(document.getElementById(`month-${id}`).value); + const year = parseInt(document.getElementById(`year-${id}`).value); const notify = document.getElementById(`notify-${id}`).checked; const notified = document.getElementById(`notified-${id}`).checked; - const isNew = id === '0'; + const isNew = id === 0; const updatedData = { id: isNew ? undefined : id, name: name, description: description, + day: day, month: month, - day: day, + year: isNaN(year) ? null : year, notify: notify, notified: notified };@@ -329,7 +343,7 @@
function addNewOccurrenceRow() { const newRow = mainTable.insertRow(-1); newRow.id = 'new-occurrence'; - newRow.innerHTML = createInputFields('0', '', '', '', '', true, false, true); + newRow.innerHTML = createInputFields('0', '', '', '', '', '', true, false, true); hideAddButton(); }@@ -349,7 +363,7 @@ function updateRow(rowElementId, response) {
const newRow = document.getElementById(rowElementId); response.json().then((res) => { newRow.id = `occurrence-${res.id}`; - newRow.innerHTML = createRow(res.id, res.name, res.description, res.day, res.month, res.notify, res.notified); + newRow.innerHTML = createRow(res.id, res.name, res.description, res.day, res.month, res.year, res.notify, res.notified); newRow.classList.remove('editing'); updateRowDisplay() });@@ -366,21 +380,20 @@ function getValueFromRow(row, field) {
return row.querySelector(`td[data-field="${field}"]`).getAttribute('data-value'); } - function parseDateString(now, dateString) { - const [day, month] = dateString.split('/').map((x) => Number(x)); - return new Date(now.getFullYear(), month - 1, day, 23, 59, 59); + function parseDateString(dateString) { + const [day, month, year] = dateString.split('/').map((x) => Number(x)); + return new Date(currentYear, month - 1, day, 23, 59, 59); } function findNextOccurrence() { if (currentNext !== null) { currentNext.classList.remove('next'); } - const now = new Date(); const occurrenceRows = Array.from(mainTable.querySelectorAll("tr[id]:not(#occurrence-none):not(#new-occurrence)")); const occurrences = occurrenceRows.map((row) => { const id = row.id.split('-')[1]; const dateString = getValueFromRow(row, 'date'); - const date = parseDateString(now, dateString); + const date = parseDateString(dateString); return { id, date }; });
@@ -5,11 +5,16 @@ "embed"
"fmt" "html/template" "log" + "time" "github.com/gin-gonic/gin" ) -func padZero(i int) string { +func calcYear(currentYear, year uint) uint { + return currentYear - year +} + +func padZero(i uint) string { return fmt.Sprintf("%02d", i) }@@ -17,7 +22,10 @@ var (
//go:embed templates/index.html templates embed.FS indexTemplate *template.Template - funcMap = template.FuncMap{"padZero": padZero} + funcMap = template.FuncMap{ + "padZero": padZero, + "calcYear": calcYear, + } ) func ParseTemplates() {@@ -31,15 +39,19 @@ }
func ShowIndexPage(c *gin.Context) { var occurrences []Occurrence - db.Order("month, day").Find(&occurrences) + db.Order("month, day, name").Find(&occurrences) data := struct { Occurrences []Occurrence + CurrentYear uint }{ Occurrences: occurrences, + CurrentYear: uint(time.Now().Year()), } - if indexTemplate.Execute(c.Writer, data) != nil { + err := indexTemplate.Execute(c.Writer, data) + if err != nil { + log.Println(err.Error()) c.String(500, "Internal Server Error") } }