Compare commits
2 Commits
76ef62bb96
...
45b220c69f
Author | SHA1 | Date |
---|---|---|
Borna Rajković | 45b220c69f | |
Borna Rajković | 8fbdffc965 |
Binary file not shown.
After Width: | Height: | Size: 633 KiB |
|
@ -4,8 +4,62 @@ body {
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
:root {
|
||||
--background-color: #f3ebe6;
|
||||
--background: #ffffff;
|
||||
--color: #000;
|
||||
--border: #dddddd;
|
||||
|
||||
--form-label: #999;
|
||||
--form-border: #bbb;
|
||||
--form-focus: #333;
|
||||
|
||||
--dialog-border: #5f9ea07f;
|
||||
--dialog-backdrop: #979f9f63;
|
||||
|
||||
--dropdown-background: #f0f0f0;
|
||||
|
||||
--primary: #5f9ea0;
|
||||
--primary-contrast: #ffffff;
|
||||
--primary-accent: #6a7579;
|
||||
|
||||
--code-background: #aaa;
|
||||
--code-color: #000;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background-color: #f3ebe6;
|
||||
--background: #333;
|
||||
--color: #f0f0f0;
|
||||
--border: #666;
|
||||
|
||||
--form-label: #999;
|
||||
--form-border: #555;
|
||||
--form-focus: #ccc;
|
||||
|
||||
--dialog-border: rgba(16, 173, 173, 0.5);
|
||||
--dialog-backdrop: rgba(1, 70, 70, 0.39);
|
||||
|
||||
--dropdown-background: #222;
|
||||
|
||||
--primary: #0e8d93;
|
||||
--primary-contrast: #333;
|
||||
--primary-accent: #08363a;
|
||||
|
||||
--code-background: #444;
|
||||
--code-color: #d0d0d0;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
color: var(--color);
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
|
||||
main {
|
||||
background: #f3ebe6;
|
||||
background: var(--background-color);
|
||||
width: 640px;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
|
@ -15,11 +69,11 @@ header {
|
|||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 1em;
|
||||
background: white;
|
||||
background: var(--background);
|
||||
position: sticky;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
@ -29,8 +83,8 @@ header {
|
|||
|
||||
.dialog {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e0e0e0;
|
||||
background: white;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--background);
|
||||
margin: auto;
|
||||
padding: 1em 0.5em;
|
||||
}
|
||||
|
@ -55,6 +109,21 @@ header {
|
|||
background-size: cover;
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.background-image {
|
||||
min-width: 100vw;
|
||||
min-height: 100dvh;
|
||||
|
||||
max-width: 100vw;
|
||||
max-height: 100dvh;
|
||||
background: url("/assets/background-dark.jpg");
|
||||
background-position: bottom;
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: Quicksand;
|
||||
src: url(fonts/Quicksand-Light.ttf);
|
||||
|
@ -108,13 +177,13 @@ h3 {
|
|||
}
|
||||
.form-field label {
|
||||
display: block;
|
||||
color: #999;
|
||||
color: var(--form-label);
|
||||
}
|
||||
.form-field input, .form-field textarea {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-bottom: 1px solid #bbb;
|
||||
border-bottom: 1px solid var(--form-border);
|
||||
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
|
@ -127,47 +196,57 @@ h3 {
|
|||
.form-field input[type=checkbox] {
|
||||
width: inherit;
|
||||
margin-left: 1em;
|
||||
accent-color: #6a7579;
|
||||
accent-color: var(--primary-accent);
|
||||
}
|
||||
|
||||
.form-field input:focus, .form-field textarea:focus {
|
||||
color: cadetblue;
|
||||
border-bottom: 1px solid cadetblue;
|
||||
color: var(--primary);
|
||||
border-bottom: 1px solid var(--primary);
|
||||
}
|
||||
|
||||
.form-field input:not(focus), .form-field textarea:not(focus) {
|
||||
color: #333;
|
||||
color: var(--form-focus);
|
||||
}
|
||||
|
||||
.form-field select {
|
||||
all: unset;
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-bottom: 1px solid #bbb;
|
||||
border-bottom: 1px solid var(--form-border);
|
||||
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.form-field select:focus {
|
||||
color: cadetblue;
|
||||
border-bottom: 1px solid cadetblue;
|
||||
color: var(--primary);
|
||||
border-bottom: 1px solid var(--primary);
|
||||
}
|
||||
|
||||
.form-field select:not(focus) {
|
||||
color: #333;
|
||||
color: var(--form-focus);
|
||||
}
|
||||
|
||||
.form-field:focus-within label {
|
||||
color: cadetblue;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.button.primary, button.primary {
|
||||
padding: 0.75em 1em;
|
||||
background-color: cadetblue;
|
||||
background-color: var(--primary);
|
||||
font-family: Quicksand, Arial, sans-serif;
|
||||
border: none;
|
||||
color: white;
|
||||
color: var(--primary-contrast);
|
||||
font-size: 1rem;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.button.secondary, button.secondary {
|
||||
padding: 0.75em 1em;
|
||||
background-color: var(--primary-contrast);
|
||||
font-family: Quicksand, Arial, sans-serif;
|
||||
border: 1px solid var(--primary);
|
||||
color: var(--primary);
|
||||
font-size: 1rem;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
@ -175,19 +254,17 @@ h3 {
|
|||
.button.primary, button.primary:hover {
|
||||
filter: contrast(140%);
|
||||
}
|
||||
|
||||
.button.secondary, button.secondary {
|
||||
padding: 0.75em 1em;
|
||||
background-color: white;
|
||||
font-family: Quicksand, Arial, sans-serif;
|
||||
border: 1px solid cadetblue;
|
||||
color: cadetblue;
|
||||
font-size: 1rem;
|
||||
margin-top: 1em;
|
||||
.button.secondary:hover, button.secondary:hover {
|
||||
filter: contrast(80%);
|
||||
}
|
||||
|
||||
.button.secondary:hover, button.secondary:hover {
|
||||
filter: contrast(140%);
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.button.primary, button.primary:hover {
|
||||
filter: brightness(140%);
|
||||
}
|
||||
.button.secondary:hover, button.secondary:hover {
|
||||
filter: brightness(140%);
|
||||
}
|
||||
}
|
||||
|
||||
/*index.html*/
|
||||
|
@ -197,7 +274,6 @@ h3 {
|
|||
width: 100%;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 480px) {
|
||||
|
@ -218,7 +294,6 @@ h3 {
|
|||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding-bottom: 40px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.results-dialog table {
|
||||
|
@ -249,7 +324,6 @@ h3 {
|
|||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 480px) {
|
||||
|
@ -263,7 +337,8 @@ h3 {
|
|||
|
||||
.documentation-dialog #result-content {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
background: #aaa;
|
||||
background: var(--code-background);
|
||||
color: var(--code-color);
|
||||
padding: 1em;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
@ -280,9 +355,9 @@ section.radio-group div label {
|
|||
display: inline-block;
|
||||
padding: 0.75em 1em;
|
||||
border: none;
|
||||
color: black;
|
||||
color: var(--color);
|
||||
font-size: 1rem;
|
||||
background: #ddd;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
section.radio-group div label {
|
||||
|
@ -303,8 +378,8 @@ section.radio-group div label:last-of-type {
|
|||
}
|
||||
|
||||
section.radio-group div input:checked+label {
|
||||
background: cadetblue;
|
||||
color: white;
|
||||
background: var(--primary);
|
||||
color: var(--primary-contrast);
|
||||
}
|
||||
|
||||
.documentation-dialog .range-selector {
|
||||
|
@ -362,7 +437,18 @@ section.radio-group div input:checked+label {
|
|||
}
|
||||
}
|
||||
|
||||
img.icon, button.icon svg {
|
||||
.edit-icon {
|
||||
mask: url(/assets/icons/edit.svg) no-repeat;
|
||||
mask-size: contain;
|
||||
background: var(--color);
|
||||
}
|
||||
.delete-icon {
|
||||
mask: url(/assets/icons/trash-delete.svg) no-repeat;
|
||||
mask-size: contain;
|
||||
background: var(--color);
|
||||
}
|
||||
|
||||
img.icon, button.icon svg, span.icon {
|
||||
height: 1.2em;
|
||||
width: 1.2em;
|
||||
}
|
||||
|
@ -378,16 +464,16 @@ button.icon, a.icon {
|
|||
}
|
||||
|
||||
button.icon svg, button.icon svg * {
|
||||
fill: white;
|
||||
color: white;
|
||||
stroke: white;
|
||||
fill: var(--primary-contrast);
|
||||
color: var(--primary-contrast);
|
||||
stroke: var(--primary-contrast);
|
||||
}
|
||||
|
||||
dialog {
|
||||
border: 1px solid rgba(95, 158, 160, 0.5);
|
||||
border: 1px solid var(--dialog-border);
|
||||
}
|
||||
dialog::backdrop {
|
||||
background: rgba(151, 159, 159, 0.39);
|
||||
background: var(--dialog-backdrop);
|
||||
}
|
||||
dialog {
|
||||
width: 400px;
|
||||
|
|
|
@ -37,22 +37,22 @@ func (s *HolidayService) FindById(id uuid.UUID) (Holiday, error) {
|
|||
|
||||
func (s *HolidayService) findByDate(date time.Time, isState *bool, isReligious *bool, country string) ([]Holiday, error) {
|
||||
var holidays []Holiday
|
||||
return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE "date" = $1 AND country = $2 `+s.filter(isState, isReligious)+";", date, country)
|
||||
return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE "date" = $1 AND country = $2 `+s.filter(isState, isReligious)+" ORDER BY \"date\";", date, country)
|
||||
}
|
||||
|
||||
func (s *HolidayService) findForRange(rangeStart time.Time, rangeEnd time.Time, isState *bool, isReligious *bool, country string) ([]Holiday, error) {
|
||||
var holidays []Holiday
|
||||
return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE "date" BETWEEN $1 AND $2 AND country = $3`+s.filter(isState, isReligious)+";", rangeStart, rangeEnd, country)
|
||||
return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE "date" BETWEEN $1 AND $2 AND country = $3`+s.filter(isState, isReligious)+" ORDER BY \"date\";", rangeStart, rangeEnd, country)
|
||||
}
|
||||
|
||||
func (s *HolidayService) findByYear(year int, isState *bool, isReligious *bool, country string) ([]Holiday, error) {
|
||||
var holidays []Holiday
|
||||
return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE extract(year from "date") = $1 AND country = $2 `+s.filter(isState, isReligious)+";", year, country)
|
||||
return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE extract(year from "date") = $1 AND country = $2 `+s.filter(isState, isReligious)+" ORDER BY \"date\";", year, country)
|
||||
}
|
||||
|
||||
func (s *HolidayService) find(isState *bool, isReligious *bool, country string) ([]Holiday, error) {
|
||||
var holidays []Holiday
|
||||
return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE country = $1 `+s.filter(isState, isReligious)+";", country)
|
||||
return holidays, s.DB.Select(&holidays, `SELECT * FROM "holiday" WHERE country = $1 `+s.filter(isState, isReligious)+" ORDER BY \"date\";", country)
|
||||
}
|
||||
|
||||
func (s *HolidayService) paginate(holidays []Holiday, paging Paging) []Holiday {
|
||||
|
@ -102,6 +102,23 @@ func (s *HolidayService) Delete(id uuid.UUID) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *HolidayService) Copy(country string, from int, to int) error {
|
||||
holidays, err := s.findByYear(from, nil, nil, country)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var diff = to - from
|
||||
|
||||
for _, holiday := range holidays {
|
||||
holiday.Date = holiday.Date.AddDate(diff, 0, 0)
|
||||
_, err := s.Create(holiday)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDateOrDefault(date *time.Time, year int) time.Time {
|
||||
if date == nil {
|
||||
return time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
|
29
main.go
29
main.go
|
@ -110,6 +110,8 @@ func loadTemplates(g *gin.Engine) {
|
|||
"templates/admin_dashboard.gohtml",
|
||||
"templates/countries.gohtml",
|
||||
|
||||
"templates/dialogs/copy-year.gohtml",
|
||||
|
||||
"templates/dialogs/add-holiday.gohtml",
|
||||
"templates/dialogs/edit-holiday.gohtml",
|
||||
"templates/dialogs/delete-holiday.gohtml",
|
||||
|
@ -189,6 +191,24 @@ func setupAdminDashboard(adminDashboard *gin.RouterGroup, service holiday.Holida
|
|||
c.Redirect(http.StatusSeeOther, "/admin?country="+request.Country+"&year="+strconv.FormatInt(int64(request.Date.Year()), 10))
|
||||
}
|
||||
})
|
||||
adminDashboard.POST("/holidays/copy", func(c *gin.Context) {
|
||||
request := struct {
|
||||
From int `form:"from"`
|
||||
To int `form:"to"`
|
||||
Country string `form:"country" binding:"len=2"`
|
||||
}{}
|
||||
if err := c.ShouldBind(&request); err != nil {
|
||||
c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
err := service.Copy(request.Country, request.From, request.To)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusInternalServerError, err)
|
||||
} else {
|
||||
c.Redirect(http.StatusSeeOther, "/admin?country="+request.Country+"&year="+strconv.FormatInt(int64(request.To), 10))
|
||||
}
|
||||
})
|
||||
adminDashboard.POST("/holidays/:id/delete", func(c *gin.Context) {
|
||||
id := uuid.MustParse(c.Param("id"))
|
||||
hol, err := service.FindById(id)
|
||||
|
@ -266,6 +286,15 @@ func setupAdminDashboard(adminDashboard *gin.RouterGroup, service holiday.Holida
|
|||
countries, _ := countryService.Find()
|
||||
c.HTML(http.StatusOK, "edit-holiday.gohtml", gin.H{"Countries": countries, "Holiday": hol})
|
||||
})
|
||||
adminDashboard.GET("/dialogs/copy-year", func(c *gin.Context) {
|
||||
country := c.Query("country")
|
||||
year, err := strconv.ParseInt(c.Query("year"), 10, 32)
|
||||
if err != nil {
|
||||
c.AbortWithError(http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
c.HTML(http.StatusOK, "copy-year.gohtml", gin.H{"Country": country, "Year": year})
|
||||
})
|
||||
adminDashboard.GET("/dialogs/edit-country", func(c *gin.Context) {
|
||||
id := uuid.MustParse(c.Query("id"))
|
||||
country, err := countryService.FindById(id)
|
||||
|
|
2
makefile
2
makefile
|
@ -1,7 +1,7 @@
|
|||
# scripts for building app
|
||||
# requires go 1.19+ and git installed
|
||||
|
||||
VERSION := 0.3.1
|
||||
VERSION := 0.5.0
|
||||
|
||||
serve:
|
||||
go run ./...
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<a style="all: unset;" href="/admin/countries">Države</a>
|
||||
</header>
|
||||
<main class="admin-dashboard">
|
||||
<div class="filter-dialog">
|
||||
<div class="filter-dialog" style="background: transparent">
|
||||
<section class="dialog filter-dialog">
|
||||
<h2 style="margin-top: 0.5em">Pretraga</h2>
|
||||
<form action="#" method="get">
|
||||
|
@ -63,9 +63,12 @@
|
|||
</section>
|
||||
</div>
|
||||
|
||||
<div style="flex-grow: 1">
|
||||
<button style="float: right; margin-top: 0" data-type="dialog" data-trigger="#create-card" data-url="/admin/dialogs/add-holiday" class="primary">Dodaj novi praznik</button>
|
||||
<section class="dialog table-results" style="margin: 0; margin-top: 3.5em" id="results">
|
||||
<div style="flex-grow: 1; background: transparent">
|
||||
<div style="display: flex; flex-direction: row; flex-wrap: wrap; justify-content: end; background: transparent; gap: 0.5em">
|
||||
<button data-type="dialog" data-trigger="#copy-year" data-url="/admin/dialogs/copy-year?year={{$.Search.Year}}&country={{$.Search.Country}}" class="secondary">Kopiraj godinu</button>
|
||||
<button data-type="dialog" data-trigger="#create-card" data-url="/admin/dialogs/add-holiday" class="primary">Dodaj novi praznik</button>
|
||||
</div>
|
||||
<section class="dialog table-results" style="margin: 0; margin-top: 0.5em" id="results">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -81,8 +84,8 @@
|
|||
<td>{{$entry.Name}}</td>
|
||||
<td>{{$entry.Date.Format "02.01.2006"}}</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/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/icons/trash-delete.svg"></button>
|
||||
<button data-type="dialog" data-trigger="#update-card" data-url="/admin/dialogs/edit-holiday?id={{$entry.Id}}" class="icon"><span class="icon edit-icon"></span></button>
|
||||
<button data-type="dialog" data-trigger="#delete-card" data-url="/admin/dialogs/delete-holiday?id={{$entry.Id}}" class="icon"><span class="icon delete-icon"></span></button>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
|
|
@ -16,9 +16,11 @@
|
|||
<h1><a href="/admin?country=HR">Holiday-api | Države</a></h1>
|
||||
</header>
|
||||
<main class="admin-dashboard">
|
||||
<div style="width: 640px; max-width: 100%; margin: auto">
|
||||
<button style="float: right; margin-top: 0" data-type="dialog" data-trigger="#create-card" data-url="/admin/dialogs/add-country" class="primary">Dodaj državu</button>
|
||||
<section class="dialog table-results" style="margin: 0; margin-top: 3.5em" id="results">
|
||||
<div style="width: 640px; max-width: 100%; margin: auto; background: transparent">
|
||||
<div style="display: flex; flex-direction: row; flex-wrap: wrap; justify-content: end; background: transparent">
|
||||
<button style="float: right; margin-top: 0" data-type="dialog" data-trigger="#create-card" data-url="/admin/dialogs/add-country" class="primary">Dodaj državu</button>
|
||||
</div>
|
||||
<section class="dialog table-results" style="margin: 0; margin-top: 0.5em" id="results">
|
||||
<table style="width: 100%">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -34,8 +36,8 @@
|
|||
<td>{{$entry.IsoName}}</td>
|
||||
<td>{{$entry.Name}}</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/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/icons/trash-delete.svg"></button>
|
||||
<button type="button" data-type="dialog" data-trigger="#update-card" data-url="/admin/dialogs/edit-country?id={{$entry.Id}}" class="clean icon"><span class="icon edit-icon"></span></button>
|
||||
<button type="button" data-type="dialog" data-trigger="#delete-card" data-url="/admin/dialogs/delete-country?id={{$entry.Id}}" class="clean icon"><span class="icon delete-icon"></span></button>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
<dialog class="card" id="copy-year">
|
||||
<h3 class="card-title">Kopiraj godinu {{.Year}}</h3>
|
||||
|
||||
<p>Na koji godinu želite kopirati praznike iz godine {{.Year}}?</p>
|
||||
<form method="post" action="/admin/holidays/copy">
|
||||
<input type="hidden" name="from" value="{{.Year}}">
|
||||
<input type="hidden" name="country" value="{{.Country}}">
|
||||
<section class="form-field">
|
||||
<label for="to">Godina:</label>
|
||||
<input required id="to" name="to" type="number" step="1">
|
||||
</section>
|
||||
<section class="actions">
|
||||
<button class="primary" type="submit">Kopiraj</button>
|
||||
<button class="secondary" type="button" onclick="closeDialog('#copy-year')">Odustani</button>
|
||||
</section>
|
||||
</form>
|
||||
</dialog>
|
|
@ -18,9 +18,9 @@
|
|||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #f9f9f9;
|
||||
background-color: var(--dropdown-background);
|
||||
min-width: 160px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue