Updated user pages
|
@ -1,3 +1,4 @@
|
||||||
.idea/**
|
.idea/**
|
||||||
holiday-api
|
holiday-api
|
||||||
.env
|
.env
|
||||||
|
**/.DS_Store
|
||||||
|
|
After Width: | Height: | Size: 236 KiB |
|
@ -0,0 +1,203 @@
|
||||||
|
body {
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
background: #f3ebe6;
|
||||||
|
width: 640px;
|
||||||
|
max-width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
background: white;
|
||||||
|
margin: auto;
|
||||||
|
padding: 16px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-image {
|
||||||
|
min-width: 100vw;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
|
max-width: 100vw;
|
||||||
|
max-height: 100vh;
|
||||||
|
|
||||||
|
background: url("/assets/background.jpg");
|
||||||
|
background-position: bottom;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: Quicksand;
|
||||||
|
src: url(fonts/Quicksand-Light.ttf);
|
||||||
|
font-weight: 100 200;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: Quicksand;
|
||||||
|
src: url(fonts/Quicksand-Medium.ttf);
|
||||||
|
font-weight: 300 300;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: Quicksand;
|
||||||
|
src: url(fonts/Quicksand-Regular.ttf);
|
||||||
|
font-weight: 400 500;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: Quicksand;
|
||||||
|
src: url(fonts/Quicksand-Bold.ttf);
|
||||||
|
font-weight: 800 900;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: Quicksand;
|
||||||
|
src: url(fonts/Quicksand-SemiBold.ttf);
|
||||||
|
font-weight: 600 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-family: Quicksand, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
h1 a {
|
||||||
|
all: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.form-field label {
|
||||||
|
display: block;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.form-field input {
|
||||||
|
width: 300px;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid #bbb;
|
||||||
|
|
||||||
|
font-size: 20px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field input:focus {
|
||||||
|
color: cadetblue;
|
||||||
|
border-bottom: 1px solid cadetblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field input:not(focus) {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field label {
|
||||||
|
display: block;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.form-field select {
|
||||||
|
all: unset;
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid #bbb;
|
||||||
|
|
||||||
|
font-size: 20px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field select:focus {
|
||||||
|
color: cadetblue;
|
||||||
|
border-bottom: 1px solid cadetblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field select:not(focus) {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field:focus-within label {
|
||||||
|
color: cadetblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.button.secondary, button.primary {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background-color: cadetblue;
|
||||||
|
font-family: Quicksand, Arial, sans-serif;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.secondary, button.primary:hover {
|
||||||
|
filter: contrast(140%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.secondary, button.secondary {
|
||||||
|
padding: 12px 16px;
|
||||||
|
background-color: white;
|
||||||
|
font-family: Quicksand, Arial, sans-serif;
|
||||||
|
border: 1px solid cadetblue;
|
||||||
|
color: cadetblue;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.secondary:hover, button.secondary:hover {
|
||||||
|
filter: contrast(140%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*index.html*/
|
||||||
|
|
||||||
|
.search-dialog {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 480px) {
|
||||||
|
.search-dialog {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 10vw;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 380px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*results.html*/
|
||||||
|
.results-dialog {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-dialog table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-dialog table th, .results-dialog table td {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 480px) {
|
||||||
|
.results-dialog {
|
||||||
|
position: relative;
|
||||||
|
margin: auto;
|
||||||
|
width: 600px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.results td {
|
||||||
|
padding: 0.4em 0;
|
||||||
|
}
|
|
@ -1,6 +1,19 @@
|
||||||
const dialogContainerId = "#dialog-container"
|
const dialogContainerId = "#dialog-container"
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
|
document.querySelectorAll(".dropdown").forEach(el => {
|
||||||
|
const action = el.querySelector(".dropdown-action");
|
||||||
|
const content = el.querySelector(".dropdown-content");
|
||||||
|
|
||||||
|
console.log(action, content, el.offsetWidth, content.offsetWidth);
|
||||||
|
|
||||||
|
action.addEventListener('click', () => {
|
||||||
|
content.classList.toggle('selected');
|
||||||
|
console.log(content.offsetWidth - action.offsetWidth);
|
||||||
|
content.style.marginLeft = `-${(content.offsetWidth - action.offsetWidth)}px`;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// configure radio button groups
|
// configure radio button groups
|
||||||
document.querySelectorAll("section.radio-group").forEach(el => {
|
document.querySelectorAll("section.radio-group").forEach(el => {
|
||||||
const submittedInput = el.querySelector("input[type=hidden]");
|
const submittedInput = el.querySelector("input[type=hidden]");
|
||||||
|
@ -23,7 +36,13 @@ window.addEventListener('load', () => {
|
||||||
let response = await fetch(btn.dataset.url);
|
let response = await fetch(btn.dataset.url);
|
||||||
if(response.ok) {
|
if(response.ok) {
|
||||||
response = await response.text();
|
response = await response.text();
|
||||||
const container = document.querySelector(dialogContainerId);
|
let container = document.querySelector(dialogContainerId);
|
||||||
|
if(container == null) {
|
||||||
|
const node = document.createElement('div');
|
||||||
|
node.id = "dialog-container";
|
||||||
|
document.querySelector("body").appendChild(node);
|
||||||
|
container = document.querySelector(dialogContainerId);
|
||||||
|
}
|
||||||
container.innerHTML = response;
|
container.innerHTML = response;
|
||||||
const dialogReference = document.querySelector(selector);
|
const dialogReference = document.querySelector(selector);
|
||||||
dialogReference?.showModal();
|
dialogReference?.showModal();
|
||||||
|
|
Before Width: | Height: | Size: 795 B After Width: | Height: | Size: 791 B |
Before Width: | Height: | Size: 807 B After Width: | Height: | Size: 804 B |
Before Width: | Height: | Size: 602 B After Width: | Height: | Size: 599 B |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 696 B After Width: | Height: | Size: 693 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
@ -1,5 +1,11 @@
|
||||||
/* cleanup */
|
/* cleanup */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary: #9222dd;
|
||||||
|
--primary-darker: #6400a2;
|
||||||
|
--primary-contrast: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
@ -10,22 +16,26 @@
|
||||||
header {
|
header {
|
||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #666;
|
background: var(--primary);
|
||||||
|
color: var(--primary-contrast);
|
||||||
}
|
}
|
||||||
header h1 {
|
header h1 {
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
header a {
|
header a {
|
||||||
color: #fff;
|
color: var(--primary-contrast);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
all: unset;
|
all: unset;
|
||||||
padding: 0.25em 0.5em;
|
padding: 0.25em 0.5em;
|
||||||
background: #dddddd;
|
color: var(--primary-contrast);
|
||||||
border: solid 1px #cccccc;
|
background: var(--primary);
|
||||||
|
border: solid 1px var(--primary-darker);
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
|
|
||||||
|
--text: var(--primary-contrast);
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
all: unset;
|
all: unset;
|
||||||
|
@ -63,6 +73,10 @@ nav a.selected, nav button.selected {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
table:not(.clean) {
|
table:not(.clean) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -71,7 +85,8 @@ table th, table td {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-spacing: 0;
|
||||||
|
/*border-collapse: collapse;*/
|
||||||
}
|
}
|
||||||
table thead * {
|
table thead * {
|
||||||
background: #666;
|
background: #666;
|
||||||
|
@ -184,9 +199,9 @@ section.radio-group div input:checked+label {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.icon {
|
img.icon, button.icon svg {
|
||||||
height: 1.5em;
|
height: 1.2em;
|
||||||
width: 1.5em;
|
width: 1.2em;
|
||||||
}
|
}
|
||||||
button.icon, a.icon {
|
button.icon, a.icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -194,6 +209,11 @@ button.icon, a.icon {
|
||||||
gap: 0.3em;
|
gap: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.icon svg, button.icon svg * {
|
||||||
|
fill: var(--text, var(--primary-contrast));
|
||||||
|
color: var(--text, var(--primary-contrast));
|
||||||
|
stroke: var(--text, var(--primary-contrast));
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
19
handlers.go
|
@ -1,12 +1,15 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/csv"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"holiday-api/holiday"
|
"holiday-api/holiday"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,9 +29,9 @@ func getHolidays(service holiday.HolidayService) gin.HandlerFunc {
|
||||||
|
|
||||||
holidays, err := service.Find(search, paging)
|
holidays, err := service.Find(search, paging)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render(c, http.StatusNotFound, ErrorResponse{Created: time.Now(), Message: "failed fetching holidays"})
|
render(c, http.StatusNotFound, ErrorResponse{Created: time.Now(), Message: "failed fetching holidays"}, nil)
|
||||||
} else {
|
} else {
|
||||||
render(c, http.StatusOK, mapHolidays(holidays))
|
render(c, http.StatusOK, mapHolidays(holidays), search.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,6 +57,18 @@ type HolidayResponse struct {
|
||||||
Holidays []HolidayItemResponse `json:"holidays"`
|
Holidays []HolidayItemResponse `json:"holidays"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h HolidayResponse) CSV() []byte {
|
||||||
|
buffer := bytes.Buffer{}
|
||||||
|
csvWriter := csv.NewWriter(&buffer)
|
||||||
|
csvWriter.Write([]string{"id", "date", "name", "description", "isStateHoliday", "isReligiousHoliday"})
|
||||||
|
|
||||||
|
for _, item := range h.Holidays {
|
||||||
|
csvWriter.Write([]string{item.Id.String(), item.Date.String(), item.Name, item.Description, strconv.FormatBool(item.IsStateHoliday), strconv.FormatBool(item.IsReligiousHoliday)})
|
||||||
|
}
|
||||||
|
csvWriter.Flush()
|
||||||
|
return buffer.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
type HolidayItemResponse struct {
|
type HolidayItemResponse struct {
|
||||||
XMLName xml.Name `json:"-" xml:"Holiday"`
|
XMLName xml.Name `json:"-" xml:"Holiday"`
|
||||||
Id uuid.UUID `json:"id" xml:"id,attr"`
|
Id uuid.UUID `json:"id" xml:"id,attr"`
|
||||||
|
|
|
@ -34,6 +34,7 @@ type Search struct {
|
||||||
RangeEnd *time.Time `form:"range_end" time_format:"2006-01-02"`
|
RangeEnd *time.Time `form:"range_end" time_format:"2006-01-02"`
|
||||||
StateHoliday string `form:"state_holiday,omitempty" binding:"omitempty"`
|
StateHoliday string `form:"state_holiday,omitempty" binding:"omitempty"`
|
||||||
ReligiousHoliday string `form:"religious_holiday,omitempty" binding:"omitempty"`
|
ReligiousHoliday string `form:"religious_holiday,omitempty" binding:"omitempty"`
|
||||||
|
Type *string `form:"type,omitempty" binding:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Search) IsStateHoliday() *bool {
|
func (s Search) IsStateHoliday() *bool {
|
||||||
|
|
24
main.go
|
@ -62,16 +62,20 @@ func main() {
|
||||||
setupAdminDashboard(g.Group("/admin"), holidayService, countryService, yearService)
|
setupAdminDashboard(g.Group("/admin"), holidayService, countryService, yearService)
|
||||||
|
|
||||||
g.GET("/", func(c *gin.Context) {
|
g.GET("/", func(c *gin.Context) {
|
||||||
year := time.Now().Year()
|
search := holiday.Search{Country: "HR", Year: nil}
|
||||||
search := holiday.Search{Country: "HR", Year: &year}
|
|
||||||
if err := c.ShouldBindQuery(&search); err != nil {
|
if err := c.ShouldBindQuery(&search); err != nil {
|
||||||
c.AbortWithError(http.StatusBadRequest, err)
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100})
|
|
||||||
countries, _ := countryService.Find()
|
countries, _ := countryService.Find()
|
||||||
years, _ := yearService.Find()
|
years, _ := yearService.Find()
|
||||||
c.HTML(http.StatusOK, "index.gohtml", gin.H{"Years": years, "Countries": countries, "Search": search, "Holidays": mapHolidays(holidays).Holidays})
|
if search.Year == nil {
|
||||||
|
c.HTML(http.StatusOK, "index.gohtml", gin.H{"Years": years, "Countries": countries, "Search": search})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100})
|
||||||
|
c.HTML(http.StatusOK, "results.gohtml", gin.H{"Years": years, "Countries": countries, "Search": search, "Holidays": mapHolidays(holidays).Holidays})
|
||||||
})
|
})
|
||||||
g.GET("/documentation", func(c *gin.Context) {
|
g.GET("/documentation", func(c *gin.Context) {
|
||||||
countries, _ := countryService.Find()
|
countries, _ := countryService.Find()
|
||||||
|
@ -113,9 +117,11 @@ func loadTemplates(g *gin.Engine) {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
"importSvg": IncludeHTML,
|
||||||
})
|
})
|
||||||
g.LoadHTMLFiles(
|
g.LoadHTMLFiles(
|
||||||
"templates/index.gohtml",
|
"templates/index.gohtml",
|
||||||
|
"templates/results.gohtml",
|
||||||
"templates/search.gohtml",
|
"templates/search.gohtml",
|
||||||
"templates/documentation.gohtml",
|
"templates/documentation.gohtml",
|
||||||
|
|
||||||
|
@ -132,6 +138,16 @@ func loadTemplates(g *gin.Engine) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IncludeHTML(path string) template.HTML {
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("includeHTML - error reading file: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.HTML(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
func setupAdminDashboard(adminDashboard *gin.RouterGroup, service holiday.HolidayService, countryService holiday.CountryService, yearService holiday.YearService) {
|
func setupAdminDashboard(adminDashboard *gin.RouterGroup, service holiday.HolidayService, countryService holiday.CountryService, yearService holiday.YearService) {
|
||||||
adminDashboard.Use(gin.BasicAuth(loadAuth()))
|
adminDashboard.Use(gin.BasicAuth(loadAuth()))
|
||||||
|
|
||||||
|
|
28
render.go
|
@ -4,12 +4,34 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func render[T any](c *gin.Context, status int, response T) {
|
type CSV interface {
|
||||||
switch c.GetHeader("accept") {
|
CSV() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func render(c *gin.Context, status int, response any, contentType *string) {
|
||||||
|
value := c.GetHeader("accept")
|
||||||
|
if contentType != nil {
|
||||||
|
switch *contentType {
|
||||||
|
case "xml":
|
||||||
|
value = "text/xml"
|
||||||
|
case "json":
|
||||||
|
value = "application/json"
|
||||||
|
case "csv":
|
||||||
|
value = "text/csv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch value {
|
||||||
|
case "text/csv":
|
||||||
|
if csvResponse, ok := response.(CSV); ok {
|
||||||
|
c.Data(200, value+"; charset=utf-8", csvResponse.CSV())
|
||||||
|
} else {
|
||||||
|
c.Header("content-type", "application/json; charset=utf-8")
|
||||||
|
c.JSON(status, response)
|
||||||
|
}
|
||||||
case "application/xml":
|
case "application/xml":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "text/xml":
|
case "text/xml":
|
||||||
c.Header("content-type", c.GetHeader("accept")+"; charset=utf-8")
|
c.Header("content-type", value+"; charset=utf-8; header=present;")
|
||||||
c.XML(status, response)
|
c.XML(status, response)
|
||||||
case "application/json":
|
case "application/json":
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
</section>
|
</section>
|
||||||
<section class="actions">
|
<section class="actions">
|
||||||
<button class="icon primary" type="submit">
|
<button class="icon primary" type="submit">
|
||||||
<img class="icon" src="/assets/images/search.svg">
|
{{ importSvg "/assets/icons/search.svg"}}
|
||||||
<span>Search</span>
|
<span>Search</span>
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
@ -85,11 +85,11 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{$entry.Name}}</td>
|
<td>{{$entry.Name}}</td>
|
||||||
<td>{{$entry.Date.Format "2006-01-02"}}</td>
|
<td>{{$entry.Date.Format "2006-01-02"}}</td>
|
||||||
<td><img class="icon" src="{{if $entry.IsStateHoliday}}/assets/images/done-v.svg{{else}}/assets/images/close-x.svg{{end}}"></td>
|
<td><img class="icon" src="{{if $entry.IsStateHoliday}}/assets/icons/done-v.svg{{else}}/assets/icons/close-x.svg{{end}}"></td>
|
||||||
<td><img class="icon" src="{{if $entry.IsReligiousHoliday}}/assets/images/done-v.svg{{else}}/assets/images/close-x.svg{{end}}"></td>
|
<td><img class="icon" src="{{if $entry.IsReligiousHoliday}}/assets/icons/done-v.svg{{else}}/assets/icons/close-x.svg{{end}}"></td>
|
||||||
<td>
|
<td>
|
||||||
<button data-type="dialog" data-trigger="#update-card" data-url="/admin/dialogs/edit-holiday?id={{$entry.Id}}" class="icon"><img class="icon" src="/assets/images/edit.svg"></button>
|
<button data-type="dialog" data-trigger="#update-card" data-url="/admin/dialogs/edit-holiday?id={{$entry.Id}}" class="icon"><img class="icon" src="/assets/icons/edit.svg"></button>
|
||||||
<button data-type="dialog" data-trigger="#delete-card" data-url="/admin/dialogs/delete-holiday?id={{$entry.Id}}" class="icon"><img class="icon" src="/assets/images/trash-delete.svg"></button>
|
<button data-type="dialog" data-trigger="#delete-card" data-url="/admin/dialogs/delete-holiday?id={{$entry.Id}}" class="icon"><img class="icon" src="/assets/icons/trash-delete.svg"></button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -46,8 +46,8 @@
|
||||||
<td>{{$entry.IsoName}}</td>
|
<td>{{$entry.IsoName}}</td>
|
||||||
<td>{{$entry.Name}}</td>
|
<td>{{$entry.Name}}</td>
|
||||||
<td>
|
<td>
|
||||||
<button type="button" data-type="dialog" data-trigger="#update-card" data-url="/admin/dialogs/edit-country?id={{$entry.Id}}" class="clean icon"><img class="icon" src="/assets/images/edit.svg"></button>
|
<button type="button" data-type="dialog" data-trigger="#update-card" data-url="/admin/dialogs/edit-country?id={{$entry.Id}}" class="clean icon"><img class="icon" src="/assets/icons/edit.svg"></button>
|
||||||
<button type="button" data-type="dialog" data-trigger="#delete-card" data-url="/admin/dialogs/delete-country?id={{$entry.Id}}" class="clean icon"><img class="icon" src="/assets/images/trash-delete.svg"></button>
|
<button type="button" data-type="dialog" data-trigger="#delete-card" data-url="/admin/dialogs/delete-country?id={{$entry.Id}}" class="clean icon"><img class="icon" src="/assets/icons/trash-delete.svg"></button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<dialog class="card" data-closeable id="check-is-a-holiday">
|
<dialog class="card" data-closeable id="check-is-a-holiday">
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
<h2 style="margin-right: 1em;">Is it a holiday?</h2>
|
<h2 style="margin-right: 1em;">Is it a holiday?</h2>
|
||||||
<button onclick="closeDialog('#check-is-a-holiday')" class="clean icon"><img class="icon" src="/assets/images/close-x.svg"></button>
|
<button onclick="closeDialog('#check-is-a-holiday')" class="clean icon"><img class="icon" src="/assets/icons/close-x.svg"></button>
|
||||||
</div>
|
</div>
|
||||||
<form method="get" action="/search">
|
<form method="get" action="/search">
|
||||||
<section>
|
<section>
|
||||||
|
|
|
@ -1,95 +1,32 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Holiday-api</title>
|
<title>Holiday-api</title>
|
||||||
<link rel="stylesheet" href="/assets/style.css">
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="/assets/global.css">
|
||||||
<script src="/assets/global.js"></script>
|
<script src="/assets/global.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="dialog-container"></div>
|
<div class="background-image">
|
||||||
<header>
|
<article class="dialog search-dialog">
|
||||||
<section class="container">
|
|
||||||
<h1><a href="/">Holiday-api</a></h1>
|
<h1><a href="/">Holiday-api</a></h1>
|
||||||
</section>
|
<form action="/">
|
||||||
</header>
|
<div class="form-field">
|
||||||
<nav>
|
<label for="year">Za godinu:</label>
|
||||||
<section class="container">
|
<select id="year" name="year">
|
||||||
<a class="selected" href="#">Search</a>
|
{{range $entry := .Years}}
|
||||||
<a href="/documentation">Documentation</a>
|
<option {{if intpeq $.Search.Year $entry}}selected{{end}} value="{{$entry}}">{{$entry}}</option>
|
||||||
<button data-type="dialog" data-trigger="#check-is-a-holiday" data-url="/dialogs/check-is-a-holiday">Check is a holiday</button>
|
{{end}}
|
||||||
</section>
|
</select>
|
||||||
</nav>
|
</div>
|
||||||
<main>
|
<div class="button-actions">
|
||||||
<section id="search">
|
<button class="primary">Pretraži</button>
|
||||||
<article class="card">
|
</div>
|
||||||
<form action="/" method="get">
|
</form>
|
||||||
<section>
|
</article>
|
||||||
<label for="country">Country:</label>
|
</div>
|
||||||
<select id="country" name="country">
|
|
||||||
{{range $entry := .Countries}}
|
|
||||||
<option {{if eq $.Search.Country $entry.IsoName}}selected{{end}} value="{{$entry.IsoName}}">{{$entry.Name}}</option>
|
|
||||||
{{end}}
|
|
||||||
</select>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<label for="year">Year:</label>
|
|
||||||
<select id="year" name="year">
|
|
||||||
{{range $entry := .Years}}
|
|
||||||
<option {{if intpeq $.Search.Year $entry}}selected{{end}} value="{{$entry}}">{{$entry}}</option>
|
|
||||||
{{end}}
|
|
||||||
</select>
|
|
||||||
</section>
|
|
||||||
<section class="radio-group">
|
|
||||||
<label>Is state holiday:</label>
|
|
||||||
<div>
|
|
||||||
<input type="radio" value="true" name="sh" id="sh_true"><label for="sh_true">True
|
|
||||||
</label><input type="radio" value="false" name="sh" id="sh_false"><label for="sh_false">False
|
|
||||||
</label><input type="radio" value="" name="sh" id="sh_any"><label for="sh_any">All</label>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" value="{{.Search.StateHoliday}}" name="state_holiday">
|
|
||||||
</section>
|
|
||||||
<section class="radio-group">
|
|
||||||
<label>Is religious holiday:</label>
|
|
||||||
<div>
|
|
||||||
<input type="radio" value="true" name="rh" id="rh_true"><label for="rh_true">True
|
|
||||||
</label><input type="radio" value="false" name="rh" id="rh_false"><label for="rh_false">False
|
|
||||||
</label><input type="radio" value="" name="rh" id="rh_any"><label for="rh_any">All</label>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" value="{{.Search.ReligiousHoliday}}" name="religious_holiday">
|
|
||||||
</section>
|
|
||||||
<section class="actions">
|
|
||||||
<button class="icon primary" type="submit">
|
|
||||||
<img class="icon" src="/assets/images/search.svg">
|
|
||||||
<span>Search</span>
|
|
||||||
</button>
|
|
||||||
</section>
|
|
||||||
</form>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
<section id="results">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>State</th>
|
|
||||||
<th>Religious</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{range $entry := .Holidays}}
|
|
||||||
<tr>
|
|
||||||
<td>{{$entry.Name}}</td>
|
|
||||||
<td>{{$entry.Date.Format "2006-01-02"}}</td>
|
|
||||||
<td><img class="icon" src="{{if $entry.IsStateHoliday}}/assets/images/done-v.svg{{else}}/assets/images/close-x.svg{{end}}"></td>
|
|
||||||
<td><img class="icon" src="{{if $entry.IsReligiousHoliday}}/assets/images/done-v.svg{{else}}/assets/images/close-x.svg{{end}}"></td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -0,0 +1,88 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Holiday-api | {{deferint $.Search.Year}}</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="icon" href="favicon.ico" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="/assets/global.css">
|
||||||
|
<script src="/assets/global.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
min-width: 160px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content.selected {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.dropdown-content a {
|
||||||
|
all: unset;
|
||||||
|
display: block;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
}
|
||||||
|
.dropdown-content a:hover {
|
||||||
|
background: rgba(95, 158, 160, 0.2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="background-image" style="position: fixed;"></div>
|
||||||
|
<article class="dialog results-dialog">
|
||||||
|
<section style="margin-bottom: 1em;">
|
||||||
|
<h1><a href="/">Holiday-api</a> | {{deferint $.Search.Year}}</h1>
|
||||||
|
<form style="display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 1em" action="/">
|
||||||
|
<div style="flex-grow: 1;"></div>
|
||||||
|
<div class="form-field" style="width: auto;" >
|
||||||
|
<label for="year">Za godinu:</label>
|
||||||
|
<select id="year" name="year" style="width: 150px">
|
||||||
|
{{range $entry := .Years}}
|
||||||
|
<option {{if intpeq $.Search.Year $entry}}selected{{end}} value="{{$entry}}">{{$entry}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="button-actions">
|
||||||
|
<button class="primary">Pretraži</button>
|
||||||
|
</div>
|
||||||
|
<div class="button-actions" style="margin-left: auto">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button type="button" class="secondary dropdown-action">Preuzmi</button>
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a target="_blank" href="/api/v1/holidays?country=HR&type=csv&year={{deferint $.Search.Year}}">Kao csv</a>
|
||||||
|
<a target="_blank" href="/api/v1/holidays?country=HR&type=json&year={{deferint $.Search.Year}}">Kao json</a>
|
||||||
|
<a target="_blank" href="/api/v1/holidays?country=HR&type=xml&year={{deferint $.Search.Year}}">Kao xml</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<table class="results">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Ime</th>
|
||||||
|
<th>Datum</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range $entry := .Holidays}}
|
||||||
|
<tr>
|
||||||
|
<td>{{$entry.Name}}</td>
|
||||||
|
<td>{{$entry.Date.Format "02.01.2006."}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</article>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -60,11 +60,11 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Is state holiday: </th>
|
<th>Is state holiday: </th>
|
||||||
<td><img class="icon" src="{{if $entry.IsStateHoliday}}/assets/images/done-v.svg{{else}}/assets/images/close-x.svg{{end}}"></td>
|
<td><img class="icon" src="{{if $entry.IsStateHoliday}}/assets/icons/done-v.svg{{else}}/assets/images/close-x.svg{{end}}"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Is religious holiday: </th>
|
<th>Is religious holiday: </th>
|
||||||
<td><img class="icon" src="{{if $entry.IsReligiousHoliday}}/assets/images/done-v.svg{{else}}/assets/images/close-x.svg{{end}}"></td>
|
<td><img class="icon" src="{{if $entry.IsReligiousHoliday}}/assets/icons/done-v.svg{{else}}/assets/images/close-x.svg{{end}}"></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|