Updated design + added documentation page

This commit is contained in:
Borna Rajković 2023-08-05 17:50:40 +02:00
parent c33a883c34
commit 76a3c446da
25 changed files with 1080 additions and 448 deletions

110
assets/documentation.js Normal file
View File

@ -0,0 +1,110 @@
window.addEventListener('load', () => {
function parseBoolean(value) {
if(value === 'true') {
return true;
} else if (value === 'false') {
return false;
} else {
return undefined;
}
}
function generateQuery(query) {
initialQuery = `curl -H "accept: ${query['contentType']}" "https://holiday.bbr-dev.info/api/v1/holidays?country=${query['country']}`
delete query['contentType'];
delete query['country'];
for(const key in query) {
initialQuery += `&${key}=${query[key]}`
}
initialQuery+='"';
document.querySelector('#result-content').innerHTML = initialQuery;
}
let country = document.querySelector("#country");
let contentType = document.querySelector("#content-type");
let stateHoliday = document.querySelector("#state-holiday");
let religiousHoliday = document.querySelector("#religious-holiday");
// select which date/year/range is used
let dateSelector = document.querySelector("#date-selector");
let dYear = document.querySelector("#dsy-year");
let dDate = document.querySelector("#dsd-date");
let dStartRange = document.querySelector("#dsr-start-range");
let dStartRangeRequired = document.querySelector("#dsr-start-range-required");
let dEndRange = document.querySelector("#dsr-end-range");
let dEndRangeRequired = document.querySelector("#dsr-end-range-required");
let queryGenerator = document.querySelector("#query-generator");
{
const query = {};
query['country'] = country.value;
query['contentType'] = contentType.value;
if(stateHoliday.value === 'true' || stateHoliday.value === 'false') {
query['stateHoliday'] = parseBoolean(stateHoliday.value);
}
if(religiousHoliday.value === 'true' || religiousHoliday.value === 'false') {
query['religiousHoliday'] = parseBoolean(religiousHoliday.value);
}
switch(dateSelector.value) {
case 'year':
query['year'] = dYear.value;
break;
case 'date':
if(dDate.value) {
query['date'] = dDate.value;
}
break;
case 'range':
if(dStartRangeRequired.checked && dStartRange.value) {
query['startRange'] = dStartRange.value;
}
if(dEndRangeRequired.checked && dEndRange.value) {
query['endRange'] = dEndRange.value;
}
case 'all':
default:
}
generateQuery(query);
}
queryGenerator.addEventListener('change', event => {
const query = {};
query['country'] = country.value;
query['contentType'] = contentType.value;
if(stateHoliday.value === 'true' || stateHoliday.value === 'false') {
query['stateHoliday'] = parseBoolean(stateHoliday.value);
}
if(religiousHoliday.value === 'true' || religiousHoliday.value === 'false') {
query['religiousHoliday'] = parseBoolean(religiousHoliday.value);
}
switch(dateSelector.value) {
case 'year':
query['year'] = dYear.value;
break;
case 'date':
if(dDate.value) {
query['date'] = dDate.value;
}
break;
case 'range':
if(dStartRangeRequired.checked && dStartRange.value) {
query['startRange'] = dStartRange.value;
}
if(dEndRangeRequired.checked && dEndRange.value) {
query['endRange'] = dEndRange.value;
}
case 'all':
default:
}
generateQuery(query);
})
})

68
assets/global.js Normal file
View File

@ -0,0 +1,68 @@
const dialogContainerId = "#dialog-container"
window.addEventListener('load', () => {
// configure radio button groups
document.querySelectorAll("section.radio-group").forEach(el => {
const submittedInput = el.querySelector("input[type=hidden]");
el.querySelector(`input[value="${submittedInput.value}"]`).checked = true;
el.querySelectorAll("input[type=radio]").forEach(rb => {
if(rb?.checked) {
submittedInput.value = rb.value;
}
rb.addEventListener('click', () => {
submittedInput.value = rb.value;
})
})
})
// configure dialog buttons
document.querySelectorAll("button[data-type=dialog]").forEach(btn => {
const selector = btn.dataset.trigger;
btn.addEventListener('click', async () => {
let response = await fetch(btn.dataset.url);
if(response.ok) {
response = await response.text();
const container = document.querySelector(dialogContainerId);
container.innerHTML = response;
const dialogReference = document.querySelector(selector);
dialogReference?.showModal();
} else {
// todo display alert
console.log("no dialog found");
}
});
});
// configure section selector
document.querySelectorAll("select[data-type=section-selector]").forEach(select => {
const sectionPrefix = select.dataset.selectorPrefix;
const options = [...select.options].map(o => o.value);
for(let option of options) {
const querySelector = "div[data-section-id=" + sectionPrefix + "-" + option + "]";
const section = document.querySelector(querySelector);
if(option === select.value) {
section?.classList?.remove('hide');
} else {
section?.classList?.add('hide');
}
}
select.addEventListener("change", () => {
for(let option of options) {
const querySelector = "div[data-section-id=" + sectionPrefix + "-" + option + "]";
const section = document.querySelector(querySelector);
if(option === select.value) {
section?.classList?.remove('hide');
} else {
section?.classList?.add('hide');
}
}
})
})
})
function closeDialog(selector) {
document.querySelector(selector).close();
document.querySelector(dialogContainerId).innerHTML="";
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 8C13 7.44772 12.5523 7 12 7C11.4477 7 11 7.44772 11 8V11H8C7.44772 11 7 11.4477 7 12C7 12.5523 7.44772 13 8 13H11V16C11 16.5523 11.4477 17 12 17C12.5523 17 13 16.5523 13 16V13H16C16.5523 13 17 12.5523 17 12C17 11.4477 16.5523 11 16 11H13V8Z" fill="#0F1729"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM4 12C4 7.58172 7.58172 4 12 4C16.4183 4 20 7.58172 20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12Z" fill="#0F1729"/>
</svg>

After

Width:  |  Height:  |  Size: 795 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.29289 5.29289C5.68342 4.90237 6.31658 4.90237 6.70711 5.29289L12 10.5858L17.2929 5.29289C17.6834 4.90237 18.3166 4.90237 18.7071 5.29289C19.0976 5.68342 19.0976 6.31658 18.7071 6.70711L13.4142 12L18.7071 17.2929C19.0976 17.6834 19.0976 18.3166 18.7071 18.7071C18.3166 19.0976 17.6834 19.0976 17.2929 18.7071L12 13.4142L6.70711 18.7071C6.31658 19.0976 5.68342 19.0976 5.29289 18.7071C4.90237 18.3166 4.90237 17.6834 5.29289 17.2929L10.5858 12L5.29289 6.70711C4.90237 6.31658 4.90237 5.68342 5.29289 5.29289Z" fill="#0F1729"/>
</svg>

After

Width:  |  Height:  |  Size: 807 B

4
assets/images/done-v.svg Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.7071 6.29289C20.0976 6.68342 20.0976 7.31658 19.7071 7.70711L10.4142 17C9.63316 17.7811 8.36683 17.781 7.58579 17L3.29289 12.7071C2.90237 12.3166 2.90237 11.6834 3.29289 11.2929C3.68342 10.9024 4.31658 10.9024 4.70711 11.2929L9 15.5858L18.2929 6.29289C18.6834 5.90237 19.3166 5.90237 19.7071 6.29289Z" fill="#0F1729"/>
</svg>

After

Width:  |  Height:  |  Size: 602 B

4
assets/images/edit.svg Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.1739 3.5968C13.8662 3.2047 14.686 3.10369 15.4528 3.31598C15.7928 3.41011 16.0833 3.57409 16.3571 3.7593C16.6172 3.9352 16.9155 4.16808 17.2613 4.43799L17.3117 4.47737C17.6575 4.74728 17.9559 4.98016 18.1897 5.18977C18.4358 5.41046 18.6654 5.65248 18.8393 5.95945C19.2314 6.65177 19.3324 7.47151 19.1201 8.23831C19.026 8.5783 18.862 8.86883 18.6768 9.14267C18.5009 9.40276 18.268 9.70112 17.998 10.0469L10.8953 19.1462C10.8773 19.1692 10.8596 19.1919 10.8421 19.2144C10.5087 19.6419 10.2566 19.9651 9.9445 20.2306C9.68036 20.4553 9.38811 20.6447 9.07512 20.794C8.70535 20.9704 8.30733 21.0685 7.78084 21.1983C7.75324 21.2051 7.72528 21.212 7.69696 21.219L5.57214 21.7435C5.42499 21.7799 5.25702 21.8215 5.10885 21.8442C4.94367 21.8696 4.68789 21.8926 4.40539 21.8022C4.06579 21.6934 3.77603 21.4672 3.58809 21.1642C3.43175 20.9121 3.39197 20.6584 3.3765 20.492C3.36262 20.3427 3.36213 20.1697 3.3617 20.0181C3.36167 20.0087 3.36165 19.9994 3.36162 19.9902L3.35475 17.8295C3.35465 17.8003 3.35455 17.7715 3.35445 17.7431C3.3525 17.2009 3.35103 16.7909 3.4324 16.3894C3.50128 16.0495 3.61406 15.72 3.76791 15.4093C3.94967 15.0421 4.20204 14.7191 4.53586 14.2918C4.55336 14.2694 4.57109 14.2467 4.58905 14.2237L11.6918 5.12435C11.9617 4.77856 12.1946 4.48019 12.4042 4.2464C12.6249 4.00025 12.8669 3.77065 13.1739 3.5968ZM14.9191 5.24347C14.6635 5.17271 14.3903 5.20638 14.1595 5.33708C14.1203 5.35928 14.0459 5.41135 13.8934 5.5815C13.7348 5.75836 13.5438 6.00211 13.2487 6.38018L16.4018 8.84145C16.697 8.46338 16.887 8.21896 17.0201 8.02221C17.1482 7.83291 17.1806 7.74808 17.1926 7.70467C17.2634 7.44907 17.2297 7.17583 17.099 6.94505C17.0768 6.90586 17.0247 6.83145 16.8546 6.6789C16.6777 6.52033 16.434 6.32938 16.0559 6.03426C15.6778 5.73914 15.4334 5.54904 15.2367 5.41597C15.0474 5.28794 14.9625 5.25549 14.9191 5.24347ZM15.1712 10.418L12.0181 7.95674L6.16561 15.4543C5.75585 15.9792 5.6403 16.135 5.56031 16.2966C5.48339 16.452 5.42699 16.6167 5.39256 16.7866C5.35675 16.9633 5.35262 17.1572 5.35474 17.8231L5.36082 19.7357L7.2176 19.2773C7.86411 19.1177 8.05119 19.0666 8.21391 18.9889C8.37041 18.9143 8.51653 18.8196 8.64861 18.7072C8.78593 18.5904 8.90897 18.4405 9.31872 17.9156L15.1712 10.418ZM12 21C12 20.4477 12.4477 20 13 20H20C20.5523 20 21 20.4477 21 21C21 21.5523 20.5523 22 20 22H13C12.4477 22 12 21.5523 12 21Z" fill="#0F1729"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

4
assets/images/search.svg Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 5C7.68629 5 5 7.68629 5 11C5 14.3137 7.68629 17 11 17C14.3137 17 17 14.3137 17 11C17 7.68629 14.3137 5 11 5ZM3 11C3 6.58172 6.58172 3 11 3C15.4183 3 19 6.58172 19 11C19 12.8487 18.3729 14.551 17.3199 15.9056L20.7071 19.2929C21.0976 19.6834 21.0976 20.3166 20.7071 20.7071C20.3166 21.0976 19.6834 21.0976 19.2929 20.7071L15.9056 17.3199C14.551 18.3729 12.8487 19 11 19C6.58172 19 3 15.4183 3 11Z" fill="#0F1729"/>
</svg>

After

Width:  |  Height:  |  Size: 696 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.10002 5H3C2.44772 5 2 5.44772 2 6C2 6.55228 2.44772 7 3 7H4.06055L4.88474 20.1871C4.98356 21.7682 6.29471 23 7.8789 23H16.1211C17.7053 23 19.0164 21.7682 19.1153 20.1871L19.9395 7H21C21.5523 7 22 6.55228 22 6C22 5.44772 21.5523 5 21 5H19.0073C19.0018 4.99995 18.9963 4.99995 18.9908 5H16.9C16.4367 2.71776 14.419 1 12 1C9.58104 1 7.56329 2.71776 7.10002 5ZM9.17071 5H14.8293C14.4175 3.83481 13.3062 3 12 3C10.6938 3 9.58254 3.83481 9.17071 5ZM17.9355 7H6.06445L6.88085 20.0624C6.91379 20.5894 7.35084 21 7.8789 21H16.1211C16.6492 21 17.0862 20.5894 17.1192 20.0624L17.9355 7ZM14.279 10.0097C14.83 10.0483 15.2454 10.5261 15.2068 11.0771L14.7883 17.0624C14.7498 17.6134 14.2719 18.0288 13.721 17.9903C13.17 17.9517 12.7546 17.4739 12.7932 16.9229L13.2117 10.9376C13.2502 10.3866 13.7281 9.97122 14.279 10.0097ZM9.721 10.0098C10.2719 9.97125 10.7498 10.3866 10.7883 10.9376L11.2069 16.923C11.2454 17.4739 10.83 17.9518 10.2791 17.9903C9.72811 18.0288 9.25026 17.6134 9.21173 17.0625L8.79319 11.0771C8.75467 10.5262 9.17006 10.0483 9.721 10.0098Z" fill="#0F1729"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

275
assets/style.css Normal file
View File

@ -0,0 +1,275 @@
/* cleanup */
* {
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding: 0;
margin: 0;
}
header {
padding: 1rem 2rem;
width: 100%;
background: #666;
}
header h1 {
font-size: 1.25em;
}
header a {
color: #fff;
text-decoration: none;
}
nav {
padding: 1rem 2rem;
width: 100%;
background: #f7f7f7;
}
nav a, nav button {
display: inline-block;
text-decoration: none;
color: #333;
font-size: 1rem;
border: none;
background: none;
padding: 0.5rem 1rem;
transition: ease-out 0.2s;
}
nav a:hover, nav button:hover {
background: #e0e0e0;
color: #000;
transition: ease-out 0.2s;
}
nav a.selected, nav button.selected {
background: #216897;
color: white;
}
table:not(.clean) {
width: 100%;
}
table th, table td {
text-align: left;
}
table {
border-collapse: collapse;
}
table thead * {
background: #666;
color: #fff;
}
table th, table td {
padding: 0.4rem 0.4rem;
}
table:not(.clean) tbody tr:nth-child(2n+1) {
background: #f5f5f5;
}
table:not(.clean) tbody tr:nth-child(2n) {
background: #e5e5e5;
}
button {
padding: 0.25rem 1rem;
}
.card {
padding: 1em;
background: #fff;
border-radius: 0.2rem;
border: 1px solid #bbb;
}
.card .card-title {
margin-bottom: 0.5em;
}
.card p {
margin-bottom: 0.5em;
}
form section {
width: fit-content;
}
/* styling */
form section:not(.radio-group) label+:not(select) {
display: flex;
}
form section {
padding-bottom: 0.75rem;
}
form section > input {
width: 200px;
padding: 0.2em 0.5em;
}
form section > textarea {
width: 200px;
padding: 0.2em 0.5em;
}
form section select {
display: block;
width: 200px;
border: none;
font-size: 0.9rem;
padding: 0.2em 0.5em;
background: #ddd;
}
form section label.checkbox {
display: block;
overflow: hidden;
width: 200px;
}
form section input[type=checkbox] {
vertical-align: middle;
float: right;
}
section.radio-group input {
display: none;
}
section.radio-group div label {
display: inline-block;
padding: 0.25rem 0.75rem;
background: #ddd;
}
section.radio-group div label {
min-width: 64px;
text-align: center;
font-size: 0.9rem;
}
section.radio-group div label:first-of-type {
border-top-left-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
}
section.radio-group div label:last-of-type {
border-top-right-radius: 0.25rem;
border-bottom-right-radius: 0.25rem;
}
section.radio-group div input:checked+label {
background: #555;
color: white;
}
img.icon {
height: 1.5em;
width: 1.5em;
}
button.icon, a.icon {
display: flex;
align-content: baseline;
gap: 0.3em;
}
main {
display: flex;
flex-wrap: wrap;
max-width: 1280px;
margin: auto;
}
.container {
max-width: 1280px;
margin: auto;
}
section#search {
margin: 1em;
width: fit-content;
}
.index-page {
margin: 1em auto;
align-content: center;
gap: 1em;
}
.index-page section {
flex-grow: 1;
}
.index-page section article {
margin: auto;
width: 300px;
max-width: 90vw;
}
section#results {
margin: 1em;
flex-grow: 1;
}
button.clean {
display: inline-block;
padding: 0.4rem;
border: none;
background: none;
text-decoration: none;
color: #000;
cursor: pointer;
}
section#results a, section#results button {
display: inline-block;
padding: 0.4rem;
border: none;
background: none;
text-decoration: none;
color: #000;
cursor: pointer;
}
dialog {
position: fixed;
left: 50%;
top: 80px;
transform: translate(-50%, 0);
max-height: calc(100vh - 160px);
overflow-y: scroll;
}
section.actions {
padding: 0;
}
article.single-holiday {
padding-bottom: 0.5em;
margin-bottom: 1em;
border-bottom: 2px solid #a0a0a0;
}
.optional-selector {
border: 1px solid #666;
border-radius: 0.5em;
}
.optional-selector .header {
padding: 1em;
background: #666;
margin-bottom: 0.5em;
color: white;
border-top-left-radius: 0.5em;
border-top-right-radius: 0.5em;
}
.optional-selector > *:not(.header) {
margin: 0 1em;
}
.hide {
display: none;
}
.source-code {
padding: 1em;
}
.source-code, .source-code * {
background: #777;
color: #fff;
font-family: 'Courier New', Courier, monospace;
}

View File

@ -17,4 +17,5 @@ FROM scratch
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=go-build /root/holiday-api /usr/bin/holiday-api COPY --from=go-build /root/holiday-api /usr/bin/holiday-api
ADD templates templates/ ADD templates templates/
ADD assets assets/
ENTRYPOINT ["holiday-api"] ENTRYPOINT ["holiday-api"]

View File

@ -26,6 +26,30 @@ type Search struct {
Date *time.Time `form:"date" time_format:"2006-01-02"` Date *time.Time `form:"date" time_format:"2006-01-02"`
RangeStart *time.Time `form:"range_start" time_format:"2006-01-02"` RangeStart *time.Time `form:"range_start" time_format:"2006-01-02"`
RangeEnd *time.Time `form:"range_end" time_format:"2006-01-02"` RangeEnd *time.Time `form:"range_end" time_format:"2006-01-02"`
StateHoliday *bool `form:"state_holiday,omitempty"` StateHoliday string `form:"state_holiday,omitempty" binding:"omitempty"`
ReligiousHoliday *bool `form:"religious_holiday,omitempty"` ReligiousHoliday string `form:"religious_holiday,omitempty" binding:"omitempty"`
}
func (s Search) IsStateHoliday() *bool {
var value bool
if s.StateHoliday == "true" {
value = true
} else if s.StateHoliday == "false" {
value = false
} else {
return nil
}
return &value
}
func (s Search) IsReligiousHoliday() *bool {
var value bool
if s.ReligiousHoliday == "true" {
value = true
} else if s.ReligiousHoliday == "false" {
value = false
} else {
return nil
}
return &value
} }

View File

@ -16,13 +16,13 @@ func (s *Service) Find(search Search, paging Paging) ([]Holiday, error) {
var holidays []Holiday var holidays []Holiday
var err error var err error
if search.Date != nil { if search.Date != nil {
holidays, err = s.findByDate(*search.Date, search.StateHoliday, search.ReligiousHoliday, search.Country) holidays, err = s.findByDate(*search.Date, search.IsStateHoliday(), search.IsReligiousHoliday(), search.Country)
} else if search.RangeStart != nil || search.RangeEnd != nil { } else if search.RangeStart != nil || search.RangeEnd != nil {
holidays, err = s.findForRange(getDateOrDefault(search.RangeStart, 1000), getDateOrDefault(search.RangeEnd, 3000), search.StateHoliday, search.ReligiousHoliday, search.Country) holidays, err = s.findForRange(getDateOrDefault(search.RangeStart, 1000), getDateOrDefault(search.RangeEnd, 3000), search.IsStateHoliday(), search.IsReligiousHoliday(), search.Country)
} else if search.Year != nil { } else if search.Year != nil {
holidays, err = s.findByYear(*search.Year, search.StateHoliday, search.ReligiousHoliday, search.Country) holidays, err = s.findByYear(*search.Year, search.IsStateHoliday(), search.IsReligiousHoliday(), search.Country)
} else { } else {
holidays, err = s.find(search.StateHoliday, search.ReligiousHoliday, search.Country) holidays, err = s.find(search.IsStateHoliday(), search.IsReligiousHoliday(), search.Country)
} }
if err != nil { if err != nil {
return nil, err return nil, err

161
main.go
View File

@ -12,6 +12,7 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -48,6 +49,8 @@ func main() {
g := gin.Default() g := gin.Default()
g.Static("assets", "assets")
loadTemplates(g) loadTemplates(g)
holidayService := holiday.Service{DB: client} holidayService := holiday.Service{DB: client}
@ -56,24 +59,20 @@ func main() {
setupAdminDashboard(g.Group("/admin"), holidayService) setupAdminDashboard(g.Group("/admin"), holidayService)
g.GET("/docs", func(c *gin.Context) {
c.HTML(http.StatusOK, "docs.gohtml", nil)
})
g.GET("/", func(c *gin.Context) { g.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.gohtml", nil) year := time.Now().Year()
search := holiday.Search{Country: "HR", Year: &year}
if err := c.ShouldBindQuery(&search); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100})
c.HTML(http.StatusOK, "index.gohtml", gin.H{"Search": search, "Holidays": mapHolidays(holidays).Holidays})
})
g.GET("/documentation", func(c *gin.Context) {
c.HTML(http.StatusOK, "documentation.gohtml", nil)
}) })
g.GET("/search", func(c *gin.Context) { g.GET("/search", func(c *gin.Context) {
request := holiday.Search{}
if err := c.ShouldBindQuery(&request); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
search := holiday.Search{Country: request.Country, Year: request.Year}
holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100})
c.HTML(http.StatusOK, "search.gohtml", mapHolidays(holidays))
})
g.GET("/search/date", func(c *gin.Context) {
request := holiday.Search{} request := holiday.Search{}
if err := c.ShouldBindQuery(&request); err != nil { if err := c.ShouldBindQuery(&request); err != nil {
c.AbortWithError(http.StatusBadRequest, err) c.AbortWithError(http.StatusBadRequest, err)
@ -81,7 +80,10 @@ func main() {
} }
search := holiday.Search{Country: request.Country, Date: request.Date} search := holiday.Search{Country: request.Country, Date: request.Date}
holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100}) holidays, _ := holidayService.Find(search, holiday.Paging{PageSize: 100})
c.HTML(http.StatusOK, "search_date.gohtml", mapHolidays(holidays)) c.HTML(http.StatusOK, "search.gohtml", gin.H{"Search": search, "Holidays": mapHolidays(holidays).Holidays})
})
g.GET("/dialogs/check-is-a-holiday", func(c *gin.Context) {
c.HTML(http.StatusOK, "check-is-a-holiday.gohtml", gin.H{})
}) })
log.Fatal(http.ListenAndServe(":5281", g)) log.Fatal(http.ListenAndServe(":5281", g))
@ -97,53 +99,60 @@ func loadTemplates(g *gin.Engine) {
} }
}, },
"deferint": func(value *int) int { return *value }, "deferint": func(value *int) int { return *value },
"intpeq": func(selected *int, value int) bool {
if selected != nil {
return *selected == value
}
return false
},
}) })
g.LoadHTMLFiles("templates/admin_dashboard.gohtml", "templates/holiday.gohtml", "templates/error.gohtml", "templates/index.gohtml", "templates/search.gohtml", "templates/search_date.gohtml", "templates/docs.gohtml") g.LoadHTMLFiles(
"templates/index.gohtml",
"templates/search.gohtml",
"templates/documentation.gohtml",
"templates/admin_dashboard.gohtml",
"templates/dialogs/add-holiday.gohtml",
"templates/dialogs/edit-holiday.gohtml",
"templates/dialogs/delete-holiday.gohtml",
"templates/dialogs/check-is-a-holiday.gohtml",
)
} }
func setupAdminDashboard(adminDashboard *gin.RouterGroup, service holiday.Service) { func setupAdminDashboard(adminDashboard *gin.RouterGroup, service holiday.Service) {
adminDashboard.Use(gin.BasicAuth(loadAuth())) adminDashboard.Use(gin.BasicAuth(loadAuth()))
adminDashboard.GET("/holiday", func(c *gin.Context) { adminDashboard.GET("/", func(c *gin.Context) {
id := c.Query("id") search := holiday.Search{Country: "HR", Year: new(int)}
*search.Year = time.Now().Year()
if err := c.ShouldBindQuery(&search); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
holidays, _ := service.Find(search, holiday.Paging{PageSize: 100})
holidayResponse := mapHolidays(holidays)
response := HolidaySingleResponse{ response := map[string]any{}
Id: nil, response["holidays"] = holidayResponse
Country: "", response["search"] = search
Date: DateResponse{time.Now()},
Name: "", c.HTML(http.StatusOK, "admin_dashboard.gohtml", response)
Description: "",
IsStateHoliday: true,
IsReligiousHoliday: false,
}
if id != "" {
if h, err := service.FindById(uuid.Must(uuid.Parse(id))); err == nil {
response = HolidaySingleResponse{
Id: &h.Id,
Country: h.Country,
Date: DateResponse{h.Date},
Name: h.Name,
Description: h.Description,
IsStateHoliday: h.IsStateHoliday,
IsReligiousHoliday: h.IsReligiousHoliday,
}
}
}
c.HTML(http.StatusOK, "holiday.gohtml", response)
}) })
adminDashboard.POST("/holiday", func(c *gin.Context) { adminDashboard.POST("/holidays", func(c *gin.Context) {
request := struct { request := struct {
Id *string `form:"id"` Id *string `form:"id"`
Name string `form:"name" binding:"required,min=1"` Name string `form:"name" binding:"required,min=1"`
Description string `form:"description"` Description string `form:"description"`
IsStateHoliday bool `form:"stateHoliday"` IsStateHoliday bool `form:"state_holiday"`
IsReligiousHoliday bool `form:"religiousHoliday"` IsReligiousHoliday bool `form:"religious_holiday"`
Country string `form:"country" binding:"len=2"` Country string `form:"country" binding:"len=2"`
Date time.Time `form:"date" time_format:"2006-01-02"` Date time.Time `form:"date" time_format:"2006-01-02"`
}{} }{}
if err := c.ShouldBind(&request); err != nil { if err := c.ShouldBind(&request); err != nil {
c.AbortWithError(http.StatusBadRequest, err) c.AbortWithError(http.StatusBadRequest, err)
return
} }
hol := holiday.Holiday{ hol := holiday.Holiday{
@ -165,42 +174,42 @@ func setupAdminDashboard(adminDashboard *gin.RouterGroup, service holiday.Servic
if err != nil { if err != nil {
c.AbortWithError(http.StatusInternalServerError, err) c.AbortWithError(http.StatusInternalServerError, err)
} else { } else {
c.Redirect(http.StatusSeeOther, "/admin/holiday?id="+hol.Id.String()) c.Redirect(http.StatusSeeOther, "/admin?country="+request.Country+"&year="+strconv.FormatInt(int64(request.Date.Year()), 10))
} }
}) })
adminDashboard.POST("/holidays/:id/delete", func(c *gin.Context) {
adminDashboard.POST("/holiday/delete", func(c *gin.Context) { id := uuid.MustParse(c.Param("id"))
request := struct { hol, err := service.FindById(id)
Id *string `form:"id" binding:"required"`
}{}
if err := c.ShouldBind(&request); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
err := service.Delete(uuid.MustParse(*request.Id))
if err != nil { if err != nil {
log.Printf("Failed deleting holiday: %v", err) c.AbortWithError(http.StatusNotFound, err)
c.AbortWithStatus(http.StatusInternalServerError)
} else {
c.Redirect(http.StatusSeeOther, "/admin")
}
})
adminDashboard.GET("/", func(c *gin.Context) {
search := holiday.Search{Country: "HR", Year: new(int)}
*search.Year = time.Now().Year()
if err := c.ShouldBindQuery(&search); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return return
} }
holidays, _ := service.Find(search, holiday.Paging{PageSize: 100}) if err := service.Delete(id); err != nil {
holidayResponse := mapHolidays(holidays) c.AbortWithError(http.StatusInternalServerError, err)
return
response := map[string]any{} }
response["holidays"] = holidayResponse c.Redirect(http.StatusSeeOther, "/admin?country="+hol.Country+"&year="+strconv.FormatInt(int64(hol.Date.Year()), 10))
response["search"] = search })
adminDashboard.GET("/dialogs/add-holiday", func(c *gin.Context) {
c.HTML(http.StatusOK, "admin_dashboard.gohtml", response) c.HTML(http.StatusOK, "add-holiday.gohtml", gin.H{})
})
adminDashboard.GET("/dialogs/edit-holiday", func(c *gin.Context) {
id := uuid.MustParse(c.Query("id"))
hol, err := service.FindById(id)
if err != nil {
c.AbortWithError(http.StatusNotFound, err)
return
}
c.HTML(http.StatusOK, "edit-holiday.gohtml", gin.H{"Holiday": hol})
})
adminDashboard.GET("/dialogs/delete-holiday", func(c *gin.Context) {
id := uuid.MustParse(c.Query("id"))
hol, err := service.FindById(id)
if err != nil {
c.AbortWithError(http.StatusNotFound, err)
return
}
c.HTML(http.StatusOK, "delete-holiday.gohtml", gin.H{"Holiday": hol})
}) })
} }

View File

@ -2,104 +2,105 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Holiday-api</title> <title>Holiday-api | Admin dashboard</title>
<link rel="stylesheet" href="/assets/style.css">
<script src="/assets/global.js"></script>
</head> </head>
<body> <body>
<style> <div id="dialog-container"></div>
* {font-family: system-ui} <header>
</style> <section class="container">
<h1><a href="/admin">Holiday-api</a></h1> <h1><a href="/">Holiday-api | Admin dashboard</a></h1>
<p>Welcome to admin interface for holiday api</p> </section>
</header>
<div> <nav>
<a href="/admin/holiday">Add a holiday</a> <section class="container">
</div> <a class="selected" href="#">Search</a>
<div> <button data-type="dialog" data-trigger="#create-card" data-url="/admin/dialogs/add-holiday">Add new holiday</button>
<form id="filter-holidays" action="/admin" method="get"> </section>
<label for="country">Country</label> </nav>
<select id="country" name="country"> <main>
<option value="HR" {{if eq .search.Country "HR"}}selected{{end}}>Croatia</option> <section id="search">
<option value="GB" {{if eq .search.Country "GB"}}selected{{end}}>Great Britain</option> <article class="card">
<option value="US" {{if eq .search.Country "US"}}selected{{end}}>USA</option> <form action="#" method="get">
<option value="FR" {{if eq .search.Country "FR"}}selected{{end}}>France</option> <section>
</select> <label for="country">Country:</label>
<select id="country" name="country">
<option value="HR" {{if eq .search.Country "HR"}}selected{{end}}>Croatia</option>
<label for="year">Year</label> <option value="GB" {{if eq .search.Country "GB"}}selected{{end}}>Great Britain</option>
<select id="year" name="year"> <option value="US" {{if eq .search.Country "US"}}selected{{end}}>USA</option>
<option value="2020" {{if eq (deferint .search.Year) 2020}}selected{{end}}>2020</option> <option value="FR" {{if eq .search.Country "FR"}}selected{{end}}>France</option>
<option value="2021" {{if eq (deferint .search.Year) 2021}}selected{{end}}>2021</option> </select>
<option value="2022" {{if eq (deferint .search.Year) 2022}}selected{{end}}>2022</option> </section>
<option value="2023" {{if eq (deferint .search.Year) 2023}}selected{{end}}>2023</option> <section>
<option value="2024" {{if eq (deferint .search.Year) 2024}}selected{{end}}>2024</option> <label for="year">Year:</label>
<option value="2025" {{if eq (deferint .search.Year) 2025}}selected{{end}}>2025</option> <select id="year" name="year">
<option value="2026" {{if eq (deferint .search.Year) 2026}}selected{{end}}>2026</option> <option value="2020" {{if eq (deferint .search.Year) 2020}}selected{{end}}>2020</option>
<option value="2027" {{if eq (deferint .search.Year) 2027}}selected{{end}}>2027</option> <option value="2021" {{if eq (deferint .search.Year) 2021}}selected{{end}}>2021</option>
</select> <option value="2022" {{if eq (deferint .search.Year) 2022}}selected{{end}}>2022</option>
<option value="2023" {{if eq (deferint .search.Year) 2023}}selected{{end}}>2023</option>
<div> <option value="2024" {{if eq (deferint .search.Year) 2024}}selected{{end}}>2024</option>
<label>State holiday</label> <option value="2025" {{if eq (deferint .search.Year) 2025}}selected{{end}}>2025</option>
<option value="2026" {{if eq (deferint .search.Year) 2026}}selected{{end}}>2026</option>
<label for="state-holiday-true">True</label> <option value="2027" {{if eq (deferint .search.Year) 2027}}selected{{end}}>2027</option>
<input type="radio" value="true" {{if boolcmp .search.StateHoliday "true"}}checked{{end}} id="state-holiday-true" name="state_holiday"> </select>
</section>
<label for="state-holiday-false">False</label> <section class="radio-group">
<input type="radio" value="false" {{if boolcmp .search.StateHoliday "false"}}checked{{end}} id="state-holiday-false" name="state_holiday"> <label>Is state holiday:</label>
<div>
<label for="state-holiday-any">Any</label> <input type="radio" value="true" name="sh" id="sh_true"><label for="sh_true">True
<input type="radio" value="" {{if boolcmp .search.StateHoliday "nil"}}checked{{end}} id="state-holiday-any" name="state_holiday"> </label><input type="radio" value="false" name="sh" id="sh_false"><label for="sh_false">False
</div> </label><input type="radio" value="" name="sh" id="sh_any"><label for="sh_any">All</label>
</div>
<div> <input type="hidden" value="{{.search.StateHoliday}}" name="state_holiday">
<label>Religious holiday</label> </section>
<section class="radio-group">
<label for="religious-holiday-true">True</label> <label>Is religious holiday:</label>
<input type="radio" value="true" {{if boolcmp .search.ReligiousHoliday "true"}}checked{{end}} id="religious-holiday-true" name="religious_holiday"> <div>
<input type="radio" value="true" name="rh" id="rh_true"><label for="rh_true">True
<label for="religious-holiday-false">False</label> </label><input type="radio" value="false" name="rh" id="rh_false"><label for="rh_false">False
<input type="radio" value="false" {{if boolcmp .search.ReligiousHoliday "false"}}checked{{end}} id="religious-holiday-false" name="religious_holiday"> </label><input type="radio" value="" name="rh" id="rh_any"><label for="rh_any">All</label>
</div>
<label for="religious-holiday-any">Any</label> <input type="hidden" value="{{.search.ReligiousHoliday}}" name="religious_holiday">
<input type="radio" value="" {{if boolcmp .search.ReligiousHoliday "nil"}}checked{{end}} id="religious-holiday-any" name="religious_holiday"> </section>
<section class="actions">
</div> <button class="icon primary" type="submit">
<img class="icon" src="/assets/images/search.svg">
<button type="submit">Search</button> <span>Search</span>
</form> </button>
</div> </section>
<div> </form>
<table> </article>
<thead> </section>
<section id="results">
<table>
<thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Description</th>
<th>Date</th> <th>Date</th>
<th>State holiday</th> <th>State</th>
<th>Religious holiday</th> <th>Religious</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{range $entry := .holidays.Holidays}}
<tr> {{range $entry := .holidays.Holidays}}
<td>{{$entry.Name}}</td> <tr>
<td>{{$entry.Description}}</td> <td>{{$entry.Name}}</td>
<td>{{$entry.Date.Format "2006-01-02"}}</td> <td>{{$entry.Date.Format "2006-01-02"}}</td>
<td>{{$entry.IsStateHoliday}}</td> <td><img class="icon" src="{{if $entry.IsStateHoliday}}/assets/images/done-v.svg{{else}}/assets/images/close-x.svg{{end}}"></td>
<td>{{$entry.IsReligiousHoliday}}</td> <td><img class="icon" src="{{if $entry.IsReligiousHoliday}}/assets/images/done-v.svg{{else}}/assets/images/close-x.svg{{end}}"></td>
<td> <td>
<a href="/admin/holiday?id={{$entry.Id}}">Edit</a> <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>
<form action="/admin/holiday/delete" method="post"> <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>
<input id="id" type="hidden" name="id" value="{{$entry.Id}}"> </td>
<button type="submit">Delete</button> </tr>
</form> {{end}}
</td> </tbody>
</tr> </table>
{{end}} </section>
</tbody> </main>
</table>
</div>
</body> </body>
</html> </html>

View File

@ -0,0 +1,36 @@
<dialog class="card" id="create-card">
<h3 class="card-title">Create holiday</h3>
<form method="post" action="/admin/holidays">
<section>
<label for="country">Country:</label>
<select id="country" required name="country">
<option value="HR">Croatia</option>
<option value="US">United states</option>
<option value="FR">France</option>
<option value="GB">Great Britain</option>
</select>
</section>
<section>
<label for="name">Name:</label>
<input required id="name" name="name" type="text">
</section>
<section>
<label for="description">Description:</label>
<textarea id="description" name="description"></textarea>
</section>
<section>
<label for="date">Date:</label>
<input required id="date" name="date" type="date">
</section>
<section>
<label class="checkbox"><span>State holiday</span><input id="state_holiday" value="true" name="state_holiday" type="checkbox"></label>
</section>
<section>
<label class="checkbox"><span>Religious holiday</span><input id="religious_holiday" value="true" name="religious_holiday" type="checkbox"></label>
</section>
<section class="actions">
<button type="submit">Create</button>
<button type="button" onclick="closeDialog('#create-card')">Cancel</button>
</section>
</form>
</dialog>

View File

@ -0,0 +1,24 @@
<dialog class="card" data-closeable id="check-is-a-holiday">
<div style="display: flex;">
<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>
</div>
<form method="get" action="/search">
<section>
<label for="country">Country:</label>
<select id="country" name="country">
<option value="HR">Croatia</option>
<option value="US">United states</option>
<option value="FR">France</option>
<option value="GB">Great Britain</option>
</select>
</section>
<section>
<label for="date">Date:</label>
<input id="date" name="date" type="date" required>
</section>
<section class="actions">
<button style="width: 100%;" type="submit">Check is a holiday</button>
</section>
</form>
</dialog>

View File

@ -0,0 +1,11 @@
<dialog class="card" id="delete-card">
<h3 class="card-title">Delete holiday</h3>
<p>Are you sure you want to delete "{{.Holiday.Name}}"?</p>
<form method="post" action="/admin/holidays/{{.Holiday.Id}}/delete">
<section class="actions">
<button type="submit">Delete</button>
<button type="button" onclick="closeDialog('#delete-card')">Cancel</button>
</section>
</form>
</dialog>

View File

@ -0,0 +1,37 @@
<dialog class="card" id="update-card">
<h3 class="card-title">Edit holiday</h3>
<form method="post" action="/admin/holidays">
<input type="hidden" name="id" value="{{.Holiday.Id}}">
<section>
<label for="country">Country:</label>
<select id="country" required name="country">
<option {{if eq .Holiday.Country "HR"}}selected{{end}} value="HR">Croatia</option>
<option {{if eq .Holiday.Country "US"}}selected{{end}} value="US">United states</option>
<option {{if eq .Holiday.Country "FR"}}selected{{end}} value="FR">France</option>
<option {{if eq .Holiday.Country "GB"}}selected{{end}} value="GB">Great Britain</option>
</select>
</section>
<section>
<label for="name">Name:</label>
<input required id="name" value="{{.Holiday.Name}}" name="name" type="text">
</section>
<section>
<label for="description">Description:</label>
<textarea id="description" name="description">{{.Holiday.Description}}</textarea>
</section>
<section>
<label for="date">Date:</label>
<input required id="date" value="{{.Holiday.Date.Format "2006-01-02"}}" name="date" type="date">
</section>
<section>
<label class="checkbox"><span>State holiday</span><input id="state_holiday" value="true" {{if .Holiday.IsStateHoliday}}checked{{end}} name="state_holiday" type="checkbox"></label>
</section>
<section>
<label class="checkbox"><span>Religious holiday</span><input id="religious_holiday" value="true" {{if .Holiday.IsReligiousHoliday}}checked{{end}} name="religious_holiday" type="checkbox"></label>
</section>
<section class="actions">
<button type="submit">Update</button>
<button type="button" onclick="closeDialog('#update-card')">Cancel</button>
</section>
</form>
</dialog>

View File

@ -1,104 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Holiday-api</title>
</head>
<body>
<h1><a href="/">Holiday-api</a></h1>
<p>Create query</p>
<form>
<label for="country">Country</label>
<select id="country" name="country">
<option value="HR">Croatia</option>
<option value="GB">Great Britain</option>
<option value="US">USA</option>
<option value="FR">France</option>
</select>
<div>
<label>Date selector</label>
<br>
<div style="border: 1px solid black">
<label for="date-year">Select year</label>
<input type="radio" id="date-year" name="date-selector" value="year">
<label for="date-year-year">Year</label>
<select id="date-year-year" name="year">
<option value="2020">2020</option>
<option value="2021">2021</option>
<option value="2022">2022</option>
<option value="2023">2023</option>
<option value="2024">2024</option>
<option value="2025">2025</option>
<option value="2026">2026</option>
<option value="2027">2027</option>
</select>
</div>
<div style="border: 1px solid black">
<label for="date-date">Select date</label>
<input type="radio" id="date-date" name="date-selector" value="date">
<label for="date-date-date">Date</label>
<input type="date" id="date-date-date" name="date-date-date">
</div>
<div style="border: 1px solid black">
<label for="date-date-range">Select date range</label>
<input type="radio" id="date-date-range" name="date-selector" value="date-range">
<label for="date-date-start-range">Start range</label>
<input type="date" id="date-date-start-range" name="date-date-start-range">
<label for="date-date-start-range-required">Required</label>
<input type="checkbox" id="date-date-start-range-required" name="date-date-start-range-required">
<label for="date-date-end-range">End range</label>
<input type="date" id="date-date-end-range" name="date-date-end-range">
<label for="date-date-end-range-required">Required</label>
<input type="checkbox" id="date-date-end-range-required" name="date-date-end-range-required">
</div>
<div style="border: 1px solid black">
<label for="date-all">All</label>
<input type="radio" id="date-all" name="date-selector" value="all">
</div>
</div>
<div>
<label>State holiday</label>
<br>
<label for="state-holiday-true">True</label>
<input type="radio" value="true" id="state-holiday-true" name="stateHoliday">
<label for="state-holiday-false">False</label>
<input type="radio" value="false" id="state-holiday-false" name="stateHoliday">
<label for="state-holiday-any">Any</label>
<input type="radio" value="" id="state-holiday-any" name="stateHoliday">
</div>
<div>
<label>Religious holiday</label>
<br>
<label for="religious-holiday-true">True</label>
<input type="radio" value="true" id="religious-holiday-true" name="religiousHoliday">
<label for="religious-holiday-false">False</label>
<input type="radio" value="false" id="religious-holiday-false" name="religiousHoliday">
<label for="religious-holiday-any">Any</label>
<input type="radio" value="" id="religious-holiday-any" name="religiousHoliday">
</div>
</form>
</body>
</html>

View File

@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Holiday-api</title>
<link rel="stylesheet" href="/assets/style.css">
<script src="/assets/global.js"></script>
<script src="/assets/documentation.js"></script>
</head>
<body>
<div id="dialog-container"></div>
<header>
<section class="container">
<h1><a href="/">Holiday-api</a></h1>
</section>
</header>
<nav>
<section class="container">
<a href="/">Search</a>
<a class="selected" href="#">Documentation</a>
<button data-type="dialog" data-trigger="#check-is-a-holiday" data-url="/dialogs/check-is-a-holiday">Check is a holiday</button>
</section>
</nav>
<main>
<section id="query">
<article class="card" style="margin: 1em;">
<form id="query-generator">
<section>
<label for="country">Country</label>
<select id="country" name="country">
<option value="HR">Croatia</option>
<option value="GB">Great Britain</option>
<option value="US">USA</option>
<option value="FR">France</option>
</select>
</section>
<section>
<label for="content-type">Content type</label>
<select id="content-type" name="content-type">
<option value="application/json">JSON</option>
<option value="application/xml">XML</option>
<option value="text/csv">CSV</option>
</select>
</section>
<article class="optional-selector">
<div class="header">
<section>
<label for="date-selector">Date selector</label>
<select data-type="section-selector" data-selector-prefix="date-selector" id="date-selector" name="date-selector">
<option value="year">Select year</option>
<option value="date">Select date</option>
<option value="range">Select date range</option>
<option value="all">All</option>
</select>
</section>
</div>
<div data-section-id="date-selector-year">
<section>
<label for="dsy-year">Year</label>
<select id="dsy-year" name="year">
<option value="2020">2020</option>
<option value="2021">2021</option>
<option value="2022">2022</option>
<option value="2023">2023</option>
<option value="2024">2024</option>
<option value="2025">2025</option>
<option value="2026">2026</option>
<option value="2027">2027</option>
</select>
</section>
</div>
<div data-section-id="date-selector-date">
<section>
<label for="dsd-date">Date</label>
<input type="date" id="dsd-date" name="dsd-date">
</section>
</div>
<div data-section-id="date-selector-range">
<section>
<label for="dsr-start-range">Start range</label>
<input type="date" id="dsr-start-range" name="dsr-start-range">
</section>
<section>
<label for="dsr-start-range-required">Required</label>
<input type="checkbox" id="dsr-start-range-required" name="dsr-start-range-required">
</section>
<section>
<label for="dsr-end-range">End range</label>
<input type="date" id="dsr-end-range" name="dsr-end-range">
</section>
<section>
<label for="dsr-end-range-required">Required</label>
<input type="checkbox" id="dsr-end-range-required" name="dsr-end-range-required">
</section>
</div>
</article>
<section class="radio-group" style="margin-top: 0.5em;">
<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" id="state-holiday" 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" id="religious-holiday" name="religious_holiday">
</section>
</form>
</article>
</section>
<section id="result" style="flex-grow: 1; margin: 1em;">
<h3 style="margin-bottom: 1em;">Query</h3>
<article id="result-content" class="card source-code">
Loading...
</article>
</section>
</main>
</body>
</html>

View File

@ -1,5 +0,0 @@
<html>
<body>
<h1>Test for error</h1>
</body>
</html>

View File

@ -1,44 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Holiday-api holiday</title>
</head>
<body>
<h1>Holiday-api - {{if .Id}}Updating{{else}}Creating{{end}}</h1>
<p>Page for creating or editing holidays</p>
<a href="/admin">Back</a>
<form method="post" action="/admin/holiday">
{{if .Id}}
<input type="hidden" id="id" name="id" value="{{.Id}}">
{{end}}
<label for="country">Country</label>
<select id="country" name="country">
<option value="HR" {{if eq .Country "HR"}}selected{{end}}>Croatia</option>
<option value="US" {{if eq .Country "US"}}selected{{end}}>USA</option>
<option value="GB" {{if eq .Country "GB"}}selected{{end}}>Great Britain</option>
<option value="FR" {{if eq .Country "FR"}}selected{{end}}>France</option>
</select>
<label for="name">Name</label>
<input type="text" id="name" name="name" value="{{.Name}}">
<label for="description">Description</label>
<textarea id="description" name="description">{{.Description}}</textarea>
<label for="date">Date</label>
<input type="date" id="date" name="date" value="{{.Date.String}}">
<label for="state-holiday">State holiday</label>
<input type="checkbox" id="state-holiday" value="true" name="stateHoliday" {{if .IsStateHoliday}}checked{{end}}>
<label for="religious-holiday">Religious holiday</label>
<input type="checkbox" id="religious-holiday" value="true" name="religiousHoliday" {{if .IsReligiousHoliday}}checked{{end}}>
<button type="submit">{{if .Id}}Update{{else}}Create{{end}}</button>
</form>
</body>
</html>

View File

@ -2,61 +2,96 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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">
<script src="/assets/global.js"></script>
</head> </head>
<body> <body>
<h1><a href="/">Holiday-api</a></h1> <div id="dialog-container"></div>
<p>Welcome to holiday api - simple page for tracking holidays</p> <header>
<div> <section class="container">
<h2>Is a holiday?</h2> <h1><a href="/">Holiday-api</a></h1>
<form method="get" action="/search/date"> </section>
<label for="country">Country</label> </header>
<select id="country" name="country"> <nav>
<option value="HR">Croatia</option> <section class="container">
<option value="GB">Great Britain</option> <a class="selected" href="#">Search</a>
<option value="US">USA</option> <a href="/documentation">Documentation</a>
<option value="FR">France</option> <button data-type="dialog" data-trigger="#check-is-a-holiday" data-url="/dialogs/check-is-a-holiday">Check is a holiday</button>
</select> </section>
</nav>
<label for="date">Date</label> <main>
<input id="date" name="date" type="date"> <section id="search">
<article class="card">
<button type="submit">Check is a holiday</button> <form action="/" method="get">
</form> <section>
</div> <label for="country">Country:</label>
<select id="country" name="country">
<div> <option {{if eq .Search.Country "HR"}}selected{{end}} value="HR">Croatia</option>
<h2>Find holidays for</h2> <option {{if eq .Search.Country "US"}}selected{{end}} value="US">United states</option>
<form method="get" action="/search"> <option {{if eq .Search.Country "FR"}}selected{{end}} value="FR">France</option>
<label for="country">Country</label> <option {{if eq .Search.Country "GB"}}selected{{end}} value="GB">Great Britain</option>
<select id="country" name="country"> </select>
<option value="HR">Croatia</option> </section>
<option value="GB">Great Britain</option> <section>
<option value="US">USA</option> <label for="year">Year:</label>
<option value="FR">France</option> <select id="year" name="year">
</select> <option {{if intpeq .Search.Year 2020}}selected{{end}} value="2020">2020</option>
<option {{if intpeq .Search.Year 2021}}selected{{end}} value="2021">2021</option>
<label for="year">Year</label> <option {{if intpeq .Search.Year 2022}}selected{{end}} value="2022">2022</option>
<select id="year" name="year"> <option {{if intpeq .Search.Year 2023}}selected{{end}} value="2023">2023</option>
<option value="2020">2020</option> </select>
<option value="2021">2021</option> </section>
<option value="2022">2022</option> <section class="radio-group">
<option value="2023">2023</option> <label>Is state holiday:</label>
<option value="2024">2024</option> <div>
<option value="2025">2025</option> <input type="radio" value="true" name="sh" id="sh_true"><label for="sh_true">True
<option value="2026">2026</option> </label><input type="radio" value="false" name="sh" id="sh_false"><label for="sh_false">False
<option value="2027">2027</option> </label><input type="radio" value="" name="sh" id="sh_any"><label for="sh_any">All</label>
</select> </div>
<button type="submit">Find holidays</button> <input type="hidden" value="{{.Search.StateHoliday}}" name="state_holiday">
</form> </section>
</div> <section class="radio-group">
<!-- <label>Is religious holiday:</label>
<div> <div>
<h2>Create api query</h2> <input type="radio" value="true" name="rh" id="rh_true"><label for="rh_true">True
<a href="/docs">Goto query editor</a> </label><input type="radio" value="false" name="rh" id="rh_false"><label for="rh_false">False
</div> </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>

View File

@ -2,37 +2,76 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Holiday-api</title> <title>Holiday-api | Admin dashboard</title>
<link rel="stylesheet" href="/assets/style.css">
<script src="/assets/global.js"></script>
</head> </head>
<body> <body>
<h1><a href="/">Holiday-api</a></h1> <div id="dialog-container"></div>
<p>Search results</p> <header>
<section class="container">
<div> <h1><a href="/">Holiday-api | {{.Search.Date.Format "2006-01-02"}}</a></h1>
<table> </section>
<thead> </header>
<tr> <nav>
<th>Name</th> <section class="container">
<th>Description</th> <a href="/">Search</a>
<th>Date</th> <a href="/documentation">Documentation</a>
<th>State holiday</th> <a class="selected" href="#">For date</a>
<th>Religious holiday</th> </section>
</tr> </nav>
</thead> <main>
<tbody> <section id="search">
<article class="card">
<h2 style="margin-right: 1em;">Is it a holiday?</h2>
<form method="get" action="/search">
<section>
<label for="country">Country:</label>
<select id="country" name="country">
<option {{if eq .Search.Country "HR"}}selected{{end}} value="HR">Croatia</option>
<option {{if eq .Search.Country "US"}}selected{{end}} value="US">United states</option>
<option {{if eq .Search.Country "FR"}}selected{{end}} value="FR">France</option>
<option {{if eq .Search.Country "GB"}}selected{{end}} value="GB">Great Britain</option>
</select>
</section>
<section>
<label for="date">Date:</label>
<input id="date" value="{{.Search.Date.Format "2006-01-02"}}" name="date" type="date" required>
</section>
<section class="actions">
<button style="width: 100%;" type="submit">Check is a holiday</button>
</section>
</form>
</article>
</section>
<section id="results">
<h2 style="margin-bottom: 1em;">Results</h2>
{{range $entry := .Holidays}} {{range $entry := .Holidays}}
<tr> <article class="single-holiday">
<td>{{$entry.Name}}</td> <table class="clean">
<td>{{$entry.Description}}</td> <tbody>
<td>{{$entry.Date.Format "2006-01-02"}}</td> <tr>
<td>{{$entry.IsStateHoliday}}</td> <th>Name: </th>
<td>{{$entry.IsReligiousHoliday}}</td> <td>{{$entry.Name}}</td>
</tr> </tr>
<tr>
<th>Description: </th>
<td>{{$entry.Description}}</td>
</tr>
<tr>
<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>
</tr>
<tr>
<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>
</tr>
</tbody>
</table>
</article>
{{end}} {{end}}
</tbody> </section>
</table> </main>
</div>
</body> </body>
</html> </html>

View File

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Holiday-api</title>
</head>
<body>
<h1><a href="/">Holiday-api</a></h1>
<p>{{if .Holidays}}Yes it is a holday{{else}}No it isn't a holiday{{end}}</p>
<p>Search results</p>
<div>
{{if .Holidays}}
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Date</th>
<th>State holiday</th>
<th>Religious holiday</th>
</tr>
</thead>
<tbody>
{{range $entry := .Holidays}}
<tr>
<td>{{$entry.Name}}</td>
<td>{{$entry.Description}}</td>
<td>{{$entry.Date.Format "2006-01-02"}}</td>
<td>{{$entry.IsStateHoliday}}</td>
<td>{{$entry.IsReligiousHoliday}}</td>
</tr>
{{end}}
</tbody>
</table>
{{end}}
</div>
</body>
</html>