merged.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
{:db {:server "localhost"}
|
||||
:scheme "http"
|
||||
:base-url "http://localhost:3449"
|
||||
:solr-uri "http://localhost:8983"
|
||||
:solr-impl :solr
|
||||
:client-config {:server-type :dev-local
|
||||
@@ -24,21 +25,22 @@
|
||||
:yodlee-cobrand-name "restserver"
|
||||
:yodlee-cobrand-login "sbCobda48aa19712a83c3ca4e935dd5e5d46b1a"
|
||||
:yodlee-cobrand-password "0a07ea32-1b5d-461b-ad0f-2752cdd77602"
|
||||
:yodlee-user-login "sbMemda48aa19712a83c3ca4e935dd5e5d46b1a4"
|
||||
:yodlee-user-login "G7T9kiwaG8rMiykdV4pckmQnfj4OM2pf"
|
||||
:yodlee-user-password "sbMemda48aa19712a83c3ca4e935dd5e5d46b1a4#123"
|
||||
:yodlee-base-url "https://developer.api.yodlee.com/ysl"
|
||||
:yodlee-app "10003600"
|
||||
:yodlee-fastlink "https://node.developer.yodlee.com/authenticate/restserver/?channelAppName=restserver"
|
||||
:yodlee-fastlink "https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink"
|
||||
:run-web? true
|
||||
:run-background? true
|
||||
:dd-env "dev"
|
||||
:dd-service "integreat-app"
|
||||
:yodlee2-admin-user "e02b38f9-9865-4264-8e4f-6a5ac2c500b0_ADMIN"
|
||||
:yodlee2-admin-user "50ec7e57-297d-4970-941e-1cb07b8dcb4e_ADMIN"
|
||||
:yodlee2-integreat-user "integreat-main"
|
||||
:yodlee2-client-id "l6sUyK2NEq3mwopISHlFGWUcJ1U8OUQd"
|
||||
:yodlee2-client-secret "wZQHoGEkv5AGG2ZH"
|
||||
:yodlee2-base-url "https://development.api.yodlee.com/ysl"
|
||||
:yodlee2-fastlink "https://fl4.preprod.yodlee.com/authenticate/USDevexPreProd2-195/fastlink/?channelAppName=usdevexpreprod2"
|
||||
:yodlee2-test-user "sbMem5f61421aba2161"
|
||||
:yodlee2-client-id "G7T9kiwaG8rMiykdV4pckmQnfj4OM2pf"
|
||||
:yodlee2-client-secret "8I0mmq1wmAWSSpr9"
|
||||
:yodlee2-base-url "https://sandbox.api.yodlee.com/ysl"
|
||||
:yodlee2-fastlink "https://fl4.sandbox.yodlee.com/authenticate/restserver/fastlink"
|
||||
|
||||
:square-config {"NGE1"
|
||||
{:square-location "SCX0Y8CTGM1S0",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{:scheme "https"
|
||||
:db-name "prod-mirror2"
|
||||
:base-url "https://prod-cloud.app.integreatconsult.com"
|
||||
:solr-uri "http://solr-prod-cloud.local:8983"
|
||||
:solr-impl :solr
|
||||
:datomic-url "datomic:ddb://us-east-1/iol-dev/dev"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{:db {:server "database"}
|
||||
:datomic-url "datomic:ddb://us-east-1/integreat/integreat-prod"
|
||||
:base-url "https://app.integreatconsult.com"
|
||||
:solr-uri "http://solr-prod.local:8983"
|
||||
:solr-impl :solr
|
||||
:scheme "https"
|
||||
@@ -23,7 +24,6 @@
|
||||
:yodlee-proxy-port 8888
|
||||
:run-background? false
|
||||
:run-web? true
|
||||
:yodlee2-admin-user "93398522-412b-470d-8400-3691392b12fb_ADMIN"
|
||||
:yodlee2-integreat-user "integreat-main"
|
||||
:yodlee2-client-id "3AATcwfPsWP1rP9oDoo4HvZhtaroGVcA"
|
||||
:yodlee2-client-secret "cXTBmKbGfkaBFIpM"
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
.htmx-added .fade-in {
|
||||
opacity: 0.0 !important;
|
||||
}
|
||||
|
||||
.htmx-added.fade-in {
|
||||
opacity: 0.0 !important;
|
||||
}
|
||||
.fade-in {
|
||||
opacity: 1.0;
|
||||
}
|
||||
@@ -12,6 +16,9 @@
|
||||
.htmx-added .slide-up {
|
||||
@apply translate-y-5 !important;
|
||||
}
|
||||
.hidden .slide-up {
|
||||
@apply translate-y-5 !important;
|
||||
}
|
||||
.slide-up {
|
||||
@apply translate-y-0;
|
||||
}
|
||||
@@ -65,3 +72,7 @@
|
||||
.fade-out {
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.min-h-content {
|
||||
min-height: calc(100vh - 4em);
|
||||
}
|
||||
|
||||
16
resources/public/js/htmx-disable.js
Normal file
16
resources/public/js/htmx-disable.js
Normal file
@@ -0,0 +1,16 @@
|
||||
htmx.defineExtension('disable-submit', {
|
||||
onEvent: function (name, evt, data) {
|
||||
let elt = evt.detail.elt;
|
||||
let result = elt.querySelectorAll('.hx-disable');
|
||||
|
||||
if (name === 'htmx:beforeRequest') {
|
||||
result.forEach(element => element.disabled = true);
|
||||
if (elt.classList.contains('hx-disable')) {
|
||||
elt.disabled = true;}
|
||||
|
||||
} else if(name == 'htmx:afterRequest') {
|
||||
result.forEach(element => element.disabled = false);
|
||||
if (elt.classList.contains('hx-disable')) elt.disabled = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1073,6 +1073,18 @@ input:checked + .toggle-bg {
|
||||
top: 1.25rem;
|
||||
}
|
||||
|
||||
.right-4 {
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.right-2 {
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.top-2 {
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
@@ -1109,6 +1121,18 @@ input:checked + .toggle-bg {
|
||||
grid-column: span 6 / span 6;
|
||||
}
|
||||
|
||||
.m-2 {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.m-8 {
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.m-4 {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.mx-2 {
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
@@ -1124,11 +1148,21 @@ input:checked + .toggle-bg {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.my-20 {
|
||||
margin-top: 5rem;
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
|
||||
.my-4 {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.my-0 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.-mb-1 {
|
||||
margin-bottom: -0.25rem;
|
||||
}
|
||||
@@ -1149,10 +1183,6 @@ input:checked + .toggle-bg {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ml-0 {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.ml-1 {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
@@ -1165,10 +1195,6 @@ input:checked + .toggle-bg {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
.ml-8 {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
.mr-16 {
|
||||
margin-right: 4rem;
|
||||
}
|
||||
@@ -1201,6 +1227,10 @@ input:checked + .toggle-bg {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.mt-8 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
@@ -1253,6 +1283,10 @@ input:checked + .toggle-bg {
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.h-48 {
|
||||
height: 12rem;
|
||||
}
|
||||
|
||||
.h-5 {
|
||||
height: 1.25rem;
|
||||
}
|
||||
@@ -1269,6 +1303,10 @@ input:checked + .toggle-bg {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.h-96 {
|
||||
height: 24rem;
|
||||
}
|
||||
|
||||
.h-\[calc\(100\%-1rem\)\] {
|
||||
height: calc(100% - 1rem);
|
||||
}
|
||||
@@ -1281,28 +1319,12 @@ input:checked + .toggle-bg {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.h-2 {
|
||||
height: 0.5rem;
|
||||
.h-16 {
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.h-48 {
|
||||
height: 12rem;
|
||||
}
|
||||
|
||||
.h-64 {
|
||||
height: 16rem;
|
||||
}
|
||||
|
||||
.h-80 {
|
||||
height: 20rem;
|
||||
}
|
||||
|
||||
.h-1\/2 {
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.h-96 {
|
||||
height: 24rem;
|
||||
.max-h-96 {
|
||||
max-height: 24rem;
|
||||
}
|
||||
|
||||
.max-h-full {
|
||||
@@ -1345,21 +1367,12 @@ input:checked + .toggle-bg {
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
.w-auto {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-max {
|
||||
width: -moz-max-content;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.w-2 {
|
||||
width: 0.5rem;
|
||||
.w-16 {
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.max-w-2xl {
|
||||
@@ -1370,6 +1383,10 @@ input:checked + .toggle-bg {
|
||||
max-width: 1536px;
|
||||
}
|
||||
|
||||
.max-w-screen-lg {
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
||||
.max-w-sm {
|
||||
max-width: 24rem;
|
||||
}
|
||||
@@ -1510,16 +1527,16 @@ input:checked + .toggle-bg {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gap-4 {
|
||||
gap: 1rem;
|
||||
.gap-1 {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.gap-6 {
|
||||
gap: 1.5rem;
|
||||
.gap-4 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.gap-8 {
|
||||
@@ -1568,18 +1585,6 @@ input:checked + .toggle-bg {
|
||||
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.space-y-8 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(2rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
@@ -1633,10 +1638,6 @@ input:checked + .toggle-bg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.rounded-xl {
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.rounded-l-lg {
|
||||
border-top-left-radius: 0.5rem;
|
||||
border-bottom-left-radius: 0.5rem;
|
||||
@@ -1706,6 +1707,11 @@ input:checked + .toggle-bg {
|
||||
border-color: rgb(175 211 130 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-red-300 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(255 104 104 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(204 235 251 / var(--tw-bg-opacity));
|
||||
@@ -1746,6 +1752,11 @@ input:checked + .toggle-bg {
|
||||
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-green-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(228 240 213 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-green-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(121 181 46 / var(--tw-bg-opacity));
|
||||
@@ -1756,14 +1767,9 @@ input:checked + .toggle-bg {
|
||||
background-color: rgb(242 248 234 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-red-700 {
|
||||
.bg-red-50 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(153 2 2 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-slate-300 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(203 213 225 / var(--tw-bg-opacity));
|
||||
background-color: rgb(255 230 230 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
@@ -1780,19 +1786,9 @@ input:checked + .toggle-bg {
|
||||
background-color: rgb(253 246 178 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-200 {
|
||||
.bg-green-200 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(153 215 247 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-300 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(102 196 242 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-green-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(228 240 213 / var(--tw-bg-opacity));
|
||||
background-color: rgb(201 225 171 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-opacity-50 {
|
||||
@@ -1823,14 +1819,6 @@ input:checked + .toggle-bg {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.p-8 {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.p-5 {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
@@ -1866,11 +1854,6 @@ input:checked + .toggle-bg {
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.py-1\.5 {
|
||||
padding-top: 0.375rem;
|
||||
padding-bottom: 0.375rem;
|
||||
}
|
||||
|
||||
.py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
@@ -1896,6 +1879,14 @@ input:checked + .toggle-bg {
|
||||
padding-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.pb-2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pb-3 {
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.pl-10 {
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
@@ -1904,6 +1895,10 @@ input:checked + .toggle-bg {
|
||||
padding-left: 2.75rem;
|
||||
}
|
||||
|
||||
.pl-2 {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.pl-3 {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
@@ -1912,6 +1907,14 @@ input:checked + .toggle-bg {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.pr-2 {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.pr-2\.5 {
|
||||
padding-right: 0.625rem;
|
||||
}
|
||||
|
||||
.pt-16 {
|
||||
padding-top: 4rem;
|
||||
}
|
||||
@@ -1924,16 +1927,8 @@ input:checked + .toggle-bg {
|
||||
padding-top: 1.25rem;
|
||||
}
|
||||
|
||||
.pb-2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pr-2 {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.pr-2\.5 {
|
||||
padding-right: 0.625rem;
|
||||
.pt-8 {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
@@ -1978,10 +1973,6 @@ input:checked + .toggle-bg {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-light {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -2054,34 +2045,38 @@ input:checked + .toggle-bg {
|
||||
color: rgb(17 24 39 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-green-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(48 72 18 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-primary-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(97 145 37 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-red-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(102 1 1 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-yellow-400 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(227 160 8 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-yellow-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(114 59 19 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-200 {
|
||||
.text-red-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(229 231 235 / var(--tw-text-opacity));
|
||||
color: rgb(204 2 2 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-green-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(48 72 18 / var(--tw-text-opacity));
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.opacity-0 {
|
||||
@@ -2169,6 +2164,10 @@ input:checked + .toggle-bg {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.duration-500 {
|
||||
transition-duration: 500ms;
|
||||
}
|
||||
|
||||
.duration-75 {
|
||||
transition-duration: 75ms;
|
||||
}
|
||||
@@ -2185,6 +2184,10 @@ input:checked + .toggle-bg {
|
||||
opacity: 0.0 !important;
|
||||
}
|
||||
|
||||
.htmx-added.fade-in {
|
||||
opacity: 0.0 !important;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
opacity: 1.0;
|
||||
}
|
||||
@@ -2194,6 +2197,11 @@ input:checked + .toggle-bg {
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) !important;
|
||||
}
|
||||
|
||||
.hidden .slide-up {
|
||||
--tw-translate-y: 1.25rem !important;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) !important;
|
||||
}
|
||||
|
||||
.slide-up {
|
||||
--tw-translate-y: 0px;
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
@@ -2270,6 +2278,10 @@ input:checked + .toggle-bg {
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
.min-h-content {
|
||||
min-height: calc(100vh - 4em);
|
||||
}
|
||||
|
||||
.hover\:scale-105:hover {
|
||||
--tw-scale-x: 1.05;
|
||||
--tw-scale-y: 1.05;
|
||||
@@ -2296,6 +2308,16 @@ input:checked + .toggle-bg {
|
||||
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-gray-200:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-green-100:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(228 240 213 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-green-600:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(97 145 37 / var(--tw-bg-opacity));
|
||||
@@ -2316,11 +2338,6 @@ input:checked + .toggle-bg {
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-gray-200:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:text-blue-600:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(0 125 187 / var(--tw-text-opacity));
|
||||
@@ -2446,6 +2463,11 @@ input:checked + .toggle-bg {
|
||||
border-color: rgb(0 156 234 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:border-gray-500 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(107 114 128 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:border-gray-600 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(75 85 99 / var(--tw-border-opacity));
|
||||
@@ -2504,21 +2526,16 @@ input:checked + .toggle-bg {
|
||||
background-color: rgb(97 145 37 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:bg-yellow-900 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(99 49 18 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:bg-blue-700 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(0 94 140 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:bg-green-900 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(24 36 9 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:bg-yellow-900 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(99 49 18 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:bg-opacity-80 {
|
||||
--tw-bg-opacity: 0.8;
|
||||
}
|
||||
@@ -2538,6 +2555,11 @@ input:checked + .toggle-bg {
|
||||
color: rgb(243 244 246 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:text-gray-200 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(229 231 235 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:text-gray-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(209 213 219 / var(--tw-text-opacity));
|
||||
@@ -2548,6 +2570,21 @@ input:checked + .toggle-bg {
|
||||
color: rgb(156 163 175 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:text-gray-50 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(249 250 251 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:text-green-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(175 211 130 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:text-red-400 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 53 53 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:text-white {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
@@ -2558,16 +2595,6 @@ input:checked + .toggle-bg {
|
||||
color: rgb(250 202 21 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:text-green-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(175 211 130 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:text-gray-50 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(249 250 251 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:placeholder-gray-400::-moz-placeholder {
|
||||
--tw-placeholder-opacity: 1;
|
||||
color: rgb(156 163 175 / var(--tw-placeholder-opacity));
|
||||
@@ -2602,6 +2629,11 @@ input:checked + .toggle-bg {
|
||||
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:hover\:bg-green-600:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(97 145 37 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:hover\:bg-green-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(73 109 28 / var(--tw-bg-opacity));
|
||||
@@ -2642,6 +2674,11 @@ input:checked + .toggle-bg {
|
||||
--tw-ring-color: rgb(0 156 234 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:focus\:ring-blue-800:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(0 62 94 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark .dark\:focus\:ring-gray-600:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(75 85 99 / var(--tw-ring-opacity));
|
||||
@@ -2677,14 +2714,14 @@ input:checked + .toggle-bg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sm\:flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sm\:rounded-lg {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.sm\:p-6 {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.sm\:py-5 {
|
||||
padding-top: 1.25rem;
|
||||
padding-bottom: 1.25rem;
|
||||
@@ -2742,10 +2779,6 @@ input:checked + .toggle-bg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.lg\:inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.lg\:flex {
|
||||
display: flex;
|
||||
}
|
||||
@@ -2804,10 +2837,6 @@ input:checked + .toggle-bg {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
.lg\:pl-3\.5 {
|
||||
padding-left: 0.875rem;
|
||||
}
|
||||
|
||||
.lg\:pl-64 {
|
||||
padding-left: 16rem;
|
||||
}
|
||||
|
||||
@@ -579,12 +579,14 @@
|
||||
|
||||
(defn add-sorter-fields [q sort-map args]
|
||||
(reduce
|
||||
(fn [q {:keys [sort-key]}]
|
||||
(fn [q {:keys [sort-key] :as z}]
|
||||
(prn z)
|
||||
(println (class sort-key))
|
||||
(merge-query q
|
||||
{:query {:find [(symbol (str "?sort-" sort-key))]
|
||||
:where (sort-map
|
||||
sort-key
|
||||
(println "Warning, trying to sort by unsupported field" sort-key))}}))
|
||||
(println "Warning, trying to sort by unsupported field" sort-key, "sort map" (pr-str sort-map)))}}))
|
||||
q
|
||||
(:sort args)))
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
[clj-time.coerce :as c]
|
||||
[datomic.api :as dc]))
|
||||
|
||||
(def default-read '[:db/id :report/client :report/created :report/url :report/name :report/creator])
|
||||
|
||||
(defn raw-graphql-ids [db args]
|
||||
(let [query (cond-> {:query {:find []
|
||||
:in ['$ ]
|
||||
@@ -43,8 +45,7 @@
|
||||
(apply-pagination args))))
|
||||
|
||||
(defn graphql-results [ids db args]
|
||||
(let [results (->> (pull-many db '[:db/id :report/client :report/created :report/url :report/name :report/creator]
|
||||
ids)
|
||||
(let [results (->> (pull-many db default-read ids)
|
||||
(map #(update % :report/created c/from-date))
|
||||
(group-by :db/id))]
|
||||
(->> ids
|
||||
@@ -58,13 +59,9 @@
|
||||
(map :db/id (:report/client r))))))))
|
||||
|
||||
(defn get-graphql [args]
|
||||
(clojure.pprint/pprint args)
|
||||
(let [db (dc/db conn)
|
||||
{ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)]
|
||||
|
||||
[(->> (graphql-results ids-to-retrieve db args))
|
||||
matching-count]))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
[ring.util.response :as response]
|
||||
[unilog.context :as lc]
|
||||
[clj-time.coerce :as coerce]
|
||||
[clj-time.core :as time]))
|
||||
[clj-time.core :as time]
|
||||
[cemerick.url :as url]))
|
||||
|
||||
(when (:aws-access-key-id env)
|
||||
(defcredential (:aws-access-key-id env) (:aws-secret-access-key env) (:aws-region env)))
|
||||
@@ -161,9 +162,17 @@
|
||||
(let [end-time (time/plus (time/now) (time/days 14))]
|
||||
(assoc response :session (assoc session ::idle-timeout (coerce/to-date end-time)))))))))))
|
||||
|
||||
(defn wrap-hx-current-url-params
|
||||
[handler ]
|
||||
(fn [request]
|
||||
(let [query-params (some-> (get-in request [:headers "hx-current-url"]) (url/url ) :query)
|
||||
request (assoc request :hx-query-params query-params)]
|
||||
(handler request))))
|
||||
|
||||
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
|
||||
(def app
|
||||
(-> route-handler
|
||||
(wrap-hx-current-url-params)
|
||||
(wrap-guess-route)
|
||||
(wrap-authorization auth-backend
|
||||
)
|
||||
|
||||
@@ -267,8 +267,9 @@
|
||||
))
|
||||
(into [["Vendor Name" "Address" "City" "State" "Zip" "Terms" "Account" "Account Code"]]))]
|
||||
{:body
|
||||
(into (list)
|
||||
data)})))
|
||||
(into []
|
||||
data)
|
||||
:headers {"content-disposition" "attachment; filename=\"vendors.csv\""}})))
|
||||
|
||||
(defn export-ledger [{:keys [identity query-params]}]
|
||||
(let [start-date (or (some-> (query-params "start-date")
|
||||
@@ -426,7 +427,7 @@
|
||||
"expected-deposit/" {#"export/?" {:get :export-expected-deposits}}
|
||||
"clients/" {#"export/?" {:get :export-clients}}
|
||||
"vendors/" {#"export/?" {:get :export-vendors}
|
||||
"/company" {#"export/?" {:get :export-company-vendors}}}
|
||||
"company/" {#"export" {:get :export-company-vendors}}}
|
||||
"ledger/" {#"export/?" {:get :export-ledger}}
|
||||
"accounts/" {#"export/?" {:get :export-accounts}}
|
||||
"transactions/" {#"export/?" {:get :export-transactions}
|
||||
|
||||
66
src/clj/auto_ap/ssr/company.clj
Normal file
66
src/clj/auto_ap/ssr/company.clj
Normal file
@@ -0,0 +1,66 @@
|
||||
(ns auto-ap.ssr.company
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.datomic.clients :refer [full-read]]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[cemerick.url :as url]
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[bidi.bidi :as bidi]))
|
||||
|
||||
(defn please-select-client-screen* []
|
||||
[:div.grid.grid-cols-3
|
||||
(com/content-card {}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
"Please select a company"]
|
||||
])])
|
||||
|
||||
(defn main-content* [{:keys [client]}]
|
||||
(if-not client
|
||||
(please-select-client-screen*)
|
||||
(let [client (dc/pull (dc/db conn) full-read (:db/id client))]
|
||||
[:div.grid.grid-cols-3.gap-4
|
||||
(com/content-card {}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
(:client/name client)]
|
||||
(when-let [address (-> client :client/address)]
|
||||
[:div.flex.flex-col.gap-1.text-lg.dark:text-white.text-gray-700
|
||||
[:p (-> address :address/street1)]
|
||||
[:p (-> address :address/street2)]
|
||||
[:p (-> address :address/city) " "
|
||||
(-> address :address/state) ", "
|
||||
(-> address :address/zip)]])]
|
||||
)
|
||||
(com/content-card {}
|
||||
[:div.col-span-1.p-4 {:class "p-4 sm:p-6"}
|
||||
[:h3 {:class "mb-4 text-xl font-semibold dark:text-white"}
|
||||
"Downloads"]
|
||||
[:a {:href (str (assoc (url/url (str (:base-url env) "/api/vendors/company/export"))
|
||||
:query {"client" (:client/code client)}))}
|
||||
(com/button {:color :primary}
|
||||
"Download vendor list"
|
||||
(com/button-icon {} svg/download))]])])))
|
||||
|
||||
(defn page [{:keys [identity matched-route] :as request}]
|
||||
(base-page
|
||||
request
|
||||
(com/page {:nav (com/company-aside-nav)
|
||||
:active-client (:client (:session request))
|
||||
:identity (:identity request)
|
||||
:app-params {
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-swap "outerHTML swap:300ms"}}
|
||||
(com/breadcrumbs {}
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"My Company"])
|
||||
(main-content* {:client (:client (:session request))}))
|
||||
nil))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.ssr.utils :refer [html-response form-data->map path->name]]
|
||||
[bidi.bidi :as bidi]
|
||||
[clojure.string :as str]
|
||||
[datomic.api :as dc]
|
||||
@@ -53,7 +53,6 @@
|
||||
[(>= ?d #inst "2022-01-01T08:00")]
|
||||
[(< ?d #inst "2023-01-01T08:00")]
|
||||
[?p :payment/type :payment-type/check]
|
||||
|
||||
[?p :payment/amount ?a]
|
||||
[?p :payment/vendor ?v]]
|
||||
(dc/db conn)
|
||||
@@ -113,7 +112,6 @@
|
||||
[(>= ?d #inst "2022-01-01T08:00")]
|
||||
[(< ?d #inst "2023-01-01T08:00")]
|
||||
[?p :payment/type :payment-type/check]
|
||||
|
||||
[?p :payment/amount ?a]
|
||||
[?p :payment/vendor ?v]]
|
||||
(dc/db conn)
|
||||
@@ -121,121 +119,101 @@
|
||||
(->> results
|
||||
(filter (fn [[_ _ a]]
|
||||
(>= (or a 0.0) 600.0)))
|
||||
(take 200)
|
||||
(sort-by (fn [[client _ amount]]
|
||||
[(:client/code client ) amount])))))
|
||||
[(:client/code client ) amount]))
|
||||
(into []))))
|
||||
|
||||
(defn table* [{:keys [identity session]} & {:keys [flash-id]}]
|
||||
(let [companies (take 30 (get-1099-companies identity session))]
|
||||
[:div#vendor-table {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099-vendor-table
|
||||
:request-method :get)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-swap "outerHTML swap:300ms"}
|
||||
(com/content-card {}
|
||||
[:div {:class "flex flex-col px-4 py-3 space-y-3 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:space-x-4 text-gray-800 dark:text-gray-100"}
|
||||
[:div
|
||||
[:h1.text-2xl.mb-3.font-bold "1099 Vendor Info"]
|
||||
[:div {:class "flex items-center flex-1 space-x-4"}
|
||||
[:h5
|
||||
[:span "Total Vendors:"]
|
||||
[:span {:class "dark:text-white pl-4"} (count companies)]]]]
|
||||
[:div {:class "flex flex-col flex-shrink-0 space-y-3 md:flex-row md:items-center lg:justify-end md:space-y-0 md:space-x-3"}
|
||||
(com/button {:color :primary}
|
||||
(com/button-icon {} svg/refresh)
|
||||
"Add new product")
|
||||
(com/button {:color :secondary}
|
||||
(com/button-icon {} svg/refresh)
|
||||
"Update stocks 1/250")
|
||||
(com/icon-button {}
|
||||
svg/upload)]]
|
||||
[:div {:class "overflow-x-auto"}
|
||||
(apply com/data-grid {:headers [(com/data-grid-header {} "Client")
|
||||
(com/data-grid-header {} "Vendor Name")
|
||||
(com/data-grid-header {:class "hidden md:table-cell"} "TIN")
|
||||
(com/data-grid-header {:class "hidden lg:table-cell"} "Address")
|
||||
(com/data-grid-header {})
|
||||
(com/data-grid-header {})]}
|
||||
(for [[client vendor amount] companies]
|
||||
(com/data-grid-row
|
||||
{:class (when (= flash-id
|
||||
(:db/id vendor))
|
||||
"live-added")}
|
||||
(com/data-grid-cell {} (:client/code client))
|
||||
(com/data-grid-cell
|
||||
{}
|
||||
[:div.flex.whitespace-nowrap.items-center.gap-4
|
||||
[:div [:div (:vendor/name vendor)]
|
||||
[:div.text-sm.text-gray-400
|
||||
(or (-> vendor :vendor/legal-entity-name not-empty)
|
||||
(str (-> vendor :vendor/legal-entity-first-name) " "
|
||||
(-> vendor :vendor/legal-entity-middle-name) " "
|
||||
(-> vendor :vendor/legal-entity-last-name)))]]
|
||||
(when-let [t99-type (some-> vendor :vendor/legal-entity-1099-type :db/ident name)]
|
||||
(com/pill
|
||||
{:class "text-xs font-medium"
|
||||
:color :primary}
|
||||
(str/capitalize t99-type))
|
||||
)])
|
||||
(com/data-grid-cell
|
||||
{:class "hidden md:table-cell"}
|
||||
[:div.flex.gap-4
|
||||
(when-let [tin (-> vendor :vendor/legal-entity-tin)]
|
||||
[:span {:class "text-xs font-medium py-0.5 "}
|
||||
tin])
|
||||
(when-let [tin-type (some-> vendor :vendor/legal-entity-tin-type :db/ident name)]
|
||||
(com/pill {:class "text-xs font-medium"
|
||||
:color :yellow}
|
||||
(name tin-type)))])
|
||||
(com/data-grid-cell
|
||||
{:class "hidden lg:table-cell"}
|
||||
(if (-> vendor :vendor/address :address/street1)
|
||||
[:div
|
||||
[:div (-> vendor :vendor/address :address/street1)] " "
|
||||
[:div
|
||||
(-> vendor :vendor/address :address/street2)] " "
|
||||
[:div
|
||||
(-> vendor :vendor/address :address/city) " "
|
||||
(-> vendor :vendor/address :address/state) ","
|
||||
(-> vendor :vendor/address :address/zip)]]
|
||||
[:p.text-sm.italic.text-gray-400 "No address"]))
|
||||
(com/data-grid-cell {}
|
||||
(com/pill {:class "text-xs font-medium"
|
||||
:color :primary}
|
||||
"Paid $" (Math/round amount)))
|
||||
(com/data-grid-right-stack-cell
|
||||
{}
|
||||
(if (cannot-overwrite? vendor)
|
||||
[:div (com/link {:href "mailto:ben@integreatconsult.com"} "Contact Integreat")]
|
||||
(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099-vendor-dialog
|
||||
:vendor-id (:db/id vendor))
|
||||
:hx-target "#modal-content"
|
||||
:hx-swap "outerHTML"}
|
||||
svg/pencil))))))]
|
||||
(com/paginator))]))
|
||||
|
||||
(defn form-data->map [form-data]
|
||||
(reduce-kv
|
||||
(fn [acc k v]
|
||||
(cond (and (string? v)
|
||||
(empty? v))
|
||||
acc
|
||||
|
||||
:else
|
||||
(assoc-in acc (->> (str/split k #"_")
|
||||
(mapv #(apply keyword (str/split % #"/"))))
|
||||
v)))
|
||||
{}
|
||||
form-data))
|
||||
|
||||
(defn path->name [k]
|
||||
(cond (keyword? k)
|
||||
(str (namespace k) "/" (name k))
|
||||
|
||||
(seq k)
|
||||
(str/join "_" (map path->name k))
|
||||
:else k))
|
||||
(defn row* [{:keys [client vendor amount flash?]}]
|
||||
(com/data-grid-row
|
||||
{:class (when flash?
|
||||
"live-added")}
|
||||
(com/data-grid-cell {} (:client/code client))
|
||||
(com/data-grid-cell
|
||||
{}
|
||||
[:div.flex.whitespace-nowrap.items-center.gap-4
|
||||
[:div [:div (:vendor/name vendor)]
|
||||
[:div.text-sm.text-gray-400
|
||||
(or (-> vendor :vendor/legal-entity-name not-empty)
|
||||
(str (-> vendor :vendor/legal-entity-first-name) " "
|
||||
(-> vendor :vendor/legal-entity-middle-name) " "
|
||||
(-> vendor :vendor/legal-entity-last-name)))]]
|
||||
(when-let [t99-type (some-> vendor :vendor/legal-entity-1099-type :db/ident name)]
|
||||
(com/pill
|
||||
{:class "text-xs font-medium"
|
||||
:color :primary}
|
||||
(str/capitalize t99-type))
|
||||
)])
|
||||
(com/data-grid-cell
|
||||
{:class "hidden md:table-cell"}
|
||||
[:div.flex.gap-4
|
||||
(when-let [tin (-> vendor :vendor/legal-entity-tin)]
|
||||
[:span {:class "text-xs font-medium py-0.5 "}
|
||||
tin])
|
||||
(when-let [tin-type (some-> vendor :vendor/legal-entity-tin-type :db/ident name)]
|
||||
(com/pill {:class "text-xs font-medium"
|
||||
:color :yellow}
|
||||
(name tin-type)))])
|
||||
(com/data-grid-cell
|
||||
{:class "hidden lg:table-cell"}
|
||||
(if (-> vendor :vendor/address :address/street1)
|
||||
[:div
|
||||
[:div (-> vendor :vendor/address :address/street1)] " "
|
||||
[:div
|
||||
(-> vendor :vendor/address :address/street2)] " "
|
||||
[:div
|
||||
(-> vendor :vendor/address :address/city) " "
|
||||
(-> vendor :vendor/address :address/state) ","
|
||||
(-> vendor :vendor/address :address/zip)]]
|
||||
[:p.text-sm.italic.text-gray-400 "No address"]))
|
||||
(com/data-grid-cell {}
|
||||
(com/pill {:class "text-xs font-medium"
|
||||
:color :primary}
|
||||
"Paid $" (Math/round amount)))
|
||||
(com/data-grid-right-stack-cell
|
||||
{}
|
||||
(if (cannot-overwrite? vendor)
|
||||
[:div (com/link {:href "mailto:ben@integreatconsult.com"} "Contact Integreat")]
|
||||
(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099-vendor-dialog
|
||||
:vendor-id (:db/id vendor))
|
||||
:hx-target "#modal-holder"
|
||||
:hx-swap "outerHTML"}
|
||||
svg/pencil)))))
|
||||
(defn table* [{:keys [identity session query-params hx-query-params]} & {:keys [flash-id]}]
|
||||
(let [start (or (some-> (or (get query-params "start") (get hx-query-params "start")) not-empty (Long/parseLong ))
|
||||
0)
|
||||
per-page (or (some-> (or (get query-params "per-page") (get hx-query-params "per-page")) not-empty (Long/parseLong ))
|
||||
30)
|
||||
companies (get-1099-companies identity session)
|
||||
total (count companies)
|
||||
companies (subvec companies (Math/min start total) (Math/min (+ start per-page) total))]
|
||||
(com/data-grid-card {:id "vendor-table"
|
||||
:title "1099 Vendor Info"
|
||||
:entity-name "vendors"
|
||||
:route :company-1099-vendor-table
|
||||
:start start
|
||||
:per-page per-page
|
||||
:total total
|
||||
:action-buttons [(com/button {:color :primary}
|
||||
(com/button-icon {} svg/refresh)
|
||||
"Add new product")
|
||||
(com/button {:color :secondary}
|
||||
(com/button-icon {} svg/refresh)
|
||||
"Update stocks 1/250")
|
||||
(com/icon-button {}
|
||||
svg/upload)]
|
||||
:rows (for [[client vendor amount] companies]
|
||||
(row* {:client client
|
||||
:vendor vendor
|
||||
:amount amount
|
||||
:flash? (= flash-id
|
||||
(:db/id vendor))}))
|
||||
:headers [(com/data-grid-header {} "Client")
|
||||
(com/data-grid-header {} "Vendor Name")
|
||||
(com/data-grid-header {:class "hidden md:table-cell"} "TIN")
|
||||
(com/data-grid-header {:class "hidden lg:table-cell"} "Address")
|
||||
(com/data-grid-header {})
|
||||
(com/data-grid-header {})]})))
|
||||
|
||||
(defn vendor-save [{:keys [form-params identity route-params] :as request}]
|
||||
(when-not (cannot-overwrite? (dc/pull (dc/db conn) '[*] (Long/parseLong (:vendor-id route-params))))
|
||||
@@ -246,114 +224,114 @@
|
||||
(update :vendor/legal-entity-tin-type #(some->> % not-empty (keyword "legal-entity-tin-type")))))]))
|
||||
(html-response
|
||||
(table* request :flash-id (Long/parseLong (:vendor-id route-params)))
|
||||
:headers {"hx-trigger" "closeDialog"}))
|
||||
|
||||
|
||||
:headers {"hx-trigger" "closeModal"}))
|
||||
|
||||
(defn vendor-dialog [request]
|
||||
(let [vendor (dc/pull (dc/db conn) '[* {:vendor/legal-entity-1099-type [:db/ident]
|
||||
:vendor/legal-entity-tin-type [:db/ident]}] (Long/parseLong (:vendor-id (:params request))))] ;; TODO perms
|
||||
(html-response
|
||||
[:form {:hx-post (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099-vendor-save
|
||||
:request-method :post
|
||||
:vendor-id (Long/parseLong (:vendor-id (:params request))))
|
||||
:hx-target "#vendor-table"
|
||||
:hx-swap "outerHTML swap:300ms"}
|
||||
(com/dialog
|
||||
[:div.flex [:div.p-2 "Vendor 1099 Info"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:vendor/name vendor)]]
|
||||
[:div.space-y-6
|
||||
[:div.grid.grid-cols-6.gap-4
|
||||
[:h4.text-xl.border-b.col-span-6 "Address"]
|
||||
[:div.col-span-6
|
||||
(com/field {:label "Street 1"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/street1])
|
||||
:value (-> vendor :vendor/address :address/street1)
|
||||
:placeholder "1700 Pennsylvania Ave"
|
||||
:autofocus true}))]
|
||||
[:div.col-span-6
|
||||
(com/field {:label "Street 2"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/street2])
|
||||
:value (-> vendor :vendor/address :address/street2)
|
||||
:placeholder "Suite 200"}))]
|
||||
[:div.col-span-3
|
||||
(com/field {:label "City"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/city])
|
||||
:value (-> vendor :vendor/address :address/city)
|
||||
:placeholder "Cupertino"}))]
|
||||
[:div.col-span-1
|
||||
(com/field {:label "State"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/state])
|
||||
:value (-> vendor :vendor/address :address/state)
|
||||
:placeholder "CA"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "Zip"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/zip])
|
||||
:value (-> vendor :vendor/address :address/zip)
|
||||
:placeholder "98102"}))]
|
||||
[:h4.text-xl.border-b.col-span-6 "Legal Entity"]
|
||||
[:div.col-span-6
|
||||
(com/field {:label "Legal Entity Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-name])
|
||||
:value (-> vendor :vendor/legal-entity-name)
|
||||
:placeholder "Good Restaurant LLC"}))]
|
||||
[:div.col-span-6.text-center " - OR -"]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "First Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-first-name])
|
||||
:value (-> vendor :vendor/legal-entity-first-name)
|
||||
:placeholder "John"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "Middle Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-middle-name])
|
||||
:value (-> vendor :vendor/legal-entity-middle-name)
|
||||
:placeholder "C."}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "Last Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-last-name])
|
||||
:value (-> vendor :vendor/legal-entity-last-name)
|
||||
:placeholder "Riley"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "TIN"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-tin])
|
||||
:value (-> vendor :vendor/legal-entity-tin)
|
||||
:placeholder "John"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "TIN Type"}
|
||||
(com/select {:name (path->name [:vendor/legal-entity-tin-type])
|
||||
:allow-blank? true
|
||||
:value (some-> vendor :vendor/legal-entity-tin-type :db/ident name)
|
||||
:options [["ein" "EIN"]
|
||||
["ssn" "SSN"]]}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "1099 Type"}
|
||||
(com/select {:name (path->name [:vendor/legal-entity-1099-type])
|
||||
:allow-blank? true
|
||||
:value (some-> vendor :vendor/legal-entity-1099-type :db/ident name)
|
||||
:options [["none" "None"]
|
||||
["misc" "Misc"]
|
||||
["landlord" "Landlord"]]}))]
|
||||
[:div.col-span-6
|
||||
(com/button {:color :primary}
|
||||
"Save")]]]
|
||||
[:div])]
|
||||
:headers {"hx-trigger" "openDialog"})))
|
||||
(com/modal
|
||||
{}
|
||||
[:form {:hx-post (str (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099-vendor-save
|
||||
:request-method :post
|
||||
:vendor-id (Long/parseLong (:vendor-id (:params request)))))
|
||||
:hx-target "#vendor-table"
|
||||
:hx-swap "outerHTML swap:300ms"}
|
||||
[:fieldset {:class "hx-disable"}
|
||||
(com/modal-card
|
||||
{}
|
||||
[:div.flex [:div.p-2 "Vendor 1099 Info"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600 (:vendor/name vendor)]]
|
||||
[:div.space-y-6
|
||||
[:div.grid.grid-cols-6.gap-4
|
||||
[:h4.text-xl.border-b.col-span-6 "Address"]
|
||||
[:div.col-span-6
|
||||
(com/field {:label "Street 1"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/street1])
|
||||
:value (-> vendor :vendor/address :address/street1)
|
||||
:placeholder "1700 Pennsylvania Ave"
|
||||
:autofocus true}))]
|
||||
[:div.col-span-6
|
||||
(com/field {:label "Street 2"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/street2])
|
||||
:value (-> vendor :vendor/address :address/street2)
|
||||
:placeholder "Suite 200"}))]
|
||||
[:div.col-span-3
|
||||
(com/field {:label "City"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/city])
|
||||
:value (-> vendor :vendor/address :address/city)
|
||||
:placeholder "Cupertino"}))]
|
||||
[:div.col-span-1
|
||||
(com/field {:label "State"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/state])
|
||||
:value (-> vendor :vendor/address :address/state)
|
||||
:placeholder "CA"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "Zip"}
|
||||
(com/text-input {:name (path->name [:vendor/address :address/zip])
|
||||
:value (-> vendor :vendor/address :address/zip)
|
||||
:placeholder "98102"}))]
|
||||
[:h4.text-xl.border-b.col-span-6 "Legal Entity"]
|
||||
[:div.col-span-6
|
||||
(com/field {:label "Legal Entity Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-name])
|
||||
:value (-> vendor :vendor/legal-entity-name)
|
||||
:placeholder "Good Restaurant LLC"}))]
|
||||
[:div.col-span-6.text-center " - OR -"]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "First Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-first-name])
|
||||
:value (-> vendor :vendor/legal-entity-first-name)
|
||||
:placeholder "John"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "Middle Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-middle-name])
|
||||
:value (-> vendor :vendor/legal-entity-middle-name)
|
||||
:placeholder "C."}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "Last Name"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-last-name])
|
||||
:value (-> vendor :vendor/legal-entity-last-name)
|
||||
:placeholder "Riley"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "TIN"}
|
||||
(com/text-input {:name (path->name [:vendor/legal-entity-tin])
|
||||
:value (-> vendor :vendor/legal-entity-tin)
|
||||
:placeholder "John"}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "TIN Type"}
|
||||
(com/select {:name (path->name [:vendor/legal-entity-tin-type])
|
||||
:allow-blank? true
|
||||
:value (some-> vendor :vendor/legal-entity-tin-type :db/ident name)
|
||||
:options [["ein" "EIN"]
|
||||
["ssn" "SSN"]]}))]
|
||||
[:div.col-span-2
|
||||
(com/field {:label "1099 Type"}
|
||||
(com/select {:name (path->name [:vendor/legal-entity-1099-type])
|
||||
:allow-blank? true
|
||||
:value (some-> vendor :vendor/legal-entity-1099-type :db/ident name)
|
||||
:options [["none" "None"]
|
||||
["misc" "Misc"]
|
||||
["landlord" "Landlord"]]}))]
|
||||
[:div.col-span-6
|
||||
(com/button {:color :primary}
|
||||
"Save")]]]
|
||||
[:div])]]))))
|
||||
|
||||
(defn vendor-table [request]
|
||||
(html-response (com/page
|
||||
{:nav
|
||||
(com/company-aside-nav)}
|
||||
(com/breadcrumbs {}
|
||||
[:a {:href "#"} "My Company"]
|
||||
[:a {:href "#"} "1099 Vendor Info"])
|
||||
(table* request))))
|
||||
(html-response (table* request)
|
||||
:headers {"hx-push-url" (str "?start=" (get (:query-params request) "start"))}))
|
||||
|
||||
(defn page [{:keys [identity matched-route] :as request}]
|
||||
(base-page
|
||||
request
|
||||
(com/page {:nav (com/company-aside-nav)}
|
||||
(com/page {:nav (com/company-aside-nav)
|
||||
:active-client (:client (:session request))
|
||||
:identity (:identity request)}
|
||||
(com/breadcrumbs {}
|
||||
[:a {:href "#"} "My Company"]
|
||||
[:a {:href "#"} "1099 Vendor Info"])
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)} "My Company"]
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099)} "1099 Vendor Info"])
|
||||
(table* request))
|
||||
nil))
|
||||
|
||||
83
src/clj/auto_ap/ssr/company/reports.clj
Normal file
83
src/clj/auto_ap/ssr/company/reports.clj
Normal file
@@ -0,0 +1,83 @@
|
||||
(ns auto-ap.ssr.company.reports
|
||||
(:require
|
||||
[amazonica.aws.s3 :as s3]
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.datomic.reports :as r]
|
||||
[auto-ap.graphql.utils :refer [assert-can-see-client is-admin?]]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.grid-page-helper :as helper]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.time :as atime]
|
||||
[bidi.bidi :as bidi]
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]))
|
||||
|
||||
(def grid-page {:id "report-table"
|
||||
:nav (com/company-aside-nav)
|
||||
:id-fn :db/id
|
||||
:fetch-page (fn [user args]
|
||||
(r/get-graphql (into args {:id user})))
|
||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"My Company"]
|
||||
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-reports)}
|
||||
"Reports"]]
|
||||
:title "Reports"
|
||||
:entity-name "Reports"
|
||||
:route :company-reports-table
|
||||
:action-buttons (fn [user]
|
||||
nil)
|
||||
:row-buttons (fn [user e]
|
||||
(com/a-icon-button {:href (:report/url e)}
|
||||
svg/download)[
|
||||
(when (is-admin? user)
|
||||
(com/icon-button {:hx-delete (str (bidi/path-for ssr-routes/only-routes
|
||||
:company-reports-delete
|
||||
:request-method :delete))
|
||||
:hx-target "closest tr"}
|
||||
svg/trash))])
|
||||
:headers [{:key "name"
|
||||
:name "Name"
|
||||
:sort-key "name"
|
||||
:render :report/name}
|
||||
{:key "created-by"
|
||||
:name "Created by"
|
||||
:sort-key "creator"
|
||||
:render (fn [report]
|
||||
(when (:report/creator report)
|
||||
(com/pill {:color :primary }
|
||||
(:report/creator report))))}
|
||||
{:key "created"
|
||||
:name "Created"
|
||||
:sort-key "created"
|
||||
:render #(atime/unparse-local (:report/created %)
|
||||
atime/normal-date)}]})
|
||||
|
||||
(def row* (partial helper/row* grid-page))
|
||||
(def table* (partial helper/table* grid-page))
|
||||
(def table (partial helper/table grid-page))
|
||||
(def page (partial helper/page grid-page))
|
||||
|
||||
(defn delete-report [{:keys [form-params identity]}]
|
||||
|
||||
(let [[id-to-delete key] (first (dc/q '[:find ?i ?k
|
||||
:in $ ?i
|
||||
:where [?i :report/key ?k]]
|
||||
(dc/db conn)
|
||||
(some-> (get form-params "id") not-empty Long/parseLong)))
|
||||
report (dc/pull (dc/db conn) r/default-read id-to-delete)]
|
||||
(assert-can-see-client identity (:report/client report))
|
||||
(when id-to-delete
|
||||
(s3/delete-object :bucket-name (:data-bucket env)
|
||||
:key key)
|
||||
@(dc/transact conn [[:db/retractEntity id-to-delete]]))
|
||||
(html-response
|
||||
(row* identity
|
||||
report
|
||||
{:flash? true
|
||||
:delete-after-settle? true}))))
|
||||
|
||||
123
src/clj/auto_ap/ssr/company/yodlee.clj
Normal file
123
src/clj/auto_ap/ssr/company/yodlee.clj
Normal file
@@ -0,0 +1,123 @@
|
||||
(ns auto-ap.ssr.company.yodlee
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.datomic.yodlee2 :as yodlee2]
|
||||
[auto-ap.graphql.utils :refer [is-admin?]]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.grid-page-helper :as helper]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[auto-ap.time :as atime]
|
||||
[auto-ap.yodlee.core2 :as yodlee]
|
||||
[bidi.bidi :as bidi]
|
||||
[config.core :refer [env]]
|
||||
[datomic.api :as dc]
|
||||
[hiccup2.core :as hiccup]))
|
||||
|
||||
(def default-read '[:db/id
|
||||
:yodlee-provider-account/last-updated
|
||||
:yodlee-provider-account/status
|
||||
:yodlee-provider-account/id
|
||||
:yodlee-provider-account/detailed-status
|
||||
{:yodlee-provider-account/accounts [:yodlee-account/name :yodlee-account/number]
|
||||
:yodlee-provider-account/client [:client/code]}])
|
||||
|
||||
|
||||
|
||||
(defn fastlink-dialog [{:keys [session]}]
|
||||
(html-response
|
||||
(com/modal
|
||||
{}
|
||||
(com/modal-card
|
||||
{}
|
||||
[:div.flex [:div.p-2 "Yodlee Fastlink"] ]
|
||||
[:div
|
||||
[:div#fa-spot]
|
||||
[:script {:lang "text/javascript"}
|
||||
(hiccup/raw
|
||||
(format "
|
||||
fastlink.open({fastLinkURL: '%s',
|
||||
accessToken: '%s',
|
||||
params: {'configName': 'Aggregation'}},
|
||||
'fa-spot');
|
||||
|
||||
" (:yodlee2-fastlink env) (yodlee/get-access-token (:client/code (:client session)))))]
|
||||
]
|
||||
[:div]))))
|
||||
|
||||
(def grid-page {:id "yodlee-table"
|
||||
:nav (com/company-aside-nav)
|
||||
:id-fn :db/id
|
||||
:fetch-page (fn [user args]
|
||||
(yodlee2/get-graphql (assoc args :id user)))
|
||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"My Company"]
|
||||
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-yodlee)}
|
||||
"Yodlee"]]
|
||||
:title "Yodlee Accounts"
|
||||
:entity-name "Yodlee accounts"
|
||||
:route :company-yodlee-table
|
||||
:action-buttons (fn [user]
|
||||
[(com/button {:color :primary
|
||||
:on-click "openFastlink()"
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company-yodlee-fastlink-dialog)
|
||||
:hx-target "#modal-holder"}
|
||||
(com/button-icon {} svg/refresh)
|
||||
"Link new account")])
|
||||
:row-buttons (fn [user e]
|
||||
[(when (is-admin? user)
|
||||
(com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:company-yodlee-provider-account-refresh)
|
||||
:hx-target "closest tr"}
|
||||
svg/refresh))])
|
||||
:headers [{:key "provider-account"
|
||||
:name "Provider Account"
|
||||
:sort-key "provider-account"
|
||||
:render :yodlee-provider-account/id}
|
||||
{:key "status"
|
||||
:name "Status"
|
||||
:sort-key "status"
|
||||
:render #(when-let [status (:yodlee-provider-account/status %)]
|
||||
(com/pill {:color (if (not= status "SUCCESS")
|
||||
:yellow
|
||||
:primary) }
|
||||
status))}
|
||||
{:key "detailed-status"
|
||||
:name "Detailed Status"
|
||||
:sort-key "detailed-status"
|
||||
:render #(when-let [status (:yodlee-provider-account/detailed-status %)]
|
||||
status)}
|
||||
|
||||
{:key "last-updated"
|
||||
:name "Last Updated"
|
||||
:sort-key "last-updated"
|
||||
:render #(atime/unparse-local (:yodlee-provider-account/last-updated %)
|
||||
atime/normal-date)}
|
||||
{:key "accounts"
|
||||
:name "Accounts"
|
||||
:show-starting "md"
|
||||
:render (fn [e]
|
||||
[:ul
|
||||
(for [a (:yodlee-provider-account/accounts e)]
|
||||
[:li (:yodlee-account/name a) " - " (:yodlee-account/number a)])])}]})
|
||||
|
||||
(def page (partial helper/page grid-page))
|
||||
(def table (partial helper/table grid-page))
|
||||
|
||||
;; TODO delete-after-settle
|
||||
(defn refresh-provider-account [{:keys [form-params identity]}]
|
||||
(let [provider-account (dc/pull (dc/db conn) default-read (some-> (get form-params "id") not-empty Long/parseLong))]
|
||||
(yodlee/refresh-provider-account (:client/code (:yodlee-provider-account/client provider-account))
|
||||
(:yodlee-provider-account/id provider-account))
|
||||
(html-response
|
||||
(helper/row*
|
||||
grid-page
|
||||
identity
|
||||
provider-account
|
||||
{:flash? true}))))
|
||||
@@ -2,103 +2,120 @@
|
||||
(:require
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.graphql.utils :refer [assert-can-see-client]]
|
||||
[iol-ion.query :refer [can-see-client?]]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components.navbar-dropdown :refer [navbar-dropdown]]
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[bidi.bidi :as bidi]
|
||||
[datomic.api :as dc]
|
||||
[hiccup2.core :as hiccup]))
|
||||
[hiccup2.core :as hiccup]
|
||||
[iol-ion.query :refer [can-see-client?]]))
|
||||
|
||||
(defn dropdown-contents [{:keys [identity]}]
|
||||
(let [options (->> (dc/q '[:find ?c ?n
|
||||
:in $ ?user
|
||||
:where [?c :client/name ?n]
|
||||
[(iol-ion.query/can-see-client? ?user ?c)]]
|
||||
(dc/db conn)
|
||||
identity)
|
||||
(map (fn [[k v]]
|
||||
{"key" k
|
||||
"value" v})))]
|
||||
(html-response
|
||||
[:div.navbar-dropdown {:style {:width "20em"}}
|
||||
[:a.navbar-item {:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:active-client
|
||||
:request-method :put)
|
||||
:hx-target "#company-dropdown"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-trigger "click"}
|
||||
"All"]
|
||||
[:hr.navbar-divider]
|
||||
[:input#company-search.input.navbar-item {:placeholder "Company name"
|
||||
:name "search-text"
|
||||
:autoFocus true} ]
|
||||
[:input#company-search-value {:type "hidden"
|
||||
:autocomplete "off"
|
||||
:name "search-client"
|
||||
:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:active-client
|
||||
:request-method :put)
|
||||
:hx-target "#company-dropdown"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-trigger "change"} ]
|
||||
[:script
|
||||
(hiccup/raw
|
||||
(str "
|
||||
var z = new autoComplete({
|
||||
selector:\"#company-search\",
|
||||
placeholder: \"Company Name....\",
|
||||
data: {
|
||||
keys: [\"value\"],
|
||||
src: " (cheshire.core/encode
|
||||
options)
|
||||
(defn dropdown-search-results* [{:keys [options]}]
|
||||
[:ul
|
||||
(for [[id company-name]options]
|
||||
[:li
|
||||
[:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
|
||||
|
||||
"
|
||||
},
|
||||
resultItem: {
|
||||
highlight:true,
|
||||
class: \"autocomplete-suggestion\",
|
||||
selected: \"highlighted\"
|
||||
[:a {:href "#" :class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
|
||||
"_" (hiccup/raw "on click set value of <#company-search-value/> to @data-value then send selected to #company-dropdown")
|
||||
:data-value id}
|
||||
company-name]]])])
|
||||
|
||||
},
|
||||
resultsList: {
|
||||
tabSelect: true
|
||||
},
|
||||
submit: true
|
||||
(defn get-clients [identity query]
|
||||
(dc/q '[:find ?c ?n
|
||||
:in $ ?user ?q
|
||||
:where [?c :client/name ?n]
|
||||
[(clojure.string/includes? ?n ?q)]
|
||||
[(iol-ion.query/can-see-client? ?user ?c)]]
|
||||
(dc/db conn)
|
||||
identity
|
||||
(or query "")))
|
||||
|
||||
});
|
||||
z.input.addEventListener(\"selection\", function (event) {
|
||||
z.input.blur();
|
||||
z.input.value = event.detail.selection.value.value;
|
||||
document.getElementById(\"company-search-value\").value= event.detail.selection.value.key;
|
||||
document.getElementById(\"company-search-value\").dispatchEvent(new Event('change'));
|
||||
(defn dropdown-search-results [{:keys [identity] :as request}]
|
||||
(html-response
|
||||
(dropdown-search-results* {:options (get-clients identity (get (:query-params request) "search-text"))
|
||||
:client (:client (:session request))})))
|
||||
|
||||
});
|
||||
"))]])))
|
||||
(defn dropdown [{:keys [client]}]
|
||||
[:div#company-dropdown
|
||||
{:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:active-client
|
||||
:request-method :put)
|
||||
:hx-target "#company-dropdown"
|
||||
:hx-include "#company-search-value"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-trigger "selected"}
|
||||
[:script
|
||||
(hiccup/raw
|
||||
"localStorage.setItem(\"last-client-id\", \""(:db/id client)"\")")]
|
||||
[:div
|
||||
[:button#company-dropdown-button { :class "text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2.5 text-center inline-flex items-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
:type "button"}
|
||||
(if client
|
||||
(:client/name client)
|
||||
"All Companies")
|
||||
[:div.w-4.h-4.ml-2
|
||||
svg/drop-down]]
|
||||
[:div#company-dropdown-list.hidden {"_" (hiccup/raw "init call initCompanyDropdown()")
|
||||
}
|
||||
[:div {:class "z-10 bg-white rounded-lg shadow w-64 dark:bg-gray-700 slide-up duration-500 transition-all"}
|
||||
[:div {:class "p-3"}
|
||||
[:label {:for "input-group-search", :class "sr-only"} "Search"]
|
||||
[:div {:class "relative"}
|
||||
[:div {:class "absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"}
|
||||
[:div.w-5.h-5.text-gray-500.dark:text-gray-400
|
||||
svg/search]]
|
||||
[:input#company-search {:placeholder "Company name"
|
||||
:name "search-text"
|
||||
:class "block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
:autoFocus true
|
||||
:tab-index -1
|
||||
:hx-trigger "keyup changed delay:500ms, search"
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:company-dropdown-search-results)
|
||||
:hx-target "#company-search-results"
|
||||
:hx-swap "innerHTML"} ]]
|
||||
[:input#company-search-value {:type "hidden"
|
||||
:name "search-client"}]]
|
||||
[:div.divide-y.divide-gray-100
|
||||
[:div#company-search-results {:class "h-48 px-3 pb-3 overflow-y-auto text-sm text-gray-700 dark:text-gray-200"}]
|
||||
[:div {:class "flex items-center pl-2 rounded hover:bg-green-100 dark:hover:bg-green-600"}
|
||||
|
||||
(defn dropdown [request]
|
||||
[:button {:class "w-full py-2 ml-2 text-sm font-medium text-gray-900 rounded dark:text-gray-300"
|
||||
:hx-put (bidi/path-for ssr-routes/only-routes
|
||||
:active-client
|
||||
:request-method :put)
|
||||
:hx-target "#company-dropdown"
|
||||
:hx-swap "outerHTML"
|
||||
:hx-trigger "click"}
|
||||
"All"]]]
|
||||
]]
|
||||
[:script {:lang "text/javascript"}
|
||||
(hiccup/raw
|
||||
"
|
||||
function initCompanyDropdown() {
|
||||
var $dropdownTargetEl = document.getElementById('company-dropdown-list');
|
||||
|
||||
(let [client (get-in request [:session :client])]
|
||||
// set the element that trigger the dropdown menu on click
|
||||
var $dropdownTriggerEl = document.getElementById('company-dropdown-button');
|
||||
|
||||
(navbar-dropdown
|
||||
"company-dropdown"
|
||||
[:span
|
||||
(if client
|
||||
(str "Company: " (:client/name client))
|
||||
"Company")
|
||||
[:script
|
||||
(hiccup/raw
|
||||
"localStorage.setItem(\"last-client-id\", \""(:db/id client)"\")")]]
|
||||
[:div {:hx-get
|
||||
(bidi/path-for ssr-routes/only-routes
|
||||
:company-dropdown-contents)
|
||||
:hx-swap "outerHTML"
|
||||
:hx-trigger "intersect delay:150ms"
|
||||
:hx-target "closest .navbar-dropdown"
|
||||
:style {:width "20em"
|
||||
:height "80px"}
|
||||
}
|
||||
[:div.loader.is-loading.is-active.is-centered]])))
|
||||
var dropdownOptions = {
|
||||
placement: 'bottom',
|
||||
triggerType: 'click',
|
||||
offsetSkidding: 0,
|
||||
offsetDistance: 10,
|
||||
delay: 300,
|
||||
onHide: () => {
|
||||
},
|
||||
onShow: () => {
|
||||
document.getElementById('company-search').focus()
|
||||
},
|
||||
onToggle: () => {
|
||||
}
|
||||
};
|
||||
var companyDrowdown = new Dropdown($dropdownTargetEl, $dropdownTriggerEl, dropdownOptions);
|
||||
}
|
||||
")]]])
|
||||
|
||||
(defn active-client [{:keys [identity params] :as request}]
|
||||
(let [client-id (some-> (or (:search-client params) (get params "search-client")) not-empty Long/parseLong)]
|
||||
@@ -109,7 +126,8 @@ document.getElementById(\"company-search-value\").dispatchEvent(new Event('chang
|
||||
(dc/pull (dc/db conn) [:db/id :client/name :client/code] client-id)))]
|
||||
(assoc
|
||||
(html-response
|
||||
(dropdown (assoc request :session new-session)))
|
||||
(dropdown {:client (:client new-session)
|
||||
:identity identity}))
|
||||
:session
|
||||
new-session
|
||||
:headers
|
||||
|
||||
@@ -8,14 +8,17 @@
|
||||
[auto-ap.ssr.components.navbar :as navbar]
|
||||
[auto-ap.ssr.components.page :as page]
|
||||
[auto-ap.ssr.components.data-grid :as data-grid]
|
||||
[auto-ap.ssr.components.tags :as tags]))
|
||||
[auto-ap.ssr.components.tags :as tags]
|
||||
[auto-ap.ssr.components.paginator :as paginator]))
|
||||
|
||||
|
||||
(def breadcrumbs breadcrumbs/breadcrumbs-)
|
||||
(def button buttons/button-)
|
||||
(def button-icon buttons/button-icon-)
|
||||
(def icon-button buttons/icon-button-)
|
||||
(def dialog dialog/dialog-)
|
||||
(def a-icon-button buttons/a-icon-button-)
|
||||
(def modal dialog/modal-)
|
||||
(def modal-card dialog/modal-card-)
|
||||
|
||||
(def text-input inputs/text-input-)
|
||||
(def select inputs/select-)
|
||||
@@ -34,6 +37,7 @@
|
||||
|
||||
(def data-grid data-grid/data-grid-)
|
||||
(def data-grid-header data-grid/header-)
|
||||
(def data-grid-sort-header data-grid/sort-header-)
|
||||
(def data-grid-row data-grid/row-)
|
||||
(def data-grid-cell data-grid/cell-)
|
||||
(def data-grid-right-stack-cell data-grid/right-stack-cell-)
|
||||
@@ -43,29 +47,9 @@
|
||||
:class (str "font-medium text-blue-600 dark:text-blue-500 hover:underline " class)}]
|
||||
children))
|
||||
|
||||
(defn paginator []
|
||||
[:nav {:class "flex flex-col items-start justify-between p-4 space-y-3 md:flex-row md:items-center md:space-y-0", :aria-label "Table navigation"}
|
||||
[:span {:class "text-sm font-normal text-gray-500 dark:text-gray-400"}
|
||||
[:span {:class "font-semibold text-gray-900 dark:text-white"} "1-10"]
|
||||
[:span {:class "font-semibold text-gray-900 dark:text-white"} "1000"]]
|
||||
[:ul {:class "inline-flex items-stretch -space-x-px"}
|
||||
[:li
|
||||
[:a {:href "#", :class "flex items-center justify-center h-full py-1.5 px-3 ml-0 text-gray-500 bg-white rounded-l-lg border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"}
|
||||
[:span {:class "sr-only"} "Previous"]
|
||||
[:svg {:class "w-5 h-5", :aria-hidden "true", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:fill-rule "evenodd", :d "M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z", :clip-rule "evenodd"}]]]]
|
||||
[:li
|
||||
[:a {:href "#", :class "flex items-center justify-center px-3 py-2 text-sm leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"} "1"]]
|
||||
[:li
|
||||
[:a {:href "#", :class "flex items-center justify-center px-3 py-2 text-sm leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"} "2"]]
|
||||
[:li
|
||||
[:a {:href "#", :aria-current "page", :class "z-10 flex items-center justify-center px-3 py-2 text-sm leading-tight border text-primary-600 bg-primary-50 border-primary-300 hover:bg-primary-100 hover:text-primary-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white"} "3"]]
|
||||
[:li
|
||||
[:a {:href "#", :class "flex items-center justify-center px-3 py-2 text-sm leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"} "..."]]
|
||||
[:li
|
||||
[:a {:href "#", :class "flex items-center justify-center px-3 py-2 text-sm leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"} "100"]]
|
||||
[:li
|
||||
[:a {:href "#", :class "flex items-center justify-center h-full py-1.5 px-3 leading-tight text-gray-500 bg-white rounded-r-lg border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"}
|
||||
[:span {:class "sr-only"} "Next"]
|
||||
[:svg {:class "w-5 h-5", :aria-hidden "true", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:fill-rule "evenodd", :d "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", :clip-rule "evenodd"}]]]]]])
|
||||
|
||||
|
||||
|
||||
(def paginator paginator/paginator-)
|
||||
(def data-grid-card data-grid/data-grid-card-)
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
(ns auto-ap.ssr.components.aside
|
||||
(:require [auto-ap.ssr.svg :as svg]
|
||||
[hiccup2.core :as hiccup]))
|
||||
[hiccup2.core :as hiccup]
|
||||
[bidi.bidi :as bidi]
|
||||
[auto-ap.ssr-routes :as ssr-routes]))
|
||||
|
||||
(defn menu-button- [params & children]
|
||||
[:a (-> params
|
||||
@@ -186,17 +188,25 @@
|
||||
|
||||
(defn company-aside-nav- []
|
||||
[:ul {:class "space-y-2"}
|
||||
[:li
|
||||
(menu-button- {:icon svg/vendors
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
:company)}
|
||||
"My Company")]
|
||||
|
||||
[:li
|
||||
(menu-button- {:icon svg/report}
|
||||
[:li
|
||||
(menu-button- {:icon svg/report
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-reports)}
|
||||
"Reports")]
|
||||
[:li
|
||||
(menu-button- {:icon svg/bank}
|
||||
[:li
|
||||
(menu-button- {:icon svg/bank
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-yodlee)}
|
||||
"Yodlee Link")]
|
||||
[:li
|
||||
(menu-button- {:icon svg/vendors}
|
||||
"Vendors")]
|
||||
|
||||
[:li
|
||||
(menu-button- {:icon svg/government-building}
|
||||
"1099 Vendor Info")]])
|
||||
[:li
|
||||
(menu-button- {:icon svg/government-building
|
||||
:href (bidi/path-for ssr-routes/only-routes
|
||||
:company-1099)}
|
||||
"1099 Vendor Info"
|
||||
)]])
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
[:div.h-4.w-4 i])
|
||||
|
||||
(defn button- [params & children]
|
||||
[:button { :class (cond-> "text-white focus:ring-4 font-bold rounded-lg text-sm px-5 py-2.5 text-center mr-2 inline-flex items-center hover:scale-105 transition duration-100 justify-center"
|
||||
(= :secondary (:color params)) (str " bg-blue-500 hover:bg-blue-600 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700")
|
||||
(= :primary (:color params)) (str " bg-green-500 hover:bg-green-600 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 "))}
|
||||
[:button (update params
|
||||
:class #(cond-> %
|
||||
true (str " text-white focus:ring-4 font-bold rounded-lg text-sm px-5 py-2.5 text-center mr-2 inline-flex items-center hover:scale-105 transition duration-100 justify-center")
|
||||
(= :secondary (:color params)) (str " bg-blue-500 hover:bg-blue-600 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700")
|
||||
(= :primary (:color params)) (str " bg-green-500 hover:bg-green-600 focus:ring-green-300 dark:bg-green-600 dark:hover:bg-green-700 ")))
|
||||
[:div.htmx-indicator.flex.items-center
|
||||
(svg/spinner {:class "inline w-4 h-4 text-white"})
|
||||
[:div.ml-3 "Loading..."]]
|
||||
@@ -16,6 +18,13 @@
|
||||
(defn icon-button- [params & children]
|
||||
(into
|
||||
[:button (update params :class str " inline-flex items-center justify-center bg-white dark:bg-gray-600 items-center p-3 text-sm font-medium border border-gray-300 dark:border-gray-700 text-center text-gray-500 hover:text-gray-800 rounded-lg dark:text-gray-400 dark:hover:text-gray-100")
|
||||
[:div.htmx-indicator.flex.items-center
|
||||
(svg/spinner {:class "inline w-4 h-4 text-white"})]
|
||||
[:div.htmx-indicator-hidden.inline-flex.gap-2.items-center.justify-center (into [:div.h-4.w-4] children)]]))
|
||||
|
||||
(defn a-icon-button- [params & children]
|
||||
(into
|
||||
[:a (update params :class str " inline-flex items-center justify-center bg-white dark:bg-gray-600 items-center p-3 text-sm font-medium border border-gray-300 dark:border-gray-700 text-center text-gray-500 hover:text-gray-800 rounded-lg dark:text-gray-400 dark:hover:text-gray-100")
|
||||
[:div.h-4.w-4 children]]))
|
||||
|
||||
(defn save-button- [params & children]
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
(ns auto-ap.ssr.components.data-grid)
|
||||
(ns auto-ap.ssr.components.data-grid
|
||||
(:require
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components.card :refer [content-card-]]
|
||||
[auto-ap.ssr.components.paginator :refer [paginator-]]
|
||||
[bidi.bidi :as bidi]
|
||||
[hiccup2.core :as hiccup]))
|
||||
|
||||
(defn header- [params & rest]
|
||||
(into [:th.px-4.py-3 {:scope "col" :class (:class params)} ] rest))
|
||||
(into [:th.px-4.py-3 {:scope "col" :class (:class params)
|
||||
"_" (hiccup/raw (when (:sort-key params ) (format "on click trigger sorted(key:\"%s\")", (:sort-key params))))}]
|
||||
(if (:sort-key params)
|
||||
[(into [:a {:href "#"} ] rest)]
|
||||
rest)))
|
||||
|
||||
(defn sort-header- [params & rest]
|
||||
[:th.px-4.py-3 {:scope "col" :class (:class params)
|
||||
"_" (hiccup/raw (format "on click trigger sorted(key:\"%s\")", (:sort-key params)))}
|
||||
(into [:a {:href "#"} ] rest)])
|
||||
|
||||
(defn row- [params & rest]
|
||||
(into [:tr {:class (cond-> "border-b dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
|
||||
(:class params) (str " " (:class params)))}] rest))
|
||||
(into [:tr (update params
|
||||
:class str " border-b dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700")] rest))
|
||||
|
||||
(defn cell- [params & rest]
|
||||
(into [:td.px-4.py-2 {:class (:class params)}] rest))
|
||||
@@ -23,12 +36,61 @@
|
||||
[:input {:id "checkbox-all", :type "checkbox", :class "w-4 h-4 bg-gray-100 border-gray-300 rounded text-primary-600 focus:ring-primary-500 dark:focus:ring-primary-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"}]
|
||||
[:label {:for "checkbox-all", :class "sr-only"} "checkbox"]]])
|
||||
|
||||
(defn data-grid- [{:keys [headers]} & rest]
|
||||
(defn data-grid- [{:keys [headers thead-params]} & rest]
|
||||
[:table {:class "w-full text-sm text-left text-gray-500 dark:text-gray-400"}
|
||||
[:thead {:class "text-xs text-gray-800 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"}
|
||||
[:thead (assoc thead-params :class "text-xs text-gray-800 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400")
|
||||
(into
|
||||
[:tr]
|
||||
headers)]
|
||||
(into
|
||||
[:tbody]
|
||||
rest)])
|
||||
|
||||
(defn data-grid-card- [{:keys [id
|
||||
route
|
||||
title
|
||||
action-buttons
|
||||
total
|
||||
subtitle
|
||||
thead-params
|
||||
start
|
||||
per-page
|
||||
flash-id
|
||||
headers
|
||||
rows] :as params}]
|
||||
[:div {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
route
|
||||
:request-method :get)
|
||||
:hx-trigger "clientSelected from:body"
|
||||
:hx-swap "outerHTML swap:300ms"
|
||||
:id id}
|
||||
(content-card-
|
||||
{}
|
||||
[:div {:class "flex flex-col px-4 py-3 space-y-3 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:space-x-4 text-gray-800 dark:text-gray-100"}
|
||||
[:div
|
||||
[:h1.text-2xl.mb-3.font-bold title]
|
||||
[:div {:class "flex items-center flex-1 space-x-4"}
|
||||
[:h5
|
||||
(when subtitle
|
||||
[:span subtitle])]]]
|
||||
(into [:div {:class "flex flex-col flex-shrink-0 space-y-3 md:flex-row md:items-center lg:justify-end md:space-y-0 md:space-x-3"}
|
||||
]
|
||||
action-buttons)]
|
||||
[:div {:class "overflow-x-auto"}
|
||||
(data-grid- {:headers headers
|
||||
:thead-params thead-params
|
||||
}
|
||||
rows
|
||||
|
||||
)]
|
||||
(paginator- {:start start
|
||||
:end (Math/min (+ start per-page) total)
|
||||
:per-page per-page
|
||||
:total total
|
||||
:a-params (fn [page]
|
||||
{:hx-get (str (bidi/path-for ssr-routes/only-routes
|
||||
route
|
||||
:request-method :get)
|
||||
"?start=" (* page per-page))
|
||||
:hx-target (str "#" id)
|
||||
:hx-swap "outerHTML show:#app:top"})}))])
|
||||
|
||||
@@ -1,9 +1,44 @@
|
||||
(ns auto-ap.ssr.components.dialog)
|
||||
(ns auto-ap.ssr.components.dialog
|
||||
(:require [hiccup2.core :as hiccup]))
|
||||
|
||||
(defn dialog- [header content footer]
|
||||
[:div#modal-content
|
||||
(defn modal- [params & children]
|
||||
[:div
|
||||
[:div#modal-holder { :tabindex "-1", :class "fixed top-0 left-0 right-0 z-50 w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full flex justify-center hidden" :aria-hidden true
|
||||
"_" (hiccup/raw "on closeModal transition <#modal-holder .modal-content /> opacity to 0.0 over 300ms then call hideModal() ")}
|
||||
[:div {:class "relative w-full max-w-2xl max-h-full"}
|
||||
(into [:div#modal-content]
|
||||
children)]
|
||||
]
|
||||
[:script {:lang "text/javascript"}
|
||||
(hiccup/raw "
|
||||
var modal_element = document.getElementById('modal-holder');
|
||||
var modal_options = {
|
||||
placement: 'center',
|
||||
backdrop: 'dynamic',
|
||||
backdropClasess: 'bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-40',
|
||||
closable: true,
|
||||
onOpen: function() {
|
||||
modal_element.dispatchEvent('openModal');
|
||||
|
||||
},
|
||||
onHide: function() {
|
||||
modal_element.outerHTML='<div id=\"modal-holder\"><div class=\"relative w-full max-w-2xl max-h-full\"><div id=\"modal-content\"></div></div></div>';
|
||||
},
|
||||
};
|
||||
var curModal = new Modal(modal_element, modal_options);
|
||||
curModal.show();
|
||||
function hideModal() {
|
||||
curModal.hide();
|
||||
}
|
||||
")
|
||||
|
||||
]])
|
||||
|
||||
(defn modal-card- [params header content footer]
|
||||
[:div#modal-card
|
||||
[:div {:class "relative bg-white rounded-lg shadow dark:bg-gray-700 dark:text-white fade-in slide-up duration-300 transition-all modal-content"}
|
||||
[:div {:class "flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600"} header]
|
||||
[:div {:class "p-6 space-y-6"}
|
||||
content]
|
||||
[:div footer]]])
|
||||
[:div footer]]
|
||||
])
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
[auto-ap.ssr.svg :as svg]
|
||||
[hiccup2.core :as hiccup]
|
||||
[bidi.bidi :as bidi]
|
||||
[auto-ap.ssr-routes :as ssr-routes]))
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.company-dropdown :as cd]))
|
||||
|
||||
(defn navbar- []
|
||||
(defn navbar- [{:keys [client identity]}]
|
||||
[:nav {:class "fixed z-30 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700"}
|
||||
[:div {:class "px-3 py-3 lg:px-5 lg:pl-3"}
|
||||
[:div {:class "flex items-center justify-between"}
|
||||
@@ -17,36 +18,31 @@
|
||||
[:a {:href "https://flowbite-admin-dashboard.vercel.app/", :class "flex ml-2 md:mr-24"}
|
||||
[:img {:src "/img/logo-big2.png", :class "h-10 mr-16", :alt "Integreat logo"}]
|
||||
]
|
||||
[:button.mt-1.lg:w-96.relative.hidden.lg:block {:class "bg-gray-50 hover:bg-gray-200 dark:hover:bg-gray-700 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 w-full pl-10 py-4 pr-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 gap-4 "
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:search)
|
||||
:hx-target "#modal-content"
|
||||
:hx-swap "innerHTML"}
|
||||
]
|
||||
|
||||
[:div {:class "flex items-center gap-4"}
|
||||
[:button.mt-1.lg:w-96.relative.hidden.lg:block {:class "bg-gray-50 hover:bg-gray-200 dark:hover:bg-gray-700 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 w-full pl-10 py-4 pr-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 gap-4 "
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:search)
|
||||
:hx-target "#modal-holder"
|
||||
:hx-swap "outerHTML"}
|
||||
[:div {:class "absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-500"}
|
||||
[:div.w-4.h-4 svg/search]
|
||||
[:span.ml-2 "Search"]]]]
|
||||
|
||||
[:div {:class "flex items-center gap-4"}
|
||||
[:span.ml-2 "Search"]]]
|
||||
[:div {:class "hidden mr-3 -mb-1 sm:block"}
|
||||
[:span]]
|
||||
(icon-button-
|
||||
{:id "toggleSidebarMobileSearch", :type "button", :class "p-2 text-gray-500 rounded-lg lg:hidden hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
|
||||
:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
:search)
|
||||
:hx-target "#modal-content"
|
||||
:hx-swap "innerHTML"}
|
||||
:hx-target "#modal-holder"
|
||||
:hx-swap "outerHTML"}
|
||||
svg/search)
|
||||
#_[:button
|
||||
[:div.w-4.h-4 svg/search]]
|
||||
(cd/dropdown {:client client :identity identity})
|
||||
|
||||
|
||||
|
||||
#_[:button {:type "button", :data-dropdown-toggle "apps-dropdown", :class "hidden p-2 text-gray-500 rounded-lg sm:flex hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-700"}
|
||||
[:span {:class "sr-only"} "View notifications"]
|
||||
[:svg {:class "w-6 h-6", :fill "currentColor", :viewbox "0 0 20 20", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:d "M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"}]]]
|
||||
#_(com/icon-button {}
|
||||
[:div.w-4.h-4 svg/search])
|
||||
[:div {:class "z-20 z-50 max-w-sm my-4 overflow-hidden text-base list-none bg-white divide-y divide-gray-100 rounded shadow-lg dark:bg-gray-700 dark:divide-gray-600 hidden", :id "apps-dropdown", :style "position: absolute; inset: 0px auto auto 0px; margin: 0px; transform: translate(1545px, 65px);", :data-popper-placement "bottom"}
|
||||
[:div {:class "block px-4 py-2 text-base font-medium text-center text-gray-700 bg-gray-50 dark:bg-gray-700 dark:text-gray-400"} ]
|
||||
[:div {:class "grid grid-cols-3 gap-4 p-4"}
|
||||
|
||||
@@ -1,24 +1,38 @@
|
||||
(ns auto-ap.ssr.components.page
|
||||
(:require [auto-ap.ssr.components.navbar :refer [navbar-]]
|
||||
[auto-ap.ssr.components.aside :refer [left-aside-]]
|
||||
[hiccup2.core :as hiccup]))
|
||||
(:require
|
||||
[auto-ap.ssr.components.aside :refer [left-aside-]]
|
||||
[auto-ap.ssr.components.navbar :refer [navbar-]]
|
||||
[hiccup2.core :as hiccup]
|
||||
[auto-ap.ssr.svg :as svg]))
|
||||
|
||||
(defn page- [{:keys [nav page-specific]} & children]
|
||||
[:div#app
|
||||
(navbar-)
|
||||
(defn page- [{:keys [nav page-specific active-client identity app-params] :or {app-params {}}} & children]
|
||||
[:div#app app-params
|
||||
(navbar- {:client active-client
|
||||
:identity identity})
|
||||
[:div.flex.pt-16.overflow-hidden
|
||||
(left-aside- {:nav nav
|
||||
:page-specific page-specific})
|
||||
|
||||
[:div#main-content {:class "relative w-full h-full lg:pl-64 overflow-y-auto px-4 bg-gray-100 dark:bg-gray-900"}
|
||||
[:div#main-content {:class "relative w-full h-full lg:pl-64 overflow-y-auto px-4 bg-gray-100 dark:bg-gray-900 min-h-content "
|
||||
"_" (hiccup/raw "on htmx:responseError put event.detail.xhr.response into #error-details then add .htmx-added to #error-holder then remove .hidden from #error-holder then wait 30ms then remove .htmx-added from #error-holder")}
|
||||
[:div#error-holder.hidden
|
||||
[:div.fixed.top-0.right-0.left-0.z-30.mx-auto.max-w-screen-lg.w-screen-lg.my-0.pt-8.rounded-lg
|
||||
[:div.relative
|
||||
[:button.absolute.right-2.top-2.w-6.h-6.z-50.text-red-600
|
||||
{"_" (hiccup/raw "on click add .hidden to #error-holder")}
|
||||
svg/filled-x
|
||||
]
|
||||
]
|
||||
[:div.m-4.overflow-auto.z-30.flex.center-items.justify-center.text-red-800.bg-red-50.dark:bg-gray-800.dark:text-red-400.border-red-300.rounded-lg.border.transition-all.duration-500.fade-in.slide-up.max-h-96
|
||||
|
||||
|
||||
[:div {:class "p-4 mb-4 text-lg w-full" :role "alert"}
|
||||
[:div.inline-block.w-8.h-8.mr-2 svg/alert]
|
||||
[:span.font-medium "Oh, drat! An unexpected error has occurred."]
|
||||
[:div.text-sm
|
||||
[:p "Integreat staff have been notified and are looking into it. " ]
|
||||
[:p "To see error details, " [:a.underline {:href "#" :data-collapse-toggle "error-details"} "click here"] "."]
|
||||
[:pre#error-details.text-xs.hidden]]]]]]
|
||||
(into
|
||||
[:div.p-4]
|
||||
children)]]
|
||||
[:div#modal-holder.hidden
|
||||
{"_" (hiccup/raw "on click trigger closeDialog")}
|
||||
[:div { :tabindex "-1", :class "fixed top-0 left-0 right-0 z-50 w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full flex justify-center "}
|
||||
[:div {:class "relative w-full max-w-2xl max-h-full"
|
||||
"_" (hiccup/raw "on click halt the event")}
|
||||
[:div#modal-content ]]]
|
||||
[:div {:class "bg-gray-900 bg-opacity-50 dark:bg-opacity-80 fixed inset-0 z-40"
|
||||
}]]])
|
||||
[:div#modal-holder]])
|
||||
|
||||
61
src/clj/auto_ap/ssr/components/paginator.clj
Normal file
61
src/clj/auto_ap/ssr/components/paginator.clj
Normal file
@@ -0,0 +1,61 @@
|
||||
(ns auto-ap.ssr.components.paginator)
|
||||
|
||||
(defn bound [x y z]
|
||||
(cond
|
||||
(< z x)
|
||||
x
|
||||
(< y x)
|
||||
x
|
||||
(> y z)
|
||||
z
|
||||
:else
|
||||
y))
|
||||
|
||||
(def elipsis-button
|
||||
[:p {:href "#", :class "flex items-center justify-center px-3 py-2 text-sm leading-tight text-gray-500 bg-white border border-gray-300 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400"} "..."])
|
||||
|
||||
(defn paginator-internal- [{:keys [start per-page end total a-params]}]
|
||||
(let [per-page (or per-page 20)
|
||||
max-buttons 5
|
||||
buttons-before (Math/floor (/ max-buttons 2))
|
||||
total-pages (long (Math/max (long 1) (long (Math/ceil (/ total per-page)))))
|
||||
current-page (long (Math/floor (/ start per-page)))
|
||||
first-page-button (bound 0 (- current-page buttons-before) (- total-pages max-buttons))
|
||||
all-buttons (into [] (for [x (range total-pages)]
|
||||
[:li
|
||||
[:a (-> (a-params x)
|
||||
(update
|
||||
:class #(cond-> %
|
||||
true (str " flex items-center justify-center px-3 py-2 text-sm leading-tight border ")
|
||||
|
||||
(= current-page x)
|
||||
(str " text-primary-600 bg-primary-50 border-primary-300 hover:bg-primary-100 hover:text-primary-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white")
|
||||
|
||||
(not= current-page x)
|
||||
(str " text-gray-500 bg-white border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white")))
|
||||
(assoc :href "#"))
|
||||
(inc x)]]))
|
||||
|
||||
|
||||
last-page-button (Math/min (long total-pages) (long (+ max-buttons first-page-button)))
|
||||
|
||||
extended-last-page-button (when (not= last-page-button total-pages)
|
||||
(list
|
||||
elipsis-button
|
||||
(last all-buttons)))
|
||||
|
||||
extended-first-page-button (when (not= first-page-button 0)
|
||||
(list
|
||||
(first all-buttons)
|
||||
elipsis-button))]
|
||||
[:nav
|
||||
[:ul {:class "inline-flex items-stretch -space-x-px"}
|
||||
extended-first-page-button
|
||||
(apply list (subvec all-buttons first-page-button last-page-button))
|
||||
extended-last-page-button]]))
|
||||
|
||||
(defn paginator- [{:keys [start per-page end total a-params] :as params}]
|
||||
[:nav {:class "flex flex-col items-start justify-between p-4 space-y-3 md:flex-row md:items-center md:space-y-0", :aria-label "Table navigation"}
|
||||
[:span {:class "text-sm font-normal text-gray-500 dark:text-gray-400"}
|
||||
[:span {:class "font-semibold text-gray-900 dark:text-white"} (str (inc start)) "-" (str end) " of " (str total)]]
|
||||
(paginator-internal- params)])
|
||||
@@ -6,9 +6,12 @@
|
||||
[auto-ap.ssr.auth :as auth]
|
||||
[auto-ap.ssr.transaction.insights :as insights]
|
||||
[auto-ap.ssr.company.company-1099 :as company-1099]
|
||||
[auto-ap.ssr.company.yodlee :as company-yodlee]
|
||||
[auto-ap.ssr.search :as search]
|
||||
[auto-ap.ssr.company-dropdown :as company-dropdown]
|
||||
[auto-ap.routes.ezcater-xls :as ezcater-xls]))
|
||||
[auto-ap.ssr.company.reports :as company-reports]
|
||||
[auto-ap.routes.ezcater-xls :as ezcater-xls]
|
||||
[auto-ap.ssr.company :as company]))
|
||||
|
||||
;; from auto-ap.ssr-routes, because they're shared
|
||||
|
||||
@@ -18,11 +21,20 @@
|
||||
:admin-history-search (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin admin/history-search)))
|
||||
:admin-history-inspect (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin admin/inspect)))
|
||||
:active-client (wrap-client-redirect-unauthenticated (wrap-secure (wrap-admin company-dropdown/active-client)))
|
||||
:company-dropdown-contents (wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-contents))
|
||||
:company-dropdown-search-results
|
||||
(wrap-client-redirect-unauthenticated (wrap-secure company-dropdown/dropdown-search-results))
|
||||
:company (wrap-client-redirect-unauthenticated (wrap-secure company/page))
|
||||
:company-1099 (wrap-client-redirect-unauthenticated (wrap-secure company-1099/page))
|
||||
:company-1099-vendor-table (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-table))
|
||||
:company-1099-vendor-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-dialog))
|
||||
:company-1099-vendor-save (wrap-client-redirect-unauthenticated (wrap-secure company-1099/vendor-save))
|
||||
:company-yodlee (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/page))
|
||||
:company-yodlee-table (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/table))
|
||||
:company-yodlee-fastlink-dialog (wrap-client-redirect-unauthenticated (wrap-secure company-yodlee/fastlink-dialog))
|
||||
:company-yodlee-provider-account-refresh (wrap-client-redirect-unauthenticated (wrap-admin company-yodlee/refresh-provider-account))
|
||||
:company-reports (wrap-client-redirect-unauthenticated (wrap-secure company-reports/page))
|
||||
:company-reports-table (wrap-client-redirect-unauthenticated (wrap-secure company-reports/table))
|
||||
:company-reports-delete (wrap-client-redirect-unauthenticated (wrap-admin company-reports/delete-report))
|
||||
:transaction-insights (wrap-client-redirect-unauthenticated (wrap-secure insights/page))
|
||||
:transaction-insight-table (wrap-client-redirect-unauthenticated (wrap-secure insights/insight-table))
|
||||
:transaction-insight-rows (wrap-client-redirect-unauthenticated (wrap-secure insights/transaction-rows))
|
||||
|
||||
182
src/clj/auto_ap/ssr/grid_page_helper.clj
Normal file
182
src/clj/auto_ap/ssr/grid_page_helper.clj
Normal file
@@ -0,0 +1,182 @@
|
||||
(ns auto-ap.ssr.grid-page-helper
|
||||
(:require
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.utils :refer [html-response]]
|
||||
[hiccup2.core :as hiccup]
|
||||
[bidi.bidi :as bidi]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[cemerick.url :as url]
|
||||
[clojure.string :as str]
|
||||
[auto-ap.ssr.svg :as svg]))
|
||||
|
||||
(defn row* [gridspec user entity {:keys [flash? delete-after-settle?] :as options}]
|
||||
(let [cells (mapv (fn [header]
|
||||
(com/data-grid-cell {}
|
||||
((:render header) entity)))
|
||||
(:headers gridspec))
|
||||
cells (conj cells (com/data-grid-right-stack-cell {}
|
||||
(into [:form
|
||||
[:input {:type :hidden :name "id" :value ((:id-fn gridspec) entity)}]]
|
||||
((:row-buttons gridspec) user entity))))]
|
||||
(apply com/data-grid-row
|
||||
{:class (when flash?
|
||||
"live-added")
|
||||
"_" (hiccup/raw (when delete-after-settle?
|
||||
" on htmx:afterSettle wait 400ms then remove me"))
|
||||
}
|
||||
cells)))
|
||||
|
||||
(defn sort-icon [sort key]
|
||||
(->> sort
|
||||
(filter (comp #(= key %) :sort-key))
|
||||
first
|
||||
:sort-icon))
|
||||
|
||||
(defn sort-by-list [sort]
|
||||
(if (seq sort)
|
||||
(into
|
||||
[:div.flex.gap-2.items-center
|
||||
"sorted by"
|
||||
|
||||
]
|
||||
(for [{:keys [name sort-icon ]} sort]
|
||||
[:div.py-1.px-3.text-sm.rounded.bg-gray-100.dark:bg-gray-600.flex.items-center.gap-2.relative name [:div.h-4.w-4.mr-3 sort-icon]
|
||||
[:div {:class "absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white hover:scale-110 transition-all duration-300 bg-gray-400 border-2 border-white rounded-full -top-2 -right-2 dark:border-gray-900"}
|
||||
[:div.h-4.w-4 svg/x]
|
||||
]]
|
||||
))
|
||||
"default sort"))
|
||||
|
||||
(defn table* [grid-spec user {:keys [start per-page client flash-id sort]}]
|
||||
(let [start (or start 0)
|
||||
per-page (or per-page 30)
|
||||
[entities total] ((:fetch-page grid-spec)
|
||||
user
|
||||
{:start start
|
||||
:per-page per-page
|
||||
:client-id (:db/id client)
|
||||
:sort sort})]
|
||||
(com/data-grid-card {:id (:id grid-spec)
|
||||
:title (:title grid-spec)
|
||||
:route (:route grid-spec)
|
||||
:start start
|
||||
:per-page per-page
|
||||
:total total
|
||||
:subtitle [:div.flex.items-center.gap-2
|
||||
[:span (format "Total %s: %d, " (:entity-name grid-spec) total)]
|
||||
(sort-by-list sort)]
|
||||
:action-buttons ((:action-buttons grid-spec) user)
|
||||
:rows (for [entity entities]
|
||||
(row* grid-spec user entity {:flash? (= flash-id ((:id-fn grid-spec) entity))}))
|
||||
:thead-params {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||
(:route grid-spec))
|
||||
:hx-target (str "#" (:id grid-spec))
|
||||
:hx-trigger "sorted once"
|
||||
:hx-vals "js:{\"toggle-sort\": event.detail.key || \"\"}"}
|
||||
:headers
|
||||
(conj
|
||||
(mapv
|
||||
(fn [h]
|
||||
(if (:sort-key h)
|
||||
(com/data-grid-sort-header {:class (if-let [show-starting (:show-starting h)]
|
||||
(format "hidden %s:table-cell" show-starting)
|
||||
(:class h))
|
||||
:sort-key (:sort-key h)}
|
||||
|
||||
[:div.flex.gap-4.items-center
|
||||
(:name h)
|
||||
[:div.h-6.w-6.text-gray-400.dark:text-gray-500 (sort-icon sort (:sort-key h))]])
|
||||
(com/data-grid-header {:class (if-let [show-starting (:show-starting h)]
|
||||
(format "hidden %s:table-cell" show-starting)
|
||||
(:class h))
|
||||
:sort-key (:sort-key h)}
|
||||
(:name h))
|
||||
|
||||
))
|
||||
(:headers grid-spec))
|
||||
(com/data-grid-header {}))})))
|
||||
|
||||
|
||||
|
||||
(defn parse-sort [grid-spec q]
|
||||
(if (not-empty q)
|
||||
(into []
|
||||
(map (fn [k]
|
||||
(let [[k v] (str/split k #":")]
|
||||
{:sort-key (str k)
|
||||
:asc (boolean (= "asc" v))
|
||||
:name (:name (first (filter #(= (str k) (:sort-key %)) (:headers grid-spec))))
|
||||
:sort-icon (if (= (boolean (= "asc" v)) true)
|
||||
svg/sort-down
|
||||
svg/sort-up)}))
|
||||
(str/split q #",")))
|
||||
[]))
|
||||
|
||||
(defn toggle-sort [grid-spec q k]
|
||||
(if ((set (map :sort-key q)) k)
|
||||
(mapv
|
||||
(fn [s]
|
||||
(if (= (:sort-key s)
|
||||
k)
|
||||
(-> s
|
||||
(update :asc
|
||||
#(boolean (not %)))
|
||||
(update :sort-icon (fn [x]
|
||||
(if (= x svg/sort-down)
|
||||
svg/sort-up
|
||||
svg/sort-down))))
|
||||
s))
|
||||
q)
|
||||
(conj q {:sort-key k
|
||||
:asc true
|
||||
:name (:name (first (filter #(= (str k) (:sort-key %)) (:headers grid-spec))))
|
||||
:sort-icon svg/sort-down})))
|
||||
|
||||
(defn sort->query [s]
|
||||
(str/join "," (map (fn [k] (format "%s:%s" (:sort-key k) (if (= true (:asc k))
|
||||
"asc"
|
||||
"desc")))
|
||||
s)))
|
||||
|
||||
(defn params->query-string [q]
|
||||
(-> q
|
||||
(dissoc :client :session)
|
||||
(update :sort sort->query)
|
||||
(url/map->query)))
|
||||
|
||||
(defn extract-params [grid-spec {:keys [query-params hx-query-params identity session] :as request}]
|
||||
(let [{hx-start "start" hx-per-page "per-page" hx-sort "sort" } hx-query-params
|
||||
{q-start "start" q-per-page "per-page" q-sort "sort" q-toggle-sort "toggle-sort"} query-params]
|
||||
(cond-> {}
|
||||
hx-start (assoc :start (some-> hx-start not-empty (Long/parseLong )))
|
||||
q-start (assoc :start (some-> q-start not-empty (Long/parseLong )))
|
||||
hx-per-page (assoc :per-page (some-> hx-per-page not-empty (Long/parseLong )))
|
||||
q-per-page (assoc :per-page (some-> q-per-page not-empty (Long/parseLong )))
|
||||
hx-sort (assoc :sort (parse-sort grid-spec hx-sort))
|
||||
q-sort (assoc :sort (parse-sort grid-spec q-sort))
|
||||
(not-empty q-toggle-sort) (update :sort #(toggle-sort grid-spec % q-toggle-sort) )
|
||||
(:session request) (assoc :session (:session request))
|
||||
(:client (:session request)) (assoc :client (:client (:session request))))))
|
||||
|
||||
(defn table [grid-spec {:keys [query-params hx-query-params identity session] :as request}]
|
||||
(let [params (extract-params grid-spec request)
|
||||
query-string (params->query-string params)]
|
||||
(html-response (table*
|
||||
grid-spec
|
||||
identity
|
||||
params
|
||||
)
|
||||
:headers {"hx-push-url" (str "?" query-string)})))
|
||||
|
||||
(defn page [grid-spec {:keys [identity] :as request}]
|
||||
(base-page
|
||||
request
|
||||
(com/page {:nav (:nav grid-spec)
|
||||
:active-client (:client (:session request))
|
||||
:identity (:identity request)}
|
||||
(apply com/breadcrumbs {} (:breadcrumbs grid-spec))
|
||||
(table* grid-spec
|
||||
identity
|
||||
(extract-params grid-spec request)))
|
||||
nil))
|
||||
@@ -131,26 +131,26 @@
|
||||
(if-let [q (get (:form-params request) "q")]
|
||||
(html-response (search-results* q (:identity request)))
|
||||
(html-response
|
||||
(com/dialog
|
||||
[:div.p-2 "Search"]
|
||||
[:div#search.overflow-auto.space-y-6.p-2.h-96
|
||||
|
||||
(com/text-input {:id "search-input"
|
||||
:type "search"
|
||||
:placeholder "5/5/2034 Magheritas"
|
||||
:name "q"
|
||||
:hx-post "/search"
|
||||
:hx-trigger "keyup changed delay:300ms, search"
|
||||
:hx-target "#search-results"
|
||||
:hx-indicator "#search"
|
||||
:value (:q (:params request))
|
||||
:autofocus true})
|
||||
[:i.text-sm.text-gray-600.dark:text-gray-50 "Try dates, numbers, vendors. To filter to specific type, use 'invoice', 'transaction', 'journal-entry', 'payment'."]
|
||||
#_[:style
|
||||
".htmx-request #search-results {display: none} .htmx-request .htmx-indicator { display: block !important; }"]
|
||||
[:div#search-results
|
||||
]
|
||||
[:div.loader.is-loading.big.htmx-indicator ]]
|
||||
nil)
|
||||
:headers {"hx-trigger" "openDialog"})))
|
||||
(com/modal {}
|
||||
(com/modal-card {}
|
||||
[:div.p-2 "Search"]
|
||||
[:div#search.overflow-auto.space-y-6.p-2.h-96
|
||||
|
||||
(com/text-input {:id "search-input"
|
||||
:type "search"
|
||||
:placeholder "5/5/2034 Magheritas"
|
||||
:name "q"
|
||||
:hx-post "/search"
|
||||
:hx-trigger "keyup changed delay:300ms, search"
|
||||
:hx-target "#search-results"
|
||||
:hx-indicator "#search"
|
||||
:value (:q (:params request))
|
||||
:autofocus true})
|
||||
[:i.text-sm.text-gray-600.dark:text-gray-50 "Try dates, numbers, vendors. To filter to specific type, use 'invoice', 'transaction', 'journal-entry', 'payment'."]
|
||||
#_[:style
|
||||
".htmx-request #search-results {display: none} .htmx-request .htmx-indicator { display: block !important; }"]
|
||||
[:div#search-results
|
||||
]
|
||||
[:div.loader.is-loading.big.htmx-indicator ]]
|
||||
nil)))))
|
||||
|
||||
|
||||
@@ -193,3 +193,117 @@
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
|
||||
(def drop-down
|
||||
[:svg {:class "w-4 h-4 ml-2", :aria-hidden "true", :fill "none", :stroke "currentColor", :viewbox "0 0 24 24", :xmlns "http://www.w3.org/2000/svg"}
|
||||
[:path {:stroke-linecap "round", :stroke-linejoin "round", :stroke-width "2", :d "M19 9l-7 7-7-7"}]])
|
||||
|
||||
(def download
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg", :viewBox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "download-thick-bottom"]
|
||||
[:path
|
||||
{:d
|
||||
"M5.5,11.5c-.275,0-.341.159-.146.354l6.292,6.293a.5.5,0,0,0,.709,0l6.311-6.275c.2-.193.13-.353-.145-.355L15.5,11.5V1.5a1,1,0,0,0-1-1h-5a1,1,0,0,0-1,1V11a.5.5,0,0,1-.5.5Z",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:path
|
||||
{:d "M23.5,18.5v4a1,1,0,0,1-1,1H1.5a1,1,0,0,1-1-1v-4",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
|
||||
(def trash
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg", :viewBox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "bin-1"]
|
||||
[:path
|
||||
{:d
|
||||
"M21,4.5,19.188,21.709A2,2,0,0,1,17.2,23.5H6.8a2,2,0,0,1-1.989-1.791L3,4.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "0.5",
|
||||
:y1 "4.5",
|
||||
:x2 "23.5",
|
||||
:y2 "4.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:path
|
||||
{:d "M7.5,4.5v-3a1,1,0,0,1,1-1h7a1,1,0,0,1,1,1v3",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "12",
|
||||
:y1 "9",
|
||||
:x2 "12",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "16.5",
|
||||
:y1 "9",
|
||||
:x2 "16",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]
|
||||
[:line
|
||||
{:x1 "7.5",
|
||||
:y1 "9",
|
||||
:x2 "8",
|
||||
:y2 "19.5",
|
||||
:fill "none",
|
||||
:stroke "currentColor",
|
||||
:stroke-linecap "round",
|
||||
:stroke-linejoin "round"}]])
|
||||
|
||||
(def alert
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "alert-triangle"]
|
||||
[:path {:d "M22.553,22.581a.569.569,0,0,1-.553.894H2a.569.569,0,0,1-.553-.894L11.553,2.37c.246-.492.648-.492.894,0Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "12", :y1 "16.979", :x2 "12", :y2 "9.979", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M11.991,18.979a.246.246,0,0,0-.241.255.255.255,0,0,0,.254.245h.005a.246.246,0,0,0,.241-.255A.255.255,0,0,0,12,18.979h-.005", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
|
||||
|
||||
(def x
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "delete-2"]
|
||||
[:circle {:cx "12", :cy "12", :r "11.5", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "7", :y1 "7", :x2 "17", :y2 "17", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:line {:x1 "17", :y1 "7", :x2 "7", :y2 "17", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round"}]])
|
||||
|
||||
|
||||
(def filled-x
|
||||
[:svg {:xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:circle {:cx "12", :cy "12", :r "11.5", :fill "#FFF", :stroke-linecap "round", :stroke-linejoin "round"}]
|
||||
[:path {:d "M24,12A12,12,0,1,0,12,24,12,12,0,0,0,24,12Zm-7.29,3.28a1,1,0,0,1,0,1.41,1,1,0,0,1-1.42,0l-3.11-3.11a.26.26,0,0,0-.35,0L8.72,16.69a1,1,0,0,1-1.41-1.41l3.11-3.11a.26.26,0,0,0,0-.35L7.31,8.71a1,1,0,0,1,0-1.42,1,1,0,0,1,1.41,0l3.11,3.11a.24.24,0,0,0,.35,0l3.11-3.11a1,1,0,1,1,1.42,1.42L13.6,11.82a.24.24,0,0,0,0,.35Z", :fill "currentColor"}]])
|
||||
|
||||
(def sort-down
|
||||
[:svg {:id "Regular", :xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "arrow-thick-down-4"]
|
||||
[:rect {:y "0.75", :rx "3", :stroke "currentColor", :transform "translate(0 24) rotate(-90)", :fill "none", :stroke-linejoin "round", :width "22.5", :stroke-linecap "round", :stroke-width "1.5px", :x "0.75", :ry "3", :height "22.5"}]
|
||||
[:path {:d "M9.75,6v7.5L6.53,10.28a.75.75,0,0,0-1.28.531v2.068a1.5,1.5,0,0,0,.439,1.06L11.47,19.72a.749.749,0,0,0,1.06,0l5.781-5.781a1.5,1.5,0,0,0,.439-1.06V10.811a.75.75,0,0,0-1.28-.531L14.25,13.5V6a.75.75,0,0,0-.75-.75h-3A.75.75,0,0,0,9.75,6Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]])
|
||||
|
||||
(def sort-up
|
||||
[:svg {:id "Regular", :xmlns "http://www.w3.org/2000/svg", :viewbox "0 0 24 24"}
|
||||
[:defs]
|
||||
[:title "arrow-thick-up-4"]
|
||||
[:rect {:y "0.75", :rx "3", :stroke "currentColor", :transform "translate(24 0) rotate(90)", :fill "none", :stroke-linejoin "round", :width "22.5", :stroke-linecap "round", :stroke-width "1.5px", :x "0.75", :ry "3", :height "22.5"}]
|
||||
[:path {:d "M14.25,18V10.5l3.22,3.22a.75.75,0,0,0,1.28-.531V11.121a1.5,1.5,0,0,0-.439-1.06L12.53,4.28a.749.749,0,0,0-1.06,0L5.689,10.061a1.5,1.5,0,0,0-.439,1.06v2.068a.75.75,0,0,0,1.28.531L9.75,10.5V18a.75.75,0,0,0,.75.75h3A.75.75,0,0,0,14.25,18Z", :fill "none", :stroke "currentColor", :stroke-linecap "round", :stroke-linejoin "round", :stroke-width "1.5px"}]])
|
||||
|
||||
@@ -29,11 +29,13 @@
|
||||
:crossorigin= "anonymous"}]
|
||||
[:script {:src "https://unpkg.com/htmx.org@1.9.0/dist/htmx.js"
|
||||
:crossorigin= "anonymous"}]
|
||||
[:script {:src "/js/htmx-disable.js"}]
|
||||
[:script {:type "text/javascript", :src "https://cdn.yodlee.com/fastlink/v4/initialize.js", :async "async" }]]
|
||||
[:script {:type "text/javascript", :src "https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.7/dist/autoComplete.min.js"}]
|
||||
[:script {:src "https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"}]
|
||||
[:link {:rel "stylesheet" :href "https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" :type "text/css"}]
|
||||
[:body {"_" (hiccup/raw "on closeDialog transition <#modal-holder .modal-content /> opacity to 0.0 over 300ms then add .hidden to <#modal-holder /> on openDialog remove .hidden from #modal-holder")}
|
||||
[:body {:hx-ext "disable-submit"
|
||||
}
|
||||
contents
|
||||
[:script {:src "/js/flowbite.min.js"}]
|
||||
[:script {:lang "text/javascript"}
|
||||
@@ -53,4 +55,13 @@
|
||||
}
|
||||
};
|
||||
|
||||
const collapse = new Collapse($targetEl, $triggerEl, options); ")]]]))
|
||||
const collapse = new Collapse($targetEl, $triggerEl, options);
|
||||
|
||||
|
||||
;
|
||||
|
||||
"
|
||||
|
||||
|
||||
|
||||
)]]]))
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
(:require
|
||||
[auto-ap.logging :as alog]
|
||||
[config.core :refer [env]]
|
||||
[hiccup2.core :as hiccup]))
|
||||
[hiccup2.core :as hiccup]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn html-response [hiccup & {:keys [status headers] :or {status 200 headers {}}}]
|
||||
{:status status
|
||||
@@ -39,3 +40,24 @@
|
||||
(ex-message e)]
|
||||
:status 500)))))))
|
||||
|
||||
(defn form-data->map [form-data]
|
||||
(reduce-kv
|
||||
(fn [acc k v]
|
||||
(cond (and (string? v)
|
||||
(empty? v))
|
||||
acc
|
||||
|
||||
:else
|
||||
(assoc-in acc (->> (str/split k #"_")
|
||||
(mapv #(apply keyword (str/split % #"/"))))
|
||||
v)))
|
||||
{}
|
||||
form-data))
|
||||
|
||||
(defn path->name [k]
|
||||
(cond (keyword? k)
|
||||
(str (namespace k) "/" (name k))
|
||||
|
||||
(seq k)
|
||||
(str/join "_" (map path->name k))
|
||||
:else k))
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
[clj-time.coerce :as coerce]
|
||||
[datomic.api :as dc]
|
||||
[auto-ap.datomic :refer [conn]]
|
||||
[auto-ap.datomic.clients :as d-clients]))
|
||||
[auto-ap.datomic.clients :as d-clients]
|
||||
[clojure.string :as str]))
|
||||
;; switch all of this to use tokens instead of passing around client codes, particularly because the codes
|
||||
;; need to be tweaked for repeats
|
||||
(defn client-code->login [client-code]
|
||||
@@ -61,7 +62,7 @@
|
||||
(client/post (merge {:headers (assoc base-headers
|
||||
"loginName" (:yodlee2-admin-user env)
|
||||
"Content-Type" "application/x-www-form-urlencoded")
|
||||
:body (str "clientId=" (:yodlee2-client-id env) " &secret=" (:yodlee2-client-secret env))
|
||||
:body (str "clientId=" (:yodlee2-client-id env) "&secret=" (:yodlee2-client-secret env))
|
||||
:as :json}
|
||||
other-config)
|
||||
)
|
||||
@@ -75,11 +76,13 @@
|
||||
(log/info "logging in as " client-code)
|
||||
(-> (str (:yodlee2-base-url env) "/auth/token")
|
||||
(client/post (merge {:headers (assoc base-headers
|
||||
"loginName" (if (<= (count client-code) 3)
|
||||
(str client-code client-code)
|
||||
client-code)
|
||||
"loginName" (if (:yodlee2-test-user env)
|
||||
(:yodlee2-test-user env)
|
||||
(if (<= (count client-code) 3)
|
||||
(str client-code client-code)
|
||||
client-code))
|
||||
"Content-Type" "application/x-www-form-urlencoded")
|
||||
:body (str "clientId=" (:yodlee2-client-id env) " &secret=" (:yodlee2-client-secret env))
|
||||
:body (str "clientId=" (:yodlee2-client-id env) "&secret=" (:yodlee2-client-secret env))
|
||||
:as :json}
|
||||
other-config)
|
||||
)
|
||||
@@ -278,10 +281,6 @@
|
||||
provider-accounts)
|
||||
vals)))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(defn delete-provider-account [client-code id]
|
||||
(let [cob-session (login-user (client-code->login client-code))]
|
||||
|
||||
|
||||
@@ -13,12 +13,21 @@
|
||||
["/approve/" [#"\d+" :transaction-id]] {:post :transaction-insight-approve}
|
||||
["/rows/" [#"\d+" :after]] {:get :transaction-insight-rows}
|
||||
["/explain/" [#"\d+" :transaction-id]] {:get :transaction-insight-explain}}}
|
||||
"company" {"/dropdown" :company-dropdown-contents
|
||||
"/active" {:put :active-client}
|
||||
"/1099" :company-1099
|
||||
"/1099/table" {:get :company-1099-vendor-table}
|
||||
"/1099/vendor-dialog" {["/" [#"\d+" :vendor-id]] {:get :company-1099-vendor-dialog
|
||||
:post :company-1099-vendor-save}}}})
|
||||
"company" {"" :company
|
||||
"/dropdown" :company-dropdown-search-results
|
||||
"/active" {:put :active-client}
|
||||
"/1099" :company-1099
|
||||
"/1099/table" {:get :company-1099-vendor-table}
|
||||
"/1099/vendor-dialog" {["/" [#"\d+" :vendor-id]] {:get :company-1099-vendor-dialog
|
||||
:post :company-1099-vendor-save}}
|
||||
"/reports" {"" {:get :company-reports
|
||||
:delete :company-reports-delete}
|
||||
"/table" :company-reports-table}
|
||||
"/yodlee" {"" {:get :company-yodlee}
|
||||
"/table" {:get :company-yodlee-table}
|
||||
"/fastlink" {:get :company-yodlee-fastlink-dialog}
|
||||
"/refresh" {:put :company-yodlee-provider-account-refresh}}
|
||||
}})
|
||||
|
||||
|
||||
(def only-routes ["/" routes])
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
|
||||
(def base-transaction #:transaction {:date #inst "2020-01-02T00:00:00-08:00"
|
||||
:raw-id "1"
|
||||
:id
|
||||
(di/sha-256 "1")
|
||||
:id (di/sha-256 "1")
|
||||
:amount 12.0
|
||||
:description-original "original-description"
|
||||
:status "POSTED"
|
||||
|
||||
Reference in New Issue
Block a user