Merge branch 'develop' of https://github.com/tainacan/tainacan into develop

This commit is contained in:
mateuswetah 2021-04-26 15:27:22 -03:00
commit 4e14364968
48 changed files with 5208 additions and 379 deletions

2
.gitignore vendored
View File

@ -30,6 +30,8 @@ report.txt
demosaved.csv
src/assets/css/tainacan-embeds.css
src/assets/css/tainacan-embeds.css.map
src/assets/css/tainacan-reports.css
src/assets/css/tainacan-reports.css.map
.DS_Store
src/.DS_Store
src/assets/css/tainacan-gutenberg-block-dynamic-items-list.css

View File

@ -12,33 +12,36 @@ sass -E 'UTF-8' --cache-location .tmp/sass-cache-1 src/views/admin/scss/tainacan
sass -E 'UTF-8' --cache-location .tmp/sass-cache-2 src/views/roles/tainacan-roles.scss:src/assets/css/tainacan-roles.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-3 src/views/media-component/media-component.scss:src/assets/css/media-component.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-4 src/views/gutenberg-blocks/tainacan-collections/collections-list/collections-list.scss:src/assets/css/tainacan-gutenberg-block-collections-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-3 src/views/reports/tainacan-reports.scss:src/assets/css/tainacan-reports.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-5 src/views/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-collections-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-4 src/views/media-component/media-component.scss:src/assets/css/media-component.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-6 src/views/gutenberg-blocks/tainacan-items/items-list/items-list.scss:src/assets/css/tainacan-gutenberg-block-items-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-5 src/views/gutenberg-blocks/tainacan-collections/collections-list/collections-list.scss:src/assets/css/tainacan-gutenberg-block-collections-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-7 src/views/gutenberg-blocks/tainacan-items/dynamic-items-list/dynamic-items-list.scss:src/assets/css/tainacan-gutenberg-block-dynamic-items-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-6 src/views/gutenberg-blocks/tainacan-collections/carousel-collections-list/carousel-collections-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-collections-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-8 src/views/gutenberg-blocks/tainacan-items/search-bar/search-bar.scss:src/assets/css/tainacan-gutenberg-block-search-bar.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-7 src/views/gutenberg-blocks/tainacan-items/items-list/items-list.scss:src/assets/css/tainacan-gutenberg-block-items-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-9 src/views/gutenberg-blocks/tainacan-items/carousel-items-list/carousel-items-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-items-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-8 src/views/gutenberg-blocks/tainacan-items/dynamic-items-list/dynamic-items-list.scss:src/assets/css/tainacan-gutenberg-block-dynamic-items-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-9 src/views/gutenberg-blocks/tainacan-items/search-bar/search-bar.scss:src/assets/css/tainacan-gutenberg-block-search-bar.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-10 src/views/gutenberg-blocks/tainacan-items/carousel-items-list/carousel-items-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-items-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-11 src/views/gutenberg-blocks/tainacan-terms/terms-list/terms-list.scss:src/assets/css/tainacan-gutenberg-block-terms-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-12 src/views/gutenberg-blocks/tainacan-items/carousel-items-list/carousel-items-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-items-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-12 src/views/gutenberg-blocks/tainacan-facets/facets-list/facets-list.scss:src/assets/css/tainacan-gutenberg-block-facets-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-12 src/views/gutenberg-blocks/tainacan-terms/terms-list/terms-list.scss:src/assets/css/tainacan-gutenberg-block-terms-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-13 src/views/gutenberg-blocks/tainacan-terms/carousel-terms-list/carousel-terms-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-terms-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-13 src/views/gutenberg-blocks/tainacan-facets/facets-list/facets-list.scss:src/assets/css/tainacan-gutenberg-block-facets-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-14 src/views/gutenberg-blocks/tainacan-facets/faceted-search/faceted-search.scss:src/assets/css/tainacan-gutenberg-block-faceted-search.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-14 src/views/gutenberg-blocks/tainacan-terms/carousel-terms-list/carousel-terms-list.scss:src/assets/css/tainacan-gutenberg-block-carousel-terms-list.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-15 src/views/gutenberg-blocks/tainacan-items/item-submission-form/item-submission-form.scss:src/assets/css/tainacan-gutenberg-block-item-submission-form.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-15 src/views/gutenberg-blocks/tainacan-facets/faceted-search/faceted-search.scss:src/assets/css/tainacan-gutenberg-block-faceted-search.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-16 src/views/gutenberg-blocks/gutenberg-blocks-style.scss:src/assets/css/tainacan-gutenberg-block-common-styles.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-16 src/views/gutenberg-blocks/tainacan-items/item-submission-form/item-submission-form.scss:src/assets/css/tainacan-gutenberg-block-item-submission-form.css
sass -E 'UTF-8' --cache-location .tmp/sass-cache-17 src/views/gutenberg-blocks/gutenberg-blocks-style.scss:src/assets/css/tainacan-gutenberg-block-common-styles.css
echo "Compilação do Sass Concluído!"
exit 0

116
package-lock.json generated
View File

@ -1665,6 +1665,19 @@
}
}
},
"apexcharts": {
"version": "3.24.0",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.24.0.tgz",
"integrity": "sha512-iT6czJCIVrmAtrcO90MZTQCvC+xi6R6Acf0jNH/d40FVTtCfcqECuKIh5iAMyOTtgUb7+fQ8rbadH2bm1kbL9Q==",
"requires": {
"svg.draggable.js": "^2.2.2",
"svg.easing.js": "^2.0.0",
"svg.filter.js": "^2.0.2",
"svg.pathmorphing.js": "^0.1.3",
"svg.resize.js": "^1.4.3",
"svg.select.js": "^3.0.1"
}
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
@ -2992,6 +3005,11 @@
}
}
},
"countup.js": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.0.7.tgz",
"integrity": "sha512-FO0nQdvG1iQwHp28wdvkErxnNUSbdkzztqZ6YNHKLHydngD2tdiKEW8dFrqpahF3tj+Ma70h0vyYrCBzxlVWdg=="
},
"create-ecdh": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
@ -9489,9 +9507,9 @@
"integrity": "sha512-NXzN+/HPObKAx191H3zKlYomE5WrVIkoCB5IaSdvKokxTpjBdWfr0RaP+1Z5KOfDT0ZVz+2tdtiBkhsEQ9p+0A=="
},
"ssri": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz",
"integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==",
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
"integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
"dev": true,
"requires": {
"minipass": "^3.1.1"
@ -10002,6 +10020,70 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
},
"svg.draggable.js": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
"requires": {
"svg.js": "^2.0.1"
}
},
"svg.easing.js": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
"integrity": "sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=",
"requires": {
"svg.js": ">=2.3.x"
}
},
"svg.filter.js": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
"integrity": "sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=",
"requires": {
"svg.js": "^2.2.5"
}
},
"svg.js": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA=="
},
"svg.pathmorphing.js": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
"requires": {
"svg.js": "^2.4.0"
}
},
"svg.resize.js": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
"requires": {
"svg.js": "^2.6.5",
"svg.select.js": "^2.1.2"
},
"dependencies": {
"svg.select.js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
"requires": {
"svg.js": "^2.2.5"
}
}
}
},
"svg.select.js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
"requires": {
"svg.js": "^2.6.5"
}
},
"swiper": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-5.4.5.tgz",
@ -10718,6 +10800,11 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
},
"vue-apexcharts": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/vue-apexcharts/-/vue-apexcharts-1.6.0.tgz",
"integrity": "sha512-sT6tuVTLBwfH3TA7azecDNS/W70bmz14ZJI7aE7QIqcG9I6OywyH7x3hcOeY1v1DxttI8Svc5RuYj4Dd+A5F4g=="
},
"vue-awesome-swiper": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/vue-awesome-swiper/-/vue-awesome-swiper-4.1.1.tgz",
@ -10728,6 +10815,11 @@
"resolved": "https://registry.npmjs.org/vue-blurhash/-/vue-blurhash-0.1.4.tgz",
"integrity": "sha512-B76GgfHXHkdmYgAfI2rZl3BgCMD9OxAgn4Jw2Ro0a8ZoAKa6gqTWUrTo5EGXOftm/EKuMYi1Cc+UcAvV0jnoRw=="
},
"vue-countup-v2": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/vue-countup-v2/-/vue-countup-v2-4.0.0.tgz",
"integrity": "sha512-XjKeHo1ndRlJtXvHd6B1eWOpbrJDdNU3rdYZwVPv2YlUXbvthsBT4kms5Fc/mn9RdXPrMX2H/ktAAQtKjWFisw=="
},
"vue-eslint-parser": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-5.0.0.tgz",
@ -10963,15 +11055,6 @@
"stream-each": "^1.1.0",
"through2": "^2.0.0"
}
},
"ssri": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
"dev": true,
"requires": {
"figgy-pudding": "^3.5.1"
}
}
}
},
@ -11057,6 +11140,15 @@
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"ssri": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
"integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
"dev": true,
"requires": {
"figgy-pudding": "^3.5.1"
}
},
"tapable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",

View File

@ -8,6 +8,8 @@
"build-prod": "cross-env NODE_ENV=production webpack --config webpack.prod.js --display-error-details --progress --hide-modules"
},
"dependencies": {
"apexcharts": "^3.24.0",
"countup.js": "^2.0.7",
"axios": "^0.21.1",
"blurhash": "^1.1.3",
"buefy": "^0.9.7",
@ -24,8 +26,10 @@
"t": "^0.5.1",
"v-tooltip": "^2.0.3",
"vue": "^2.6.11",
"vue-apexcharts": "^1.6.0",
"vue-awesome-swiper": "^4.1.1",
"vue-blurhash": "^0.1.4",
"vue-countup-v2": "^4.0.0",
"vue-masonry-css": "^1.0.3",
"vue-router": "^3.1.6",
"vue-the-mask": "^0.11.1",

View File

@ -0,0 +1,96 @@
#tainacan-reports-app {
padding: 10px 2px 10px 2px;
margin: 0; }
#tainacan-reports-app .tainacan-icon::before {
opacity: 0.0; }
#tainacan-reports-app a:hover {
cursor: pointer; }
#tainacan-reports-app .wp-heading-inline {
margin-bottom: 2rem; }
#tainacan-reports-app .columns {
max-width: 100%;
align-items: flex-start;
justify-content: center; }
#tainacan-reports-app .columns .column {
max-width: 100%;
padding: 0;
position: relative;
box-sizing: content-box; }
#tainacan-reports-app .columns .column .postbox {
margin: 0.75rem; }
#tainacan-reports-app .columns .column .box-last-cached-on {
position: absolute;
bottom: calc(1px + 0.75rem);
right: calc(1px + 0.75rem);
display: inline-block;
padding: 0px 10px;
background-color: var(--tainacan-block-gray2, #dbdbdb);
color: var(--tainacan-block-gray4, #555758);
font-size: 0.875em;
border-top-left-radius: 3px;
opacity: 0.0;
transition: opacity 0.3s ease; }
#tainacan-reports-app .columns .column .box-last-cached-on button {
border: none;
background: none;
cursor: pointer; }
#tainacan-reports-app .columns .column .box-last-cached-on button:hover {
color: var(--wp-admin-theme-color, #007cba); }
#tainacan-reports-app .columns .column .postbox:hover + .box-last-cached-on,
#tainacan-reports-app .columns .column .postbox:hover + .box-last-cached-on + .box-last-cached-on,
#tainacan-reports-app .columns .column .box-last-cached-on:hover {
opacity: 1.0; }
#tainacan-reports-app .postbox {
padding: 1.125rem 1.25rem;
margin-bottom: 0;
height: 100%;
min-height: 380px;
background-color: var(--tainacan-block-gray0, #f6f6f6); }
#tainacan-reports-app .postbox label {
font-weight: bold;
font-size: 0.875rem; }
#tainacan-reports-app .postbox .box-header {
display: flex;
align-items: baseline;
justify-content: space-between;
flex-wrap: wrap; }
#tainacan-reports-app .postbox .box-header .box-header__item {
margin-bottom: 10px;
line-height: 2rem; }
#tainacan-reports-app .graph-mode-switch {
display: inline-block; }
#tainacan-reports-app .graph-mode-switch button {
border: none !important;
background: none !important;
box-shadow: none !important;
padding: 0;
cursor: pointer; }
#tainacan-reports-app .graph-mode-switch button.current {
color: var(--wp-admin-theme-color, #007cba); }
#tainacan-reports-app .tainacan-custom-tooltip {
padding: 0;
display: flex;
justify-content: center;
align-items: flex-start;
flex-direction: column; }
#tainacan-reports-app .tainacan-custom-tooltip .tainacan-custom-tooltip__header {
background-color: var(--tainacan-block-gray1, #f2f2f2);
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
padding: 6px 10px 4px 10px; }
#tainacan-reports-app .tainacan-custom-tooltip .tainacan-custom-tooltip__header + .tainacan-custom-tooltip__body {
padding: 4px 10px 6px 10px; }
#tainacan-reports-app .tainacan-custom-tooltip .tainacan-custom-tooltip__body {
width: 100%;
padding: 6px 10px;
display: flex;
justify-content: center;
align-items: flex-start;
flex-direction: column; }
#tainacan-reports-app .tainacan-custom-tooltip .tainacan-custom-tooltip__body p {
margin-bottom: 4px;
font-size: 0.85rem; }
/*# sourceMappingURL=tainacan-reports.css.map */

View File

@ -0,0 +1,7 @@
{
"version": 3,
"mappings": "AAAA,qBAAsB;EAClB,OAAO,EAAE,iBAAiB;EAC1B,MAAM,EAAE,CAAC;EAGT,4CAAuB;IACnB,OAAO,EAAE,GAAG;EAGhB,6BAAQ;IACJ,MAAM,EAAE,OAAO;EAGnB,wCAAmB;IACf,aAAa,EAAE,IAAI;EAGvB,8BAAS;IACL,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,UAAU;IACvB,eAAe,EAAE,MAAM;IAEvB,sCAAQ;MACJ,SAAS,EAAE,IAAI;MACf,OAAO,EAAE,CAAC;MACV,QAAQ,EAAE,QAAQ;MAClB,UAAU,EAAE,WAAW;MAEvB,+CAAS;QACL,MAAM,EAAE,OAAO;MAEnB,0DAAoB;QAChB,QAAQ,EAAE,QAAQ;QAClB,MAAM,EAAE,mBAAmB;QAC3B,KAAK,EAAE,mBAAmB;QAC1B,OAAO,EAAE,YAAY;QACrB,OAAO,EAAE,QAAQ;QACjB,gBAAgB,EAAE,oCAAoC;QACtD,KAAK,EAAE,oCAAoC;QAC3C,SAAS,EAAE,OAAO;QAClB,sBAAsB,EAAE,GAAG;QAC3B,OAAO,EAAE,GAAG;QACZ,UAAU,EAAE,iBAAiB;QAE7B,iEAAO;UACH,MAAM,EAAE,IAAI;UACZ,UAAU,EAAE,IAAI;UAChB,MAAM,EAAE,OAAO;UAEf,uEAAQ;YACJ,KAAK,EAAE,oCAAoC;MAIvD;;sEAE0B;QACtB,OAAO,EAAE,GAAG;EAKxB,8BAAS;IACL,OAAO,EAAE,gBAAgB;IACzB,aAAa,EAAE,CAAC;IAChB,MAAM,EAAE,IAAI;IACZ,UAAU,EAAE,KAAK;IACjB,gBAAgB,EAAE,oCAAoC;IAEtD,oCAAM;MACF,WAAW,EAAE,IAAI;MACjB,SAAS,EAAE,QAAQ;IAGvB,0CAAY;MACR,OAAO,EAAE,IAAI;MACb,WAAW,EAAE,QAAQ;MACrB,eAAe,EAAE,aAAa;MAC9B,SAAS,EAAE,IAAI;MAEf,4DAAkB;QACd,aAAa,EAAE,IAAI;QACnB,WAAW,EAAE,IAAI;EAI7B,wCAAmB;IACf,OAAO,EAAE,YAAY;IACrB,+CAAO;MACH,MAAM,EAAE,eAAe;MACvB,UAAU,EAAE,eAAe;MAC3B,UAAU,EAAE,eAAe;MAC3B,OAAO,EAAE,CAAC;MACV,MAAM,EAAE,OAAO;MAEf,uDAAU;QACN,KAAK,EAAE,oCAAoC;EAKvD,8CAAyB;IACrB,OAAO,EAAE,CAAC;IACV,OAAO,EAAE,IAAI;IACb,eAAe,EAAE,MAAM;IACvB,WAAW,EAAE,UAAU;IACvB,cAAc,EAAE,MAAM;IAEtB,+EAAiC;MAC7B,gBAAgB,EAAE,oCAAoC;MACtD,OAAO,EAAE,IAAI;MACb,eAAe,EAAE,UAAU;MAC3B,WAAW,EAAE,MAAM;MACnB,KAAK,EAAE,IAAI;MACX,OAAO,EAAE,iBAAiB;IAG9B,gHAAgE;MAC5D,OAAO,EAAE,iBAAiB;IAE9B,6EAA+B;MAC3B,KAAK,EAAE,IAAI;MACX,OAAO,EAAE,QAAQ;MACjB,OAAO,EAAE,IAAI;MACb,eAAe,EAAE,MAAM;MACvB,WAAW,EAAE,UAAU;MACvB,cAAc,EAAE,MAAM;MAEtB,+EAAE;QACE,aAAa,EAAE,GAAG;QAClB,SAAS,EAAE,OAAO",
"sources": ["../../views/reports/tainacan-reports.scss"],
"names": [],
"file": "tainacan-reports.css"
}

View File

@ -0,0 +1,837 @@
<?php
namespace Tainacan\API\EndPoints;
use \Tainacan\API\REST_Controller;
use Tainacan\Entities;
use Tainacan\Repositories;
class REST_Reports_Controller extends REST_Controller {
public function __construct() {
$this->rest_base = 'reports';
parent::__construct();
add_action('init', array(&$this, 'init_objects'), 11);
}
public function init_objects() {
$this->items_repository = Repositories\Items::get_instance();
$this->taxonomy_repository = Repositories\Taxonomies::get_instance();
$this->metadatum_repository = Repositories\Metadata::get_instance();
$this->collections_repository = Repositories\Collections::get_instance();
}
/**
*
*
* @throws \Exception
*/
public function register_routes() {
register_rest_route($this->namespace, $this->rest_base . '/collection',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array($this, 'get_collections'),
'permission_callback' => array($this, 'reports_permissions_check'),
'args' => [
'force' => [
'title' => __( 'Force regenerete', 'tainacan' ),
'type' => 'string',
'default' => 'no',
'description' => __( 'Force generates the reports graphic.', 'tainacan' ),
'enum' => array(
'no',
'yes'
)
]
]
),
)
);
register_rest_route($this->namespace, $this->rest_base . '/collection/(?P<collection_id>[\d]+)/summary',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array($this, 'get_summary'),
'permission_callback' => array($this, 'reports_permissions_check'),
'args' => [
'force' => [
'title' => __( 'Force regenerete', 'tainacan' ),
'type' => 'string',
'default' => 'no',
'description' => __( 'Force generates the reports graphic.', 'tainacan' ),
'enum' => array(
'no',
'yes'
)
]
]
),
)
);
register_rest_route($this->namespace, $this->rest_base . '/collection/(?P<collection_id>[\d]+)/metadata',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array($this, 'get_stats_collection_metadata'),
'permission_callback' => array($this, 'reports_permissions_check'),
'args' => [
'force' => [
'title' => __( 'Force regenerete', 'tainacan' ),
'type' => 'string',
'default' => 'no',
'description' => __( 'Force generates the reports graphic.', 'tainacan' ),
'enum' => array(
'no',
'yes'
)
]
]
),
)
);
register_rest_route($this->namespace, $this->rest_base . '/metadata',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array($this, 'get_stats_collection_metadata'),
'permission_callback' => array($this, 'reports_permissions_check'),
'args' => [
'force' => [
'title' => __( 'Force regenerete', 'tainacan' ),
'type' => 'string',
'default' => 'no',
'description' => __( 'Force generates the reports graphic.', 'tainacan' ),
'enum' => array(
'no',
'yes'
)
]
]
),
)
);
register_rest_route($this->namespace, $this->rest_base . '/repository/summary',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array($this, 'get_summary'),
'permission_callback' => array($this, 'reports_permissions_check'),
'args' => [
'force' => [
'title' => __( 'Force regenerete', 'tainacan' ),
'type' => 'string',
'default' => 'no',
'description' => __( 'Force generates the reports graphic.', 'tainacan' ),
'enum' => array(
'no',
'yes'
)
]
]
),
)
);
register_rest_route($this->namespace, $this->rest_base . '/taxonomy',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array($this, 'get_taxonomies_list'),
'permission_callback' => array($this, 'reports_permissions_check'),
'args' => [
'force' => [
'title' => __( 'Force regenerete', 'tainacan' ),
'type' => 'string',
'default' => 'no',
'description' => __( 'Force generates the reports graphic.', 'tainacan' ),
'enum' => array(
'no',
'yes'
)
]
]
),
)
);
register_rest_route($this->namespace, $this->rest_base . '/taxonomy/(?P<taxonomy_id>[\d]+)',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array($this, 'get_taxonomy'),
'permission_callback' => array($this, 'reports_permissions_check'),
'args' => [
'force' => [
'title' => __( 'Force regenerete', 'tainacan' ),
'type' => 'string',
'default' => 'no',
'description' => __( 'Force generates the reports graphic.', 'tainacan' ),
'enum' => array(
'no',
'yes'
)
]
]
),
)
);
register_rest_route($this->namespace, $this->rest_base . '/activities',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array($this, 'get_activities'),
'permission_callback' => array($this, 'reports_permissions_check'),
'args' => [
'start' => [
'title' => __( 'start Date', 'tainacan' ),
'type' => 'string',
'format' => 'date-time',
],
'end' => [
'title' => __( 'start Date', 'tainacan' ),
'type' => 'string',
'format' => 'date-time', // RFC3339. https://tools.ietf.org/html/rfc3339#section-5.8
],
'force' => [
'title' => __( 'Force regenerete', 'tainacan' ),
'type' => 'string',
'default' => 'no',
'description' => __( 'Force generates the reports graphic.', 'tainacan' ),
'enum' => array(
'no',
'yes'
)
]
]
),
)
);
register_rest_route($this->namespace, $this->rest_base . '/collection/(?P<collection_id>[\d]+)/activities',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array($this, 'get_activities'),
'permission_callback' => array($this, 'reports_permissions_check'),
'args' => [
'start' => [
'title' => __( 'start Date', 'tainacan' ),
'type' => 'string',
'format' => 'date-time',
],
'end' => [
'title' => __( 'start Date', 'tainacan' ),
'type' => 'string',
'format' => 'date-time', // RFC3339. https://tools.ietf.org/html/rfc3339#section-5.8
],
'force' => [
'title' => __( 'Force regenerete', 'tainacan' ),
'type' => 'string',
'default' => 'no',
'description' => __( 'Force generates the reports graphic.', 'tainacan' ),
'enum' => array(
'no',
'yes'
)
]
]
),
)
);
register_rest_route($this->namespace, $this->rest_base . '/collection/(?P<collection_id>[\d]+)/metadata/(?P<metadata_id>[\d]+)',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array($this, 'get_metadata'),
'permission_callback' => array($this, 'reports_permissions_check'),
'args' => [
'parent' => [
'title' => __( 'parent', 'tainacan' ),
'type' => 'integer',
],
'force' => [
'title' => __( 'Force regenerete', 'tainacan' ),
'type' => 'string',
'default' => 'no',
'description' => __( 'Force generates the reports graphic.', 'tainacan' ),
'enum' => array(
'no',
'yes'
)
]
]
),
)
);
}
public function reports_permissions_check($request) {
return true;
}
public function get_collections($request) {
$response = array(
'list' => []
);
$key_cache_object = 'collections';
$cached_object = $this->get_cache_object($key_cache_object, $request);
if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200);
$collections = $this->collections_repository->fetch([]);
if($collections->have_posts()) {
while ($collections->have_posts()) {
$collections->the_post();
$collection = new Entities\Collection($collections->post);
$total_items = wp_count_posts( $collection->get_db_identifier(), 'readable' );
if (isset($total_items->publish) || isset($total_items->private) ||
isset($total_items->trash) || isset($total_items->draft)) {
$response['list'][$collection->get_db_identifier()] = array(
'id' => $collection->get_id(),
'name' => $collection->get_name(),
'items' => array(
'trash' => intval($total_items->trash),
'draft' => intval($total_items->draft),
'publish' => intval($total_items->publish),
'private' => intval($total_items->private),
'total' => $total_items->trash + $total_items->draft + $total_items->publish + $total_items->private
)
);
}
}
wp_reset_postdata();
}
$this->set_cache_object($key_cache_object, $response);
return new \WP_REST_Response($response, 200);
}
public function get_summary($request) {
$response = array(
'totals'=> array(
'items' => array(
'total' => 0,
'trash' => 0,
'draft' => 0,
'publish' => 0,
'private' => 0
)
)
);
if(isset($request['collection_id'])) {
$collection_id = $request['collection_id'];
$key_cache_object = 'summary_' . $collection_id;
$cached_object = $this->get_cache_object($key_cache_object, $request);
if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200);
$collection = $this->collections_repository->fetch($collection_id);
$total_items = wp_count_posts( $collection->get_db_identifier(), 'readable' );
if (isset($total_items->publish) ||
isset($total_items->private) ||
isset($total_items->trash) ||
isset($total_items->draft)) {
$response['totals']['items']['trash'] = intval($total_items->trash);
$response['totals']['items']['draft'] = intval($total_items->draft);
$response['totals']['items']['publish'] = intval($total_items->publish);
$response['totals']['items']['private'] = intval($total_items->private);
}
} else {
$key_cache_object = 'summary';
$cached_object = $this->get_cache_object($key_cache_object, $request);
if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200);
$collections = $this->collections_repository->fetch([]);
$response['totals']['collections'] = array(
'total' => 0,
'trash' => 0,
'publish' => 0,
'private' => 0
);
if($collections->have_posts()) {
while ($collections->have_posts()) {
$collections->the_post();
$collection = new Entities\Collection($collections->post);
$response['totals']['collections'][$collection->get_status()]++;
$response['totals']['collections']['total']++;
$total_items = wp_count_posts( $collection->get_db_identifier(), 'readable' );
if (isset($total_items->publish) || isset($total_items->private) ||
isset($total_items->trash) || isset($total_items->draft)) {
$response['totals']['items']['trash'] += $total_items->trash;
$response['totals']['items']['draft'] += $total_items->draft;
$response['totals']['items']['publish'] += $total_items->publish;
$response['totals']['items']['private'] += $total_items->private;
}
}
wp_reset_postdata();
}
$response['totals']['taxonomies'] = array(
'total' => 0,
'trash' => 0,
'publish' => 0,
'draft' => 0,
'private' => 0,
'used' => 0,
'not_used'=> 0
);
$total_taxonomies = wp_count_posts( 'tainacan-taxonomy', 'readable' );
if (isset($total_taxonomies->publish) ||
isset($total_taxonomies->private) ||
isset($total_taxonomies->trash) ||
isset($total_taxonomies->draft)) {
$response['totals']['taxonomies']['trash'] = intval($total_taxonomies->trash);
$response['totals']['taxonomies']['publish'] = intval($total_taxonomies->publish);
$response['totals']['taxonomies']['draft'] = intval($total_taxonomies->draft);
$response['totals']['taxonomies']['private'] = intval($total_taxonomies->private);
$response['totals']['taxonomies']['total'] = $response['totals']['taxonomies']['trash'] + $response['totals']['taxonomies']['publish'] + $response['totals']['taxonomies']['draft'] + $response['totals']['taxonomies']['private'];
$response['totals']['taxonomies']['used'] = $this->query_count_used_taxononomies();
$response['totals']['taxonomies']['not_used'] = $response['totals']['taxonomies']['total'] - $response['totals']['taxonomies']['used'];
}
}
$response['totals']['items']['total'] = ($response['totals']['items']['trash'] + $response['totals']['items']['draft'] + $response['totals']['items']['publish'] + $response['totals']['items']['private']);
$this->set_cache_object($key_cache_object, $response);
return new \WP_REST_Response($response, 200);
}
public function get_taxonomies_list($request) {
$response = array(
'list'=> array(
)
);
$key_cache_object = 'taxonomies_list';
$cached_object = $this->get_cache_object($key_cache_object, $request);
if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200);
$taxonomies = $this->taxonomy_repository->fetch();
if($taxonomies->have_posts()){
while ($taxonomies->have_posts()){
$taxonomies->the_post();
$taxonomy = new Entities\Taxonomy($taxonomies->post);
$total_terms = intval(wp_count_terms( $taxonomy->get_db_identifier(), array('hide_empty'=> false) ));
$total_terms_used = intval(wp_count_terms( $taxonomy->get_db_identifier(), array('hide_empty'=> true) ));
$total_terms_not_used = $total_terms - $total_terms_used;
$response['list'][$taxonomy->get_db_identifier()] = array(
'id' => $taxonomy->get_id(),
'name' => $taxonomy->get_name(),
'total_terms' => $total_terms,
'total_terms_used' => $total_terms_used,
'total_terms_not_used' => $total_terms_not_used
);
}
wp_reset_postdata();
}
$this->set_cache_object($key_cache_object, $response);
return new \WP_REST_Response($response, 200);
}
public function get_taxonomy($request) {
$response = array(
'terms'=> array()
);
$taxonomy_id = $request['taxonomy_id'];
$taxonomy = $this->taxonomy_repository->fetch($taxonomy_id);
$taxonomy_identifier = $taxonomy->get_db_identifier();
$taxonomy_total_terms = wp_count_terms($taxonomy_identifier, array('hide_empty' => false) );
$limit = 100;
$offset = 0;
if ( !$taxonomy_total_terms) {
$taxonomy_total_terms = 0;
} else {
$key_cache_object = 'taxonomy_' . $taxonomy_identifier;
$cached_object = $this->get_cache_object($key_cache_object, $request);
if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200);
}
while($offset < $taxonomy_total_terms) {
$terms = get_terms( array(
'taxonomy' => $taxonomy->get_db_identifier(),
'number' => $limit,
'offset' => $offset,
'hide_empty' => false,
) );
foreach ($terms as $term) {
$response['terms'][$term->term_id] = array(
'id' => $term->term_id,
'name' => $term->name,
'count' => $term->count
);
}
$offset+=$limit;
}
$this->set_cache_object($key_cache_object, $response);
return new \WP_REST_Response($response, 200);
}
public function get_metadata($request) {
$response = array(
'list' => array()
);
$collection_id = $request['collection_id'];
$taxonomy_metadata_id = $request['metadata_id'];;
$parent_id = 0;
if ( isset($request['parent']) ) {
$parent_id = (int) $request['parent'];
}
$key_cache_object = "facet_taxonomy_$taxonomy_metadata_id-$collection_id-$parent_id";
$cached_object = $this->get_cache_object($key_cache_object, $request);
if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200);
$metadatum = $this->metadatum_repository->fetch($taxonomy_metadata_id);
$metadatum_type = $metadatum->get_metadata_type();
if($metadatum_type != 'Tainacan\Metadata_Types\Taxonomy') {
return new \WP_REST_Response([
'error_message' => __('Only taxonomy metadata type has allowed.', 'tainacan'),
'metadatum_type' => $metadatum_type
], 400);
}
$args = [
'collection_id' => $collection_id,
'parent_id' => $parent_id,
'count_items'=> true,
];
$data = $this->metadatum_repository->fetch_all_metadatum_values($taxonomy_metadata_id, $args);
$response['list'] = array_map(function($item) {
return [
'type' => $item['type'],
'value' => $item['value'],
'label' => $item['label'],
'parent' => $item['parent'] == null ? 0 : $item['parent'],
'total_items' => intval($item['total_items']),
'total_children' => intval($item['total_children']),
];
}, $data['values']);
$this->set_cache_object($key_cache_object, $response);
return new \WP_REST_Response($response, 200);
}
public function get_stats_collection_metadata($request) {
$response = array(
'totals'=> array(
'metadata' => array(
'total' => 0,
'publish' => 0,
'private' => 0
),
'metadata_per_type' => array()
),
'distribution' => array(
)
);
if(isset($request['collection_id'])) {
$collection_id = $request['collection_id'];
$key_cache_object = 'stats_collection_metadata_' . $collection_id;
$cached_object = $this->get_cache_object($key_cache_object, $request);
if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200);
$collection = new Entities\Collection( $collection_id );
$result_metadatum = $this->metadatum_repository->fetch_by_collection( $collection, [] );
$response['totals']['metadata']['total'] = count($result_metadatum);
$meta_ids=[];
foreach($result_metadatum as $metadatum) {
$meta_type = explode('\\', $metadatum->get_metadata_type()) ;
$meta_type = strtolower($meta_type[sizeof($meta_type)-1]);
$meta_type_name = $metadatum->get_metadata_type_object()->get_name();
if( in_array($meta_type, ['core_description','core_title']) ) {
$meta_type = 'text';
$meta_type_name = (new \Tainacan\Metadata_Types\Text())->get_name();
}
$response['totals']['metadata'][$metadatum->get_status()]++;
$response['totals']['metadata_per_type'][$meta_type]['name'] = $meta_type_name;
$response['totals']['metadata_per_type'][$meta_type]['count']++;
$meta_ids[] = $metadatum->get_id();
}
$response['distribution'] = $this->query_item_metadata_distribution($meta_ids, $collection->get_db_identifier());
//wp_count_posts()
} else {
$args = [
'meta_query' => [
[
'key' => 'collection_id',
'value' => 'default',
'compare' => '='
]
]
];
$key_cache_object = 'stats_collection_metadata';
$cached_object = $this->get_cache_object($key_cache_object, $request);
if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200);
$result_metadatum = $this->metadatum_repository->fetch( $args, 'OBJECT' );
$meta_ids=[];
foreach($result_metadatum as $metadatum) {
$meta_type = explode('\\', $metadatum->get_metadata_type()) ;
$meta_type = strtolower($meta_type[sizeof($meta_type)-1]);
$meta_type_name = $metadatum->get_metadata_type_object()->get_name();
if( in_array($meta_type, ['core_description','core_title']) ) {
$meta_type = 'text';
$meta_type_name = (new \Tainacan\Metadata_Types\Text())->get_name();
}
$response['totals']['metadata'][$metadatum->get_status()]++;
$response['totals']['metadata_per_type'][$meta_type]['name'] = $meta_type_name;
$response['totals']['metadata_per_type'][$meta_type]['count']++;
$meta_ids[] = $metadatum->get_id();
}
$response['distribution'] = $this->query_item_metadata_distribution($meta_ids, 'default');
}
$this->set_cache_object($key_cache_object, $response);
return new \WP_REST_Response($response, 200);
}
private function query_item_metadata_distribution($meta_ids, $collection_post_type) {
$count_posts = wp_count_posts( $collection_post_type, 'readable' );
$total_items = intval($count_posts->trash) + intval($count_posts->draft) +
intval($count_posts->publish) + intval($count_posts->private);
global $wpdb;
$string_meta_ids = "'".implode("','", $meta_ids)."'";
$sql_statement = $wpdb->prepare(
"SELECT p.post_title AS 'name', p.id AS id, IFNULL(((m.total/$total_items) * 100), 0) as fill_percentage
FROM
$wpdb->posts p LEFT JOIN
(
SELECT meta_key, count(DISTINCT post_id) AS total
FROM $wpdb->postmeta
WHERE $wpdb->postmeta.meta_key IN ($string_meta_ids)
AND $wpdb->postmeta.post_id IN (
SELECT id
FROM $wpdb->posts
WHERE $wpdb->posts.post_type = '$collection_post_type'
)
GROUP BY $wpdb->postmeta.meta_key
UNION
SELECT mt.meta_key, count(DISTINCT tr.object_id) AS total
FROM $wpdb->term_taxonomy tt
INNER JOIN $wpdb->term_relationships tr ON tt.term_taxonomy_id = tr.term_taxonomy_id
INNER JOIN (
SELECT post_id as 'meta_key', concat('tnc_tax_', meta_value) as 'tax_id'
FROM $wpdb->postmeta
WHERE meta_key='_option_taxonomy_id'
) mt ON tt.taxonomy = mt.tax_id
WHERE mt.meta_key IN ($string_meta_ids)
AND tr.object_id IN (
SELECT id
FROM $wpdb->posts
WHERE $wpdb->posts.post_type = '$collection_post_type'
)
GROUP BY mt.meta_key
) m
ON (p.id = m.meta_key)
WHERE p.id IN($string_meta_ids)
"
);
$res = $wpdb->get_results($sql_statement);
//return ['t' => $res, 's' => $sql_statement];
return $res;
}
private function query_count_used_taxononomies() {
global $wpdb;
$sql_statement = $wpdb->prepare(
"SELECT COUNT(DISTINCT($wpdb->postmeta.meta_value))
FROM $wpdb->postmeta
WHERE meta_key = '_option_taxonomy_id'
"
);
$res = intval($wpdb->get_var( $sql_statement ));
return $res;
}
public function get_activities($request) {
$response = array(
'totals' => []
);
$collection_id = false;
if(isset($request['collection_id'])) {
$collection_id = $request['collection_id'];
}
if( isset($request['start']) ) {
$start = new \DateTime($request['start']);
$key_cache_object = 'activities_' . $start->format('Y-m-d') . '_' . $collection_id;
$cached_object = $this->get_cache_object($key_cache_object, $request);
if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200);
$end_limit = $start->add(new \DateInterval('P1Y1D'))->setTime(0,0,0);
$end = isset($request['end']) ? new \DateTime($request['end']) : $end_limit;
if($end > $end_limit)
$end = $end_limit;
$interval = [
'start' => (new \DateTime($request['start']))->format('Y-m-d H:i:s'),
'end' => $end->format('Y-m-d H:i:s')
];
} else {
$key_cache_object = 'activities_' . $collection_id;
$cached_object = $this->get_cache_object($key_cache_object, $request);
if($cached_object !== false ) return new \WP_REST_Response($cached_object, 200);
$end = (new \DateTime())->add(new \DateInterval('P1D'))->setTime(0,0,0);
$interval = [
'end' => $end->format('Y-m-d H:i:s'),
'start' => $end->sub(new \DateInterval('P1Y1D'))->format('Y-m-d H:i:s')
];
}
$response['totals'] = array(
'by_interval' => array(
'start' => $interval['start'],
'end' => $interval['end'],
'general' => $this->get_activities_general($collection_id, $interval),
'by_user' => $this->get_activities_general_by_user($collection_id, $interval)
),
'by_user' => $this->get_activities_users($collection_id)
);
$this->set_cache_object($key_cache_object, $response);
return new \WP_REST_Response($response, 200);
}
private function get_activities_general($collection_id = false, $interval) {
global $wpdb;
$collection_from = "";
$start = $interval['start'];
$end = $interval['end'];
if($collection_id !== false) {
$collection_from = "INNER JOIN $wpdb->postmeta pm ON p.id = pm.post_id AND (pm.meta_key='collection_id' AND pm.meta_value='$collection_id')";
}
$sql_statement = $wpdb->prepare(
"SELECT count(DISTINCT (unix_timestamp(p.post_date) DIV 60)) as total, DATE(p.post_date) as date
FROM $wpdb->posts p $collection_from
WHERE p.post_type='tainacan-log' AND p.post_date BETWEEN '$start' AND '$end'
GROUP BY DATE(p.post_date)
ORDER BY DATE(p.post_date)"
);
return $wpdb->get_results($sql_statement);
}
private function get_activities_general_by_user($collection_id = false, $interval) {
global $wpdb;
$collection_from = "";
$start = $interval['start'];
$end = $interval['end'];
if($collection_id !== false) {
$collection_from = "INNER JOIN $wpdb->postmeta pm ON p.id = pm.post_id AND (pm.meta_key='collection_id' AND pm.meta_value='$collection_id')";
}
$sql_statement = $wpdb->prepare(
"SELECT p.post_author as user_id, count(DISTINCT (unix_timestamp(p.post_date) DIV 60)) as total, DATE(p.post_date) as date
FROM $wpdb->posts p $collection_from
WHERE p.post_type='tainacan-log' AND p.post_date BETWEEN '$start' AND '$end'
GROUP BY p.post_author, DATE(p.post_date)
ORDER BY DATE(p.post_date)"
);
$data =$wpdb->get_results($sql_statement);
$arr = array();
$avatar_sizes = rest_get_avatar_sizes();
foreach ($data as $item) {
if(!isset($arr[$item->user_id])) {
$user_data = get_userdata($item->user_id);
$urls = array();
foreach ( $avatar_sizes as $size ) {
$urls[ $size ] = get_avatar_url( $user_data, array( 'size' => $size ) );
}
$arr[$item->user_id] = [
'user' => !$user_data ? [] : [
'id' => $user_data->ID,
'username' => $user_data->user_login,
'name' => $user_data->display_name,
'first_name' => $user_data->first_name,
'last_name' => $user_data->last_name,
'email' => $user_data->user_email,
'avatar_urls' => $urls,
],
'user_id' => $item->user_id,
'total' => 0,
'by_date' => []
];
}
$arr[$item->user_id]['by_date'][] = $item;
$arr[$item->user_id]['total'] += $item->total;
}
return array_values($arr);
}
private function get_activities_users($collection_id = false) {
global $wpdb;
$collection_from = "";
if($collection_id !== false) {
$collection_from = "INNER JOIN $wpdb->postmeta pm_col ON p.id = pm_col.post_id AND (pm_col.meta_key='collection_id' AND pm_col.meta_value='$collection_id')";
}
$sql_statement = $wpdb->prepare(
"SELECT count(DISTINCT (unix_timestamp(p.post_date) DIV 60)) as total, p.post_author as user, pm.meta_value as action
FROM $wpdb->posts p
INNER JOIN $wpdb->postmeta pm ON p.id = pm.post_id AND pm.meta_key = 'action'
$collection_from
WHERE p.post_type='tainacan-log'
GROUP BY p.post_author, pm.meta_value
ORDER BY total DESC"
);
$results = $wpdb->get_results($sql_statement);
$response = [];
$avatar_sizes = rest_get_avatar_sizes();
foreach($results as $key => $result) {
$user = $result->user;
$total = $result->total;
$action = $result->action;
if(!isset($response[$user])) {
$user_data = get_userdata($user);
$urls = array();
foreach ( $avatar_sizes as $size ) {
$urls[ $size ] = get_avatar_url( $user_data, array( 'size' => $size ) );
}
$response[$user] = [
'user' => !$user_data ? [] : [
'id' => $user_data->ID,
'username' => $user_data->user_login,
'name' => $user_data->display_name,
'first_name' => $user_data->first_name,
'last_name' => $user_data->last_name,
'email' => $user_data->user_email,
'avatar_urls' => $urls,
],
'user_id' => $user,
'total' => 0,
'by_action' => []
];
}
$response[$user]['by_action'][$action] = intval($total);
$response[$user]['total'] += $total;
}
return array_values($response);
}
private $prefix_transient_cahce = 'reports_tnc_';
private function get_cache_object($key, $request) {
if ( !isset($request['force']) || $request['force'] == 'no' ) {
$transient = get_transient($this->prefix_transient_cahce . $key);
return $transient;
}
return false;
}
private function set_cache_object($key, $data) {
$expiration = 604800; //one week
$data['report_cached_on'] = (new \DateTime())->format('Y-m-d H:i:s');
return set_transient($this->prefix_transient_cahce . $key, $data, $expiration);
}
}
?>

View File

@ -2,27 +2,28 @@
const TAINACAN_REST_NAMESPACE = 'tainacan/v2';
//$rest_controller = new \Tainacan\API\REST_Controller();
$rest_collections_controller = new \Tainacan\API\EndPoints\REST_Collections_Controller();
$rest_items_controller = new \Tainacan\API\EndPoints\REST_Items_Controller();
$rest_metadata_controller = new \Tainacan\API\EndPoints\REST_Metadata_Controller();
$rest_taxonomies_controller = new \Tainacan\API\EndPoints\REST_Taxonomies_Controller();
$rest_terms_controller = new \Tainacan\API\EndPoints\REST_Terms_Controller();
$rest_filters_controller = new \Tainacan\API\EndPoints\REST_Filters_Controller();
$rest_item_metadata_controller = new \Tainacan\API\EndPoints\REST_Item_Metadata_Controller();
$rest_logs_controller = new \Tainacan\API\EndPoints\REST_Logs_Controller();
$rest_metadata_types_controller = new \Tainacan\API\EndPoints\REST_Metadata_Types_Controller();
$rest_filter_types_controller = new \Tainacan\API\EndPoints\REST_Filter_Types_Controller();
$rest_importers_controller = new \Tainacan\API\EndPoints\REST_Importers_Controller();
$rest_exporters_controller = new \Tainacan\API\EndPoints\REST_Exporters_Controller();
$rest_background_processes_controller = new \Tainacan\API\EndPoints\REST_Background_Processes_Controller();
$rest_bulkedit_controller = new \Tainacan\API\EndPoints\REST_Bulkedit_Controller();
$rest_exposers_controller = new \Tainacan\API\EndPoints\REST_Exposers_Controller();
$rest_roles_controller = new \Tainacan\API\EndPoints\REST_Roles_Controller();
new \Tainacan\API\EndPoints\REST_Metadatum_Mappers_Controller();
$rest_facets_controller = new \Tainacan\API\EndPoints\REST_Facets_Controller();
$rest_oaipmh_expose_controller = new \Tainacan\API\EndPoints\REST_Oaipmh_Expose_Controller();
$rest_sequence_edit_controller = new \Tainacan\API\EndPoints\REST_Sequence_Edit_Controller();
//$rest_controller = new \Tainacan\API\REST_Controller();
$rest_items_controller = new \Tainacan\API\EndPoints\REST_Items_Controller();
$rest_terms_controller = new \Tainacan\API\EndPoints\REST_Terms_Controller();
$rest_logs_controller = new \Tainacan\API\EndPoints\REST_Logs_Controller();
$rest_roles_controller = new \Tainacan\API\EndPoints\REST_Roles_Controller();
$rest_facets_controller = new \Tainacan\API\EndPoints\REST_Facets_Controller();
$rest_reports_controller = new \Tainacan\API\EndPoints\REST_Reports_Controller();
$rest_filters_controller = new \Tainacan\API\EndPoints\REST_Filters_Controller();
$rest_exposers_controller = new \Tainacan\API\EndPoints\REST_Exposers_Controller();
$rest_bulkedit_controller = new \Tainacan\API\EndPoints\REST_Bulkedit_Controller();
$rest_metadata_controller = new \Tainacan\API\EndPoints\REST_Metadata_Controller();
$rest_importers_controller = new \Tainacan\API\EndPoints\REST_Importers_Controller();
$rest_exporters_controller = new \Tainacan\API\EndPoints\REST_Exporters_Controller();
$rest_taxonomies_controller = new \Tainacan\API\EndPoints\REST_Taxonomies_Controller();
$rest_collections_controller = new \Tainacan\API\EndPoints\REST_Collections_Controller();
$rest_filter_types_controller = new \Tainacan\API\EndPoints\REST_Filter_Types_Controller();
$rest_oaipmh_expose_controller = new \Tainacan\API\EndPoints\REST_Oaipmh_Expose_Controller();
$rest_item_metadata_controller = new \Tainacan\API\EndPoints\REST_Item_Metadata_Controller();
$rest_sequence_edit_controller = new \Tainacan\API\EndPoints\REST_Sequence_Edit_Controller();
$rest_metadata_types_controller = new \Tainacan\API\EndPoints\REST_Metadata_Types_Controller();
$rest_metadatum_mappers_controller = new \Tainacan\API\EndPoints\REST_Metadatum_Mappers_Controller();
$rest_background_processes_controller = new \Tainacan\API\EndPoints\REST_Background_Processes_Controller();
// Add here other endpoints imports
?>

View File

@ -83,10 +83,10 @@ class Elastic_Press {
return $formatted_args;
} );
// add_action('ep_add_query_log', function($query) { //using to DEBUG
//add_action('ep_add_query_log', function($query) { //using to DEBUG
// error_log("DEGUG:");
// error_log($query["args"]["body"]);
// });
//});
}
function elasticpress_config_mapping( $mapping ) {
@ -517,7 +517,7 @@ class Elastic_Press {
if (!empty($filter['include'])) {
$custom_filter_include = $custom_filter;
$custom_filter_include['bool']['must'][] = ["bool" => [ "must"=> [ [ "terms" => ["$field.term_id" => $filter['include'] ] ] ] ] ];
$terms_id_inlcude = \implode( "|", $filter['include']);
$terms_id_include = \implode( "|", $filter['include']);
$aggs[$id.'.include'] = [
"filter" => $custom_filter_include,
"aggs" => array(
@ -525,14 +525,14 @@ class Elastic_Press {
"terms"=>array(
"order" => ["_key" => "asc" ],
"field" => "$field.term_name.id",
"include" => "(.)*.($terms_id_inlcude).parent=$parent",
"include" => "(.)*.($terms_id_include).parent=$parent",
"min_doc_count" => 0
)
// "terms"=>array(
// "script" => [
// "lang" => "painless",
// "source" => "def c= ['']; for (int i = 0; i < doc['$field.term_id'].length; ++i) { if( [$terms_id_inlcude].contains(doc['$field.term_id'][i]) ) { c.add(doc['$field.term_name.id'][i]); } } return c;"
// //"source"=> "def c= ['']; if(!params._source.terms.empty && params._source.$field != null) { for(term in params._source.$field) { if( [$terms_id_inlcude].contains(term.term_id) ) { c.add(term.term_id); }}} return c;"
// "source" => "def c= ['']; for (int i = 0; i < doc['$field.term_id'].length; ++i) { if( [$terms_id_include].contains(doc['$field.term_id'][i]) ) { c.add(doc['$field.term_name.id'][i]); } } return c;"
// //"source"=> "def c= ['']; if(!params._source.terms.empty && params._source.$field != null) { for(term in params._source.$field) { if( [$terms_id_include].contains(term.term_id) ) { c.add(term.term_id); }}} return c;"
// ]
// )
)
@ -565,7 +565,7 @@ class Elastic_Press {
"terms"=>array(
"script" => [
"lang" => "painless",
"source"=> "def c= ['']; if(!params._source.meta.empty && params._source.$meta_label != null) { for(meta in params._source.$meta_label) { if([$meta_id_inlcude].contains(meta.raw)) { c.add(meta.raw); }}} return c;"
"source"=> "def c= []; if(!params._source.meta.empty && params._source.$meta_label != null) { for(meta in params._source.$meta_label) { if([$meta_id_inlcude].contains(meta.raw)) { c.add(meta.raw); }}} return c;"
]
//"field"=> $filter['field']
)
@ -632,6 +632,9 @@ class Elastic_Press {
$field = $filter['field'];
if ($filter['metadata_type'] == 'Tainacan\Metadata_Types\Taxonomy') {
$parent = $filter['parent'];
$source = !empty($search) ?
"if ( doc.containsKey('$field.term_name.id') ) {List l = new ArrayList(doc['$field.term_name.id']); return l;} return[];" :
"if ( doc.containsKey('$field.term_name.id') ) {List l = new ArrayList(doc['$field.term_name.id']); l.removeIf(item->!item.endsWith('.parent=$parent')); return l;} return[];" ;
$aggs[$id] = [
"composite" => array(
"size" => $filter['pagesize'],
@ -641,7 +644,8 @@ class Elastic_Press {
"order" => "asc",
"script" => [
"lang" => "painless",
"source" => "if ( doc.containsKey('$field.term_name.id') ) {List l = new ArrayList(doc['$field.term_name.id']); l.removeIf(item->!item.endsWith('.parent=$parent')); return l;} return[];"
"source" => $source
//"source" => "if ( doc.containsKey('$field.term_name.id') ) {List l = new ArrayList(doc['$field.term_name.id']); l.removeIf(item->!item.endsWith('.parent=$parent')); return l;} return[];"
//"source" => "for (int i = 0; i < doc['$field.parent'].length; ++i) { if (doc['$field.parent'][i] == $parent) { return doc['$field.term_name.id'][i]; }}",
//"source" => "for (int i = 0; i < doc['$field.parent'].length; ++i) { if (doc['$field.parent'][i] == $parent) { return doc['$field.term_id'][i]; }}",
//"source" => "def c= ['']; if(!params._source.terms.empty && params._source.$field != null) { for(term in params._source.$field) { if(term.parent==$parent) { c.add(term.term_id); }}} return c;"

View File

@ -208,9 +208,7 @@ class Taxonomies extends Repository {
* @return array Entities\Taxonomy
* @throws \Exception
*/
public function fetch_by_collection( Entities\Collection $collection, $args = [] ) {
$collection_id = $collection->get_id();
public function fetch_by_collection( Entities\Collection $collection, $args = [], $output = 'OBJECT' ) {
$Tainacan_Metadata = Metadata::get_instance();
// get all taxonomy metadata in this collection
@ -234,7 +232,7 @@ class Taxonomies extends Repository {
];
$args = array_merge($args, $newargs);
return $this->fetch($args, 'OBJECT');
return $this->fetch($args, $output);
}

View File

@ -92,8 +92,8 @@ function tainacan_autoload($class_name) {
$dir = TAINACAN_TAPI_DIR;
if(count($class_path) > 3) $dir .= strtolower($class_path[2]).DIRECTORY_SEPARATOR;
} else if( isset( $class_path[1] ) && $class_path[1] === 'OAIPMHExpose' ){
$dir = TAINACAN_OAIPMH_DIR;
if(count($class_path) > 3) $dir .= strtolower($class_path[2]).DIRECTORY_SEPARATOR;
$dir = TAINACAN_OAIPMH_DIR;
if(count($class_path) > 3) $dir .= strtolower($class_path[2]).DIRECTORY_SEPARATOR;
} else if( isset( $class_path[1] ) && substr($class_path[1], 0, 3) === 'Cli' ){
$dir = TAINACAN_CLI_DIR;
} else if( isset( $class_path[1] ) && $class_path[1] === 'Metadata_Types' ) {

View File

@ -110,7 +110,9 @@
promise.request
.then((res) => {
this.updateSelectedValues();
this.$emit('updateParentCollapse', res.data.values.length > 0 );
if (res && res.data && res.data.values)
this.$emit('updateParentCollapse', res.data.values.length > 0 );
})
.catch( (error) => {
if (isCancel(error)) {
@ -133,7 +135,6 @@
});
},
updateSelectedValues() {
if ( !this.query || !this.query.metaquery || !Array.isArray( this.query.metaquery ) )
return false;

View File

@ -64,8 +64,9 @@
promise.request
.then((res) => {
this.updateSelectedValues();
this.$emit('updateParentCollapse', res.data.values.length > 0 );
if (res && res.data && res.data.values)
this.$emit('updateParentCollapse', res.data.values.length > 0 );
})
.catch( error => {
if (isCancel(error))

View File

@ -180,10 +180,11 @@
});
promise.request
.then((res) => {
this.prepareOptionsForTaxonomy(res.data.values ? res.data.values : res.data);
this.isLoadingOptions = false;
this.$emit('updateParentCollapse', res.data.values.length > 0 );
this.prepareOptionsForTaxonomy(res.data.values ? res.data.values : res.data);
if (res && res.data && res.data.values)
this.$emit('updateParentCollapse', res.data.values.length > 0 );
})
.catch( error => {
if (isCancel(error)) {
@ -279,7 +280,6 @@
// });
}
}
this.$emit('sendValuesToTags', { label: onlyLabels, taxonomy: this.taxonomy, value: this.selected });
},
onSelect() {

View File

@ -39,13 +39,96 @@
type="search" />
</b-field>
<!-- Search Results -->
<div
v-if="isSearching"
:style="{ height: isModal ? 'auto' : '0px' }"
class="modal-card-body tainacan-checkbox-list-container">
<a
v-if="isUsingElasticSearch ? previousLastTerms.length && previousLastTerms[0] != checkboxListOffset : checkboxListOffset"
role="button"
class="tainacan-checkbox-list-page-changer"
@click="previousSearchPage">
<span class="icon">
<i class="tainacan-icon tainacan-icon-previous"/>
</span>
</a>
<ul
:class="{
'tainacan-modal-checkbox-list-body-dynamic-m-l': !checkboxListOffset,
'tainacan-modal-checkbox-list-body-dynamic-m-r': noMoreSearchPage,
}"
class="tainacan-modal-checkbox-list-body">
<template v-if="searchResults.length">
<li
class="tainacan-li-checkbox-list"
v-for="(option, key) in searchResults"
:key="key">
<label
v-if="isCheckbox"
class="b-checkbox checkbox">
<input
v-model="selected"
:value="option.id ? (isNaN(Number(option.id)) ? option.id : Number(option.id)) : (isNaN(Number(option.value)) ? option.value : Number(option.value))"
type="checkbox">
<span class="check" />
<span class="control-label">
<span
class="checkbox-label-text"
v-html="`${ option.name ? option.name : (option.label ? (option.hierarchy_path ? renderHierarchicalPath(option.hierarchy_path, option.label) : option.label) : '') }`" />
<span
v-if="option.total_items != undefined"
class="has-text-gray">
&nbsp;{{ "(" + option.total_items + ")" }}
</span>
</span>
</label>
<b-radio
v-tooltip="{
content: (option.name ? option.name : option.label) + (option.total_items != undefined ? ('(' + option.total_items + ' ' + $i18n.get('items') + ')') : ''),
autoHide: false,
}"
v-else
v-model="selected"
:native-value="option.id ? (isNaN(Number(option.id)) ? option.id : Number(option.value)) : (isNaN(Number(option.value)) ? option.value : Number(option.value))">
<span
class="checkbox-label-text"
v-html="`${ option.name ? option.name : (option.label ? (option.hierarchy_path ? renderHierarchicalPath(option.hierarchy_path, option.label) : option.label) : '') }`" />
<span
v-if="option.total_items != undefined"
class="has-text-gray">
&nbsp;{{ "(" + option.total_items + ")" }}
</span>
</b-radio>
</li>
</template>
<template v-if="!isLoadingSearch && !searchResults.length">
<li class="tainacan-li-checkbox-list result-info">
{{ $i18n.get('info_no_terms_found') }}
</li>
</template>
<b-loading
:is-full-page="false"
:active.sync="isLoadingSearch"/>
</ul>
<a
v-if="!noMoreSearchPage"
role="button"
class="tainacan-checkbox-list-page-changer"
@click="nextSearchPage">
<span class="icon">
<i class="tainacan-icon tainacan-icon-next"/>
</span>
</a>
</div>
<!-- Non-hierarchical lists -->
<div
v-if="!isSearching && !isTaxonomy"
:style="{ height: isModal ? 'auto' : '0px' }"
class="modal-card-body tainacan-checkbox-list-container">
<a
v-if="isUsingElasticSearch ? lastTermOnFisrtPage != checkboxListOffset : checkboxListOffset"
v-if="isUsingElasticSearch ? previousLastTerms.length && previousLastTerms[0] != checkboxListOffset : checkboxListOffset"
role="button"
class="tainacan-checkbox-list-page-changer"
@click="previousPage">
@ -59,29 +142,36 @@
'tainacan-modal-checkbox-list-body-dynamic-m-r': noMorePage,
}"
class="tainacan-modal-checkbox-list-body">
<li
class="tainacan-li-checkbox-list"
v-for="(option, key) in options"
:key="key">
<label class="b-checkbox checkbox">
<input
v-model="selected"
:value="option.value"
type="checkbox">
<span class="check" />
<span class="control-label">
<span
v-tooltip="{
content: option.label + (option.total_items != undefined ? ('(' + option.total_items + ' ' + $i18n.get('items') + ')') : ''),
autoHide: false,
}"
class="checkbox-label-text">{{ `${ (option.label ? option.label : '') }` }}</span>
<span
v-if="option.total_items != undefined"
class="has-text-gray">&nbsp;{{ "(" + option.total_items + ")" }}</span>
</span>
</label>
</li>
<template v-if="options.length">
<li
class="tainacan-li-checkbox-list"
v-for="(option, key) in options"
:key="key">
<label class="b-checkbox checkbox">
<input
v-model="selected"
:value="option.value"
type="checkbox">
<span class="check" />
<span class="control-label">
<span
v-tooltip="{
content: option.label + (option.total_items != undefined ? ('(' + option.total_items + ' ' + $i18n.get('items') + ')') : ''),
autoHide: false,
}"
class="checkbox-label-text">{{ `${ (option.label ? option.label : '') }` }}</span>
<span
v-if="option.total_items != undefined"
class="has-text-gray">&nbsp;{{ "(" + option.total_items + ")" }}</span>
</span>
</label>
</li>
</template>
<template v-if="!isCheckboxListLoading && !options.length">
<li class="tainacan-li-checkbox-list result-info">
{{ $i18n.get('info_no_terms_found') }}
</li>
</template>
<b-loading
:is-full-page="false"
:active.sync="isCheckboxListLoading"/>
@ -192,67 +282,14 @@
</ul>
</div>
</transition-group>
<b-loading
:is-full-page="false"
:active.sync="isColumnLoading"/>
<!-- Search Results -->
<div
v-if="isSearching"
:style="{ height: isModal ? 'auto' : '0px' }"
class="modal-card-body tainacan-search-results-container">
<ul class="tainacan-modal-checkbox-search-results-body">
<li
class="tainacan-li-search-results"
v-for="(option, key) in searchResults"
:key="key">
<label
v-if="isCheckbox"
class="b-checkbox checkbox">
<input
v-model="selected"
:value="option.id ? (isNaN(Number(option.id)) ? option.id : Number(option.id)) : (isNaN(Number(option.value)) ? option.value : Number(option.value))"
type="checkbox">
<span class="check" />
<span class="control-label">
<span
class="checkbox-label-text"
v-html="`${ option.name ? option.name : (option.label ? (option.hierarchy_path ? renderHierarchicalPath(option.hierarchy_path, option.label) : option.label) : '') }`" />
<span
v-if="option.total_items != undefined"
class="has-text-gray">
&nbsp;{{ "(" + option.total_items + ")" }}
</span>
</span>
</label>
<b-radio
v-tooltip="{
content: (option.name ? option.name : option.label) + (option.total_items != undefined ? ('(' + option.total_items + ' ' + $i18n.get('items') + ')') : ''),
autoHide: false,
}"
v-else
v-model="selected"
:native-value="option.id ? (isNaN(Number(option.id)) ? option.id : Number(option.value)) : (isNaN(Number(option.value)) ? option.value : Number(option.value))">
<span
class="checkbox-label-text"
v-html="`${ option.name ? option.name : (option.label ? (option.hierarchy_path ? renderHierarchicalPath(option.hierarchy_path, option.label) : option.label) : '') }`" />
<span
v-if="option.total_items != undefined"
class="has-text-gray">
&nbsp;{{ "(" + option.total_items + ")" }}
</span>
</b-radio>
</li>
<b-loading
:is-full-page="false"
:active.sync="isLoadingSearch"/>
</ul>
</div>
</b-tab-item>
<b-tab-item :label="isTaxonomy ? $i18n.get('label_selected_terms') : $i18n.get('label_selected_metadatum_values')">
<div class="modal-card-body tainacan-tags-container">
<b-field
v-if="(selected instanceof Array ? selected.length > 0 : selected) && !isSelectedTermsLoading"
@ -299,7 +336,11 @@
<!--<pre>{{ options }}</pre>-->
<!--<pre>{{ searchResults }}</pre>-->
<!--<pre>{{ selectedTagsName }}</pre>-->
<!-- <pre>{{ checkboxListOffset }}</pre>
<pre>{{ previousLastTerms }}</pre> -->
<!-- <pre>{{ isLoadingSearch }}</pre>
<pre>{{ isCheckboxListLoading }}</pre>
<pre>{{ isColumnLoading }}</pre> -->
<footer
class="field is-grouped"
:class="{ 'form-submit': isModal }">
@ -375,17 +416,26 @@
checkboxListOffset: 0,
isCheckboxListLoading: false,
isLoadingSearch: false,
noMorePage: 0,
noMorePage: false,
noMoreSearchPage: false,
activeTab: 0,
selectedTagsName: {},
isSelectedTermsLoading: false,
isUsingElasticSearch: tainacan_plugin.wp_elasticpress == "1" ? true : false,
previousLastTerms: [],
lastTermOnFisrtPage: null
previousLastTerms: []
}
},
watch: {
optionName(newValue, oldValue) {
if (newValue != oldValue) {
this.noMoreSearchPage = false;
this.checkboxListOffset = this.isUsingElasticSearch ? '' : 0;
this.previousLastTerms = [];
}
}
},
updated(){
if (!this.isSearching)
if (!this.isSearching && this.isTaxonomy)
this.highlightHierarchyPath();
},
created() {
@ -399,6 +449,7 @@
this.$parent.$on('update-taxonomy-inputs', ($event) => {
if ($event.taxonomyId == this.taxonomy_id && $event.metadatumId == this.metadatumId) {
this.finderColumns = [];
this.optionName = '';
this.hierarchicalPath = [];
this.isSearching = false;
this.searchResults = [];
@ -408,7 +459,7 @@
},
mounted() {
if (this.isModal && this.$refs.CheckboxRadioFilterInput)
this.$refs.CheckboxRadioFilterInput.focus()
this.$refs.CheckboxRadioFilterInput.focus();
},
beforeDestroy() {
// Cancels previous Request
@ -418,7 +469,7 @@
methods: {
initializeValues() {
if (!this.isModal)
this.maxNumOptionsCheckboxFinderColumns = 12;
this.maxNumOptionsCheckboxFinderColumns = 24;
if (this.isTaxonomy) {
this.getOptionChildren();
@ -472,18 +523,16 @@
},
previousPage() {
this.noMorePage = 0;
this.noMorePage = false;
this.isCheckboxListLoading = true;
if (this.isUsingElasticSearch) {
this.previousLastTerms.pop();
if (this.previousLastTerms.length > 0) {
this.getOptions(this.previousLastTerms.pop());
this.previousLastTerms.push(this.checkboxListOffset);
} else {
if (this.previousLastTerms.length > 0)
this.getOptions(this.previousLastTerms[this.previousLastTerms.length - 1]);
else
this.getOptions(0);
}
} else {
this.checkboxListOffset -= this.maxNumOptionsCheckboxList;
if (this.checkboxListOffset < 0)
@ -492,9 +541,28 @@
this.getOptions(this.checkboxListOffset);
}
},
previousSearchPage() {
this.noMoreSearchPage = false;
if (this.isUsingElasticSearch) {
this.previousLastTerms.pop();
if (this.previousLastTerms.length > 0)
this.checkboxListOffset = this.previousLastTerms[this.previousLastTerms.length - 1];
else
this.checkboxListOffset = '';
} else {
this.checkboxListOffset -= this.maxNumSearchResultsShow;
if (this.checkboxListOffset < 0)
this.checkboxListOffset = 0;
}
this.autoComplete();
},
nextPage() {
if (this.isUsingElasticSearch)
if (this.isUsingElasticSearch && this.checkboxListOffset)
this.previousLastTerms.push(this.checkboxListOffset);
if (!this.noMorePage && !this.isUsingElasticSearch) {
@ -506,9 +574,23 @@
}
this.isCheckboxListLoading = true;
this.getOptions(this.checkboxListOffset);
},
nextSearchPage() {
if (this.isUsingElasticSearch && this.checkboxListOffset)
this.previousLastTerms.push(this.checkboxListOffset);
if (!this.noMoreSearchPage && !this.isUsingElasticSearch) {
// LIMIT 0, 20 / LIMIT 19, 20 / LIMIT 39, 20 / LIMIT 59, 20
if (this.checkboxListOffset === this.maxNumSearchResultsShow)
this.checkboxListOffset += this.maxNumSearchResultsShow - 1;
else
this.checkboxListOffset += this.maxNumSearchResultsShow;
}
this.autoComplete();
},
getOptions(offset) {
let promise = '';
@ -526,14 +608,14 @@
this.isCheckboxListLoading = false;
this.isLoadingSearch = false;
if (this.isUsingElasticSearch) {
this.checkboxListOffset = res.data.last_term.es_term;
if (!this.isUsingElasticSearch && res.headers && res.headers['x-wp-total'])
this.noMorePage = res.headers['x-wp-total'] <= this.checkboxListOffset + res.data.values.length;
if (this.isUsingElasticSearch) {
this.checkboxListOffset = res.data.last_term.es_term;
this.noMorePage = !res.data.last_term || !res.data.last_term.es_term;
if (!this.lastTermOnFisrtPage || this.lastTermOnFisrtPage == this.checkboxListOffset) {
this.lastTermOnFisrtPage = this.checkboxListOffset;
this.previousLastTerms.push(0);
}
}
})
.catch(error => {
@ -549,33 +631,38 @@
autoComplete: _.debounce( function () {
this.isSearching = !!this.optionName.length;
if (!this.isSearching)
return;
if (this.isTaxonomy) {
this.isLoadingSearch = true;
this.isLoadingSearch = true;
let query_items = { 'current_query': this.query };
let query = `?order=asc&number=${this.maxNumSearchResultsShow}&search=${this.optionName}&${qs.stringify(query_items)}`;
let query_items = { 'current_query': this.query };
let query = `?order=asc&number=${this.maxNumSearchResultsShow}&search=${this.optionName}&${qs.stringify(query_items)}&${this.isUsingElasticSearch ? 'last_term' : 'offset'}=${this.checkboxListOffset}`;
let route = `/collection/${this.collectionId}/facets/${this.metadatumId}${query}`;
let route = `/collection/${this.collectionId}/facets/${this.metadatumId}${query}`;
if (this.collectionId == 'default')
route = `/facets/${this.metadatumId}${query}`;
if (this.collectionId == 'default')
route = `/facets/${this.metadatumId}${query}`;
axios.get(route)
.then((res) => {
this.searchResults = res.data.values;
this.isLoadingSearch = false;
axios.get(route)
.then((res) => {
this.searchResults = res.data.values;
this.isLoadingSearch = false;
}).catch((error) => {
this.$console.log(error);
});
} else {
this.isLoadingSearch = true;
if (!this.isUsingElasticSearch && res.headers && res.headers['x-wp-total'])
this.noMoreSearchPage = res.headers['x-wp-total'] <= this.checkboxListOffset + this.searchResults.length;
if (this.isUsingElasticSearch) {
this.checkboxListOffset = res.data.last_term.es_term;
this.getOptions(0);
}
this.noMoreSearchPage = !res.data.last_term || !res.data.last_term.es_term;
}
}).catch((error) => {
this.$console.log(error);
});
}, 500),
highlightHierarchyPath(){
for (let [index, el] of this.hierarchicalPath.entries()) {
@ -805,7 +892,7 @@
margin-bottom: 0 !important;
ul {
padding: none;
padding: 0;
}
}
.hidden-tabs-section /deep/ .tabs {
@ -844,37 +931,13 @@
cursor: pointer;
border: 1px solid var(--tainacan-gray1);
margin-top: 10px;
margin-bottom: -0.2em;
margin-bottom: 0.1em;
&:hover {
background-color: var(--tainacan-blue1);
}
}
.tainacan-li-search-results {
flex-grow: 0;
flex-shrink: 1;
width: 100%;
padding: 0 0.5em;
.b-checkbox, .b-radio {
max-width: 100%;
margin-right: 10px;
margin-bottom: 0;
overflow: hidden;
align-items: baseline;
.control-label .checkbox-label-text {
white-space: normal;
line-height: 1.45em;
}
}
&:hover {
background-color: var(--tainacan-gray1);
}
}
.tainacan-li-checkbox-modal {
display: flex;
justify-content: space-between;
@ -909,6 +972,13 @@
&:hover {
background-color: var(--tainacan-gray1);
}
&.result-info {
padding: 0.5rem 0.25rem 0.25rem 0.25rem;
width: 100%;
max-width: 100%;
column-span: all;
font-size: 0.75em;
}
}
.tainacan-finder-columns-container {

View File

@ -6,7 +6,7 @@
@input="fetchSelectedLabels()"
v-model="activeTab">
<b-tab-item
style="margin: 0 -1.5rem;"
style="margin: 0 -0.75rem;"
:label="isTaxonomy ? $i18n.get('label_all_terms') : $i18n.get('label_all_metadatum_values')">
<!-- Search input -->
@ -38,13 +38,93 @@
type="search" />
</b-field>
<!-- Search Results -->
<div
v-if="isSearching"
:style="{ height: expandResultsSection ? 'auto' : '0px' }"
class="modal-card-body tainacan-checkbox-list-container">
<a
v-if="checkboxListOffset"
role="button"
class="tainacan-checkbox-list-page-changer"
@click="previousSearchPage">
<span class="icon">
<i class="tainacan-icon tainacan-icon-previous"/>
</span>
</a>
<ul
:class="{
'tainacan-modal-checkbox-list-body-dynamic-m-l': !checkboxListOffset,
'tainacan-modal-checkbox-list-body-dynamic-m-r': noMoreSearchPage,
}"
class="tainacan-modal-checkbox-list-body">
<template v-if="searchResults.length">
<li
class="tainacan-li-checkbox-list"
v-for="(option, key) in searchResults"
:key="key">
<label
v-if="isCheckbox"
class="b-checkbox checkbox">
<input
v-model="selected"
:value="option.id ? (isNaN(Number(option.id)) ? option.id : Number(option.id)) : (isNaN(Number(option.value)) ? option.value : Number(option.value))"
type="checkbox">
<span class="check" />
<span class="control-label">
<span
class="checkbox-label-text"
v-html="`${ option.name ? option.name : (option.label ? (option.hierarchy_path ? renderHierarchicalPath(option.hierarchy_path, option.label) : option.label) : '') }`" />
</span>
</label>
<b-radio
v-tooltip="{
content: option.name ? option.name : option.label,
autoHide: false,
}"
v-else
v-model="selected"
:native-value="option.id ? (isNaN(Number(option.id)) ? option.id : Number(option.value)) : (isNaN(Number(option.value)) ? option.value : Number(option.value))">
<span
class="checkbox-label-text"
v-html="`${ option.name ? option.name : (option.label ? (option.hierarchy_path ? renderHierarchicalPath(option.hierarchy_path, option.label) : option.label) : '') }`" />
</b-radio>
</li>
</template>
<template v-if="!isLoadingSearch && !searchResults.length">
<li class="tainacan-li-checkbox-list result-info">
{{ $i18n.get('info_no_terms_found') }}
</li>
</template>
<template v-if="!isLoadingSearch && allowNew && !searchResults.length">
<li class="tainacan-li-checkbox-list result-info">
<a @click="$emit('showAddNewTerm', { name: optionName })">
{{ $i18n.get('label_new_term') + ' "' + optionName + '"' }}
</a>
</li>
</template>
<b-loading
:is-full-page="false"
:active.sync="isLoadingSearch"/>
</ul>
<a
v-if="!noMoreSearchPage"
role="button"
class="tainacan-checkbox-list-page-changer"
@click="nextSearchPage">
<span class="icon">
<i class="tainacan-icon tainacan-icon-next"/>
</span>
</a>
</div>
<!-- Non-hierarchical lists -->
<div
v-if="!isSearching && !isTaxonomy"
:style="{ height: expandResultsSection ? 'auto' : '0px' }"
class="modal-card-body tainacan-checkbox-list-container">
<a
v-if="isUsingElasticSearch ? lastTermOnFisrtPage != checkboxListOffset : checkboxListOffset"
v-if="checkboxListOffset"
role="button"
class="tainacan-checkbox-list-page-changer"
@click="previousPage">
@ -169,79 +249,20 @@
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-showmore"/>
</span>
</div>
<div
class="warning-no-more-terms"
v-else>
{{ isUsingElasticSearch ? $i18n.get('info_no_more_terms_found') : '' }}
</div>
</li>
</ul>
</div>
</transition-group>
<b-loading
:is-full-page="false"
:active.sync="isColumnLoading"/>
<!-- Search Results -->
<div
v-if="isSearching"
:style="{ height: expandResultsSection ? 'auto' : '0px' }"
class="modal-card-body tainacan-search-results-container">
<ul class="tainacan-modal-checkbox-search-results-body">
<template v-if="searchResults.length">
<li
class="tainacan-li-search-results"
v-for="(option, key) in searchResults"
:key="key">
<label
v-if="isCheckbox"
class="b-checkbox checkbox">
<input
v-model="selected"
:value="option.id ? (isNaN(Number(option.id)) ? option.id : Number(option.id)) : (isNaN(Number(option.value)) ? option.value : Number(option.value))"
type="checkbox">
<span class="check" />
<span class="control-label">
<span
class="checkbox-label-text"
v-html="`${ option.name ? option.name : (option.label ? (option.hierarchy_path ? renderHierarchicalPath(option.hierarchy_path, option.label) : option.label) : '') }`" />
</span>
</label>
<b-radio
v-tooltip="{
content: option.name ? option.name : option.label,
autoHide: false,
}"
v-else
v-model="selected"
:native-value="option.id ? (isNaN(Number(option.id)) ? option.id : Number(option.value)) : (isNaN(Number(option.value)) ? option.value : Number(option.value))">
<span
class="checkbox-label-text"
v-html="`${ option.name ? option.name : (option.label ? (option.hierarchy_path ? renderHierarchicalPath(option.hierarchy_path, option.label) : option.label) : '') }`" />
</b-radio>
</li>
</template>
<template v-if="!isLoadingSearch && !searchResults.length">
<li class="tainacan-li-search-results result-info">
{{ $i18n.get('info_no_terms_found') }}
</li>
</template>
<template v-if="!isLoadingSearch && allowNew && !searchResults.length">
<li class="tainacan-li-search-results result-info">
<a @click="$emit('showAddNewTerm', { name: optionName })">
{{ $i18n.get('label_new_term') + ' "' + optionName + '"' }}
</a>
</li>
</template>
<b-loading
:is-full-page="false"
:active.sync="isLoadingSearch"/>
</ul>
</div>
</b-tab-item>
<b-tab-item :label="(isTaxonomy ? $i18n.get('label_selected_terms') : $i18n.get('label_selected_metadatum_values')) + (amountSelected !== null && amountSelected !== undefined ? (' (' + amountSelected + ')' ): '') ">
<b-tab-item
style="min-height: 56px;"
:label="(isTaxonomy ? $i18n.get('label_selected_terms') : $i18n.get('label_selected_metadatum_values')) + (amountSelected !== null && amountSelected !== undefined ? (' (' + amountSelected + ')' ): '') ">
<div class="modal-card-body tainacan-tags-container">
<b-field
@ -339,13 +360,11 @@
checkboxListOffset: 0,
isCheckboxListLoading: false,
isLoadingSearch: false,
noMorePage: 0,
noMorePage: false,
noMoreSearchPage: false,
activeTab: 0,
selectedTagsName: {},
isSelectedTermsLoading: false,
isUsingElasticSearch: tainacan_plugin.wp_elasticpress == "1" ? true : false,
previousLastTerms: [],
lastTermOnFisrtPage: null,
expandResultsSection: false
}
},
@ -357,14 +376,19 @@
watch: {
selected() {
this.$emit('input', this.selected);
},
optionName(newValue, oldValue) {
if (newValue != oldValue) {
this.noMoreSearchPage = false;
this.checkboxListOffset = 0;
}
}
},
updated(){
if (!this.isSearching)
if (!this.isSearching && this.isTaxonomy)
this.highlightHierarchyPath();
},
created() {
this.isUsingElasticSearch = false;
if (this.shouldBeginWithListExpanded)
this.initializeValues();
@ -389,7 +413,7 @@
},
methods: {
initializeValues() {
this.maxNumOptionsCheckboxFinderColumns = 12;
this.maxNumOptionsCheckboxFinderColumns = 50;
if (this.isTaxonomy) {
this.getOptionChildren();
@ -399,7 +423,7 @@
}
},
shouldShowMoreButton(key) {
return this.totalRemaining[key].remaining == true || (this.finderColumns[key].children.length < this.totalRemaining[key].remaining);
return this.totalRemaining[key].remaining === true || (this.finderColumns[key].children.length < this.totalRemaining[key].remaining);
},
fetchSelectedLabels() {
@ -443,32 +467,31 @@
},
previousPage() {
this.noMorePage = 0;
this.noMorePage = false;
this.isCheckboxListLoading = true;
if (this.isUsingElasticSearch) {
this.previousLastTerms.pop();
this.checkboxListOffset -= this.maxNumOptionsCheckboxList;
if (this.checkboxListOffset < 0)
this.checkboxListOffset = 0;
if (this.previousLastTerms.length > 0) {
this.getOptions(this.previousLastTerms.pop());
this.previousLastTerms.push(this.checkboxListOffset);
} else {
this.getOptions(0);
}
} else {
this.checkboxListOffset -= this.maxNumOptionsCheckboxList;
if (this.checkboxListOffset < 0)
this.checkboxListOffset = 0;
this.getOptions(this.checkboxListOffset);
},
previousSearchPage() {
this.getOptions(this.checkboxListOffset);
}
this.noMoreSearchPage = false;
this.isCheckboxListLoading = true;
this.checkboxListOffset -= this.maxNumSearchResultsShow;
if (this.checkboxListOffset < 0)
this.checkboxListOffset = 0;
this.autoComplete();
},
nextPage() {
if (this.isUsingElasticSearch)
this.previousLastTerms.push(this.checkboxListOffset);
if (!this.noMorePage && !this.isUsingElasticSearch) {
if (!this.noMorePage) {
// LIMIT 0, 20 / LIMIT 19, 20 / LIMIT 39, 20 / LIMIT 59, 20
if (this.checkboxListOffset === this.maxNumOptionsCheckboxList)
this.checkboxListOffset += this.maxNumOptionsCheckboxList - 1;
@ -480,6 +503,20 @@
this.getOptions(this.checkboxListOffset);
},
nextSearchPage() {
if (!this.noMoreSearchPage) {
// LIMIT 0, 20 / LIMIT 19, 20 / LIMIT 39, 20 / LIMIT 59, 20
if (this.checkboxListOffset === this.maxNumSearchResultsShow)
this.checkboxListOffset += this.maxNumSearchResultsShow - 1;
else
this.checkboxListOffset += this.maxNumSearchResultsShow;
}
this.isCheckboxListLoading = true;
this.autoComplete();
},
getOptions(offset) {
let promise = '';
@ -493,19 +530,9 @@
promise = this.getValuesPlainText( this.metadatumId, this.optionName, this.isRepositoryLevel, [], offset, this.maxNumOptionsCheckboxList, true);
promise.request
.then((res) => {
.then(() => {
this.isCheckboxListLoading = false;
this.isLoadingSearch = false;
if (this.isUsingElasticSearch) {
this.checkboxListOffset = res.data.last_term.es_term;
if (!this.lastTermOnFisrtPage || this.lastTermOnFisrtPage == this.checkboxListOffset) {
this.lastTermOnFisrtPage = this.checkboxListOffset;
this.previousLastTerms.push(0);
}
}
})
.catch(error => {
if (isCancel(error))
@ -520,14 +547,14 @@
autoComplete: _.debounce( function () {
this.isSearching = !!this.optionName.length;
if (!this.isSearching)
return;
if (this.isTaxonomy) {
this.isLoadingSearch = true;
let query = `?order=asc&number=${this.maxNumSearchResultsShow}&search=${this.optionName}&hideempty=0`;
let query = `?order=asc&number=${this.maxNumSearchResultsShow}&search=${this.optionName}&hideempty=0&offset=${this.checkboxListOffset}`;
let route = `/collection/${this.collectionId}/facets/${this.metadatumId}${query}`;
@ -538,9 +565,14 @@
.then((res) => {
this.searchResults = res.data.values;
this.isLoadingSearch = false;
}).catch((error) => {
this.$console.log(error);
});
if (res.headers && res.headers['x-wp-total'])
this.noMoreSearchPage = res.headers['x-wp-total'] <= this.checkboxListOffset + this.searchResults.length;
})
.catch((error) => {
this.$console.log(error);
});
} else {
this.isLoadingSearch = true;
@ -604,7 +636,7 @@
this.totalRemaining = Object.assign({}, this.totalRemaining, {
[`${column == undefined ? 0 : column + 1}`]: {
remaining: this.isUsingElasticSearch ? (children.length > 0 ? res.data.last_term.value == children[children.length - 1].value : false) : res.headers['x-wp-total'],
remaining: res.headers['x-wp-total'],
}
});
@ -698,10 +730,9 @@
this.totalRemaining = Object.assign({}, this.totalRemaining, {
[`${key}`]: {
remaining: this.isUsingElasticSearch ? (res.data.values.length > 0 ? (res.data.last_term.value == res.data.values[res.data.values.length - 1].value) : false) : res.headers['x-wp-total'],
remaining: res.headers['x-wp-total'],
}
});
this.isColumnLoading = false;
})
.catch(error => {
@ -737,7 +768,7 @@
}
.tainacan-li-checkbox-list {
max-width: calc(100% - (2 * var(--tainacan-one-column))) !important;
max-width: calc(100% - 20px) !important;
}
}
@ -759,7 +790,7 @@
}
.tab-content {
transition: height 0.2s ease;
padding: 0.5em var(--tainacan-one-column) !important;
padding: 0.5em 20px !important;
}
// In theme, the bootstrap removes the style of <a> without href
@ -790,35 +821,6 @@
}
}
.tainacan-li-search-results {
flex-grow: 0;
flex-shrink: 1;
width: 100%;
padding: 0 0.5em;
.b-checkbox, .b-radio {
max-width: 100%;
margin-right: 10px;
margin-bottom: 0;
overflow: hidden;
align-items: baseline;
.control-label .checkbox-label-text {
white-space: normal;
line-height: 1.45em;
}
}
&:hover:not(.result-info) {
background-color: var(--tainacan-gray1);
}
&.result-info {
width: 100%;
column-span: all;
font-size: 0.75em;
}
}
.tainacan-li-checkbox-modal {
display: flex;
justify-content: space-between;
@ -842,7 +844,8 @@
.tainacan-li-checkbox-list {
flex-grow: 0;
flex-shrink: 1;
max-width: calc(50% - (2 * var(--tainacan-one-column)));
max-width: calc(50% - 20px);
width: 100%;
padding-left: 0.5em;
.b-checkbox, .b-radio {
@ -850,9 +853,16 @@
margin-bottom: 0;
}
&:hover {
&:hover:not(.result-info) {
background-color: var(--tainacan-gray1);
}
&.result-info {
padding: 0.5rem 0.25rem 0.25rem 0.25rem;
width: 100%;
max-width: 100%;
column-span: all;
font-size: 0.75em;
}
}
.tainacan-finder-columns-container {
@ -953,7 +963,6 @@
-webkit-margin-before: 0;
}
.field:not(:last-child) {
margin-bottom: 0 !important;
}
@ -983,10 +992,13 @@
}
.tainacan-checkbox-list-container {
padding: 0 20px !important;
border: 1px solid var(--tainacan-gray1);
border-top: 0px;
margin-top: -1px;
min-height: 232px;
display: flex;
align-items: center;
padding: 0 20px !important;
padding-right: 0 !important;
padding-left: 0 !important;
}
@ -1016,15 +1028,11 @@
}
.tainacan-modal-checkbox-list-body-dynamic-m-l {
margin-left: var(--tainacan-one-column) !important;
margin-left: 2px !important;
}
.tainacan-modal-checkbox-list-body-dynamic-m-r {
margin-right: var(--tainacan-one-column) !important;
}
.tainacan-search-results-container {
padding: 0.25em !important;
margin-right: 2px !important;
}
.tainacan-tags-container {

View File

@ -36,6 +36,7 @@
computed: {
filterTags() {
let tags = this.getFilterTags();
let flattenTags = [];
for (let tag of tags) {
if (Array.isArray(tag.label)) {

View File

@ -0,0 +1,186 @@
import axios from '../../../axios';
export const fetchSummary = ({ commit }, { collectionId, force } ) => {
let endpoint = '/reports';
if (collectionId && collectionId != 'default')
endpoint += '/collection/' + collectionId + '/summary';
else
endpoint += '/repository/summary';
if (force && force === true)
endpoint += '?force=yes'
return new Promise((resolve, reject) => {
axios.tainacan.get(endpoint)
.then(res => {
let summary = res.data;
commit('setSummary', summary);
commit('setReportLatestCachedOn', { report: 'summary-' + (collectionId ? collectionId : 'default'), reportLatestCachedOn: res.data.report_cached_on });
resolve(summary);
})
.catch(error => reject(error));
});
};
export const fetchMetadata = ({ commit }, { collectionId, force } ) => {
let endpoint = '/reports';
if (collectionId && collectionId != 'default')
endpoint += '/collection/' + collectionId + '/metadata';
else
endpoint += '/metadata';
if (force && force === true)
endpoint += '?force=yes';
return new Promise((resolve, reject) => {
axios.tainacan.get(endpoint)
.then(res => {
let metadata = res.data;
commit('setMetadata', metadata);
commit('setReportLatestCachedOn', { report: 'metadata-' + (collectionId ? collectionId : 'default'), reportLatestCachedOn: res.data.report_cached_on });
resolve(metadata);
})
.catch(error => reject(error));
});
};
export const fetchMetadataList = ({ commit }, { collectionId, onlyTaxonomies } ) => {
let endpoint = '';
if (collectionId && collectionId != 'default')
endpoint += '/collection/' + collectionId + '/metadata/';
else
endpoint += '/metadata/';
if (onlyTaxonomies)
endpoint += '?metaquery[0][key]=metadata_type&metaquery[0][value]=Tainacan\\Metadata_Types\\Taxonomy';
return new Promise((resolve, reject) => {
axios.tainacan.get(endpoint)
.then(res => {
let metadataList = res.data;
commit('setMetadataList', metadataList);
resolve(metadataList);
})
.catch(error => reject(error));
});
};
export const fetchCollectionsList = ({ commit }, force) => {
let endpoint = '/reports/collection';
if (force && force === true)
endpoint += '?force=yes';
return new Promise((resolve, reject) => {
axios.tainacan.get(endpoint)
.then(res => {
let collectionsList = res.data.list ? res.data.list : {};
commit('setCollectionsList', collectionsList);
commit('setReportLatestCachedOn', { report: 'collections', reportLatestCachedOn: res.data.report_cached_on });
resolve(collectionsList);
})
.catch(error => reject(error));
});
};
export const fetchTaxonomiesList = ({ commit }, force) => {
let endpoint = '/reports/taxonomy';
if (force && force === true)
endpoint += '?force=yes';
return new Promise((resolve, reject) => {
axios.tainacan.get(endpoint)
.then(res => {
let taxonomiesList = res.data.list ? res.data.list : {};
commit('setTaxonomiesList', taxonomiesList);
commit('setReportLatestCachedOn', { report: 'taxonomies', reportLatestCachedOn: res.data.report_cached_on });
resolve(taxonomiesList);
})
.catch(error => reject(error));
});
};
export const fetchTaxonomyTerms = ({ commit }, { taxonomyId, collectionId, parentTerm, isChildChart, force }) => {
let endpoint = '/reports';
if (collectionId && collectionId != 'default')
endpoint += '/collection/' + collectionId + '/metadata/' + taxonomyId + '?';
else
endpoint += '/taxonomy/' + taxonomyId + '?';
if (force)
endpoint += '&force=yes';
if (parentTerm)
endpoint += '&parent=' + parentTerm;
return new Promise((resolve, reject) => {
axios.tainacan.get(endpoint)
.then(res => {
let taxonomyTerms = {};
if (collectionId && collectionId != 'default')
taxonomyTerms = res.data.list ? res.data.list : [];
else
taxonomyTerms = res.data.terms ? Object.values(res.data.terms) : [];
if (isChildChart) {
commit('setTaxonomyChildTerms', taxonomyTerms);
commit('setReportLatestCachedOn', {
report: 'taxonomy-terms-' + (collectionId ? collectionId : 'default') + '-' + taxonomyId + (parentTerm ? '-' + parentTerm : '') + '-is-child-chart',
reportLatestCachedOn: res.data.report_cached_on
});
} else {
commit('setTaxonomyTerms', taxonomyTerms);
commit('setReportLatestCachedOn', {
report: 'taxonomy-terms-' + (collectionId ? collectionId : 'default') + '-' + taxonomyId + (parentTerm ? '-' + parentTerm : ''),
reportLatestCachedOn: res.data.report_cached_on
});
}
resolve(taxonomyTerms);
})
.catch(error => reject(error));
});
};
export const fetchActivities = ({ commit }, { collectionId, startDate, force } ) => {
let endpoint = '/reports';
if (collectionId && collectionId != 'default')
endpoint += '/collection/' + collectionId + '/activities?';
else
endpoint += '/activities?';
if (startDate)
endpoint += '&start=' + startDate;
if (force)
endpoint += '&force=yes';
return new Promise((resolve, reject) => {
axios.tainacan.get(endpoint)
.then(res => {
let activities = res.data;
commit('setActivities', activities);
commit('setReportLatestCachedOn', { report: 'activities-' + (collectionId ? collectionId : 'default') + (startDate ? '-' + startDate : ''), reportLatestCachedOn: res.data.report_cached_on });
resolve(activities);
})
.catch(error => reject(error));
});
};

View File

@ -0,0 +1,59 @@
export const getSummary = state => {
return state.summary;
};
export const getMetadata = state => {
return state.metadata;
};
export const getMetadataList = state => {
return state.metadataList;
};
export const getCollectionsList = state => {
return state.collectionsList;
};
export const getTaxonomiesList = state => {
return state.taxonomiesList;
};
export const getTaxonomyTerms = state => {
return state.taxonomyTerms;
};
export const getTaxonomyChildTerms = state => {
return state.taxonomyChildTerms;
};
export const getActivities = state => {
return state.activities;
};
export const getStackedBarChartOptions = state => {
return state.stackedBarChartOptions;
};
export const getHorizontalBarChartOptions = state => {
return state.horizontalBarChartOptions;
};
export const getDonutChartOptions = state => {
return state.donutChartOptions;
};
export const getHeatMapChartOptions = state => {
return state.heatMapChartOptions;
};
export const getAreaChartOptions = state => {
return state.areaChartOptions;
};
export const getTreeMapChartOptions = state => {
return state.treeMapChartOptions;
};
export const getReportsLatestCachedOn = state => {
return state.reportsLatestCachedOn;
};

View File

@ -0,0 +1,206 @@
import * as actions from './actions';
import * as getters from './getters';
import * as mutations from './mutations';
const state = {
reportsLatestCachedOn: {},
summary: {},
taxonomiesList: {},
collectionsList: {},
taxonomyTerms: [],
taxonomyChildTerms: [],
metadata: {},
metadataList: {},
activities: {},
stackedBarChartOptions: {
chart: {
type: 'bar',
height: 350,
stacked: true,
toolbar: {
show: true
},
zoom: {
enabled: true,
autoScaleYaxis: true,
}
},
title: {
text: ''
},
responsive: [{
breakpoint: 480,
options: {
legend: {
position: 'bottom',
offsetX: -10,
offsetY: 0
}
}
}],
plotOptions: {
bar: {
borderRadius: 0,
horizontal: false,
},
},
xaxis: {
type: 'category',
tickPlacement: 'on',
categories: [],
tooltip: { enabled: true }
},
yaxis: {
title: {
text: ''
},
tooltip: { enabled: true }
},
legend: {
position: 'right',
offsetY: 40
},
fill: {
opacity: 1
}
},
donutChartOptions: {
chart: {
type: 'donut',
toolbar: {
show: true
},
height: 350,
},
title: {
text: ''
},
stroke: {
width: 0
},
labels: [],
responsive: [{
breakpoint: 480,
options: {
chart: {
width: 200
},
legend: {
position: 'bottom'
}
}
}]
},
horizontalBarChartOptions: {
chart: {
type: 'bar',
height: 350,
stacked: true,
stackType: '100%',
toolbar: {
show: true
},
zoom: {
type: 'y',
enabled: true,
autoScaleYaxis: true,
}
},
plotOptions: {
bar: {
horizontal: true,
},
},
title: {
text: ''
},
xaxis: {
type: 'category',
tickPlacement: 'on',
categories: [],
tooltip: { enabled: true }
},
yaxis: {
tickPlacement: 'on',
tooltip: { enabled: true }
},
tooltip: {
enabled: true
},
fill: {
opacity: 1
},
legend: {
position: 'top',
horizontalAlign: 'left',
offsetX: 40,
}
},
areaChartOptions: {
chart: {
height: 200,
type: 'area',
toolbar: {
show: true,
tools: {
download: true,
selection: false,
zoom: true,
zoomin: true,
zoomout: true,
pan: true,
}
},
},
dataLabels: {
enabled: false
},
stroke: {
width: 1,
curve: 'smooth'
},
fill: {
opacity: 1,
type: 'gradient',
gradient: {
shade: 'light',
type: "vertical",
opacityFrom: 0.65,
opacityTo: 0.35,
}
},
legend: {
position: 'top',
horizontalAlign: 'left'
},
xaxis: {
type: 'datetime'
},
yaxis: {
labels: {
minWidth: 64
}
}
},
treeMapOptions: {
legend: {
show: false
},
chart: {
height: 350,
type: 'treemap'
},
title: {
text: ''
}
}
};
export default {
namespaced: true,
state,
mutations,
actions,
getters
}

View File

@ -0,0 +1,61 @@
import Vue from 'vue';
export const setSummary = (state, summary) => {
state.summary = summary;
};
export const setMetadata = (state, metadata) => {
state.metadata = metadata;
};
export const setMetadataList = (state, metadataList) => {
state.metadataList = metadataList;
};
export const setCollectionsList = (state, collectionsList) => {
state.collectionsList = collectionsList;
};
export const setTaxonomiesList = (state, taxonomiesList) => {
state.taxonomiesList = taxonomiesList;
};
export const setTaxonomyTerms = (state, taxonomyTerms) => {
state.taxonomyTerms = taxonomyTerms;
};
export const setTaxonomyChildTerms = (state, taxonomyTerms) => {
state.taxonomyChildTerms = taxonomyTerms;
};
export const setActivities = (state, activities) => {
state.activities = activities;
};
export const setStackedBarChartOptions = (state, stackedBarChartOptions) => {
state.stackedBarChartOptions = stackedBarChartOptions;
};
export const setHorizontalBarChartOptions = (state, horizontalBarChartOptions) => {
state.horizontalBarChartOptions = horizontalBarChartOptions;
};
export const setDonutChartOptions = (state, donutChartOptions) => {
state.donutChartOptions = donutChartOptions;
};
export const setHeatMapChartOptions = (state, heatMapChartOptions) => {
state.heatMapChartOptions = heatMapChartOptions;
};
export const setAreaChartOptions = (state, areaChartOptions) => {
state.areaChartOptions = areaChartOptions;
};
export const setTreeMapChartOptions = (state, areaChartOptions) => {
state.threeMapChartOptions = areaChartOptions;
};
export const setReportLatestCachedOn = (state, { report, reportLatestCachedOn }) => {
Vue.set(state.reportsLatestCachedOn, report, reportLatestCachedOn);
};

View File

@ -14,6 +14,7 @@ import bulkedition from './modules/bulk-edition';
import exporter from './modules/exporter';
import exposer from './modules/exposer';
import capability from './modules/capability';
import report from './modules/report';
// Vue Dev Tools!
Vue.config.devtools = process && process.env && process.env.NODE_ENV === 'development';
@ -41,6 +42,7 @@ export default new Vuex.Store({
bulkedition,
exporter,
exposer,
capability
capability,
report
}
})

View File

@ -3,8 +3,8 @@
background: none;
width: calc(100% - (2 * var(--tainacan-one-column)));
width: 91.6666667vw;
max-height: 86%;
max-height: 86vh;
max-height: 87%;
max-height: 87vh;
overflow: auto;
border-radius: 0;

View File

@ -58,6 +58,15 @@ class Admin {
array( &$this, 'roles_page' )
);
$reports_page_suffix = add_submenu_page(
$this->menu_slug,
__('Reports', 'tainacan'),
__('Reports', 'tainacan'),
'read',
'tainacan_reports',
array( &$this, 'reports_page' )
);
add_submenu_page(
$this->menu_slug,
__('Item Submission', 'tainacan'),
@ -69,6 +78,7 @@ class Admin {
add_action( 'load-' . $page_suffix, array( &$this, 'load_admin_page' ) );
add_action( 'load-' . $roles_page_suffix, array( &$this, 'load_roles_page' ) );
add_action( 'load-' . $reports_page_suffix, array( &$this, 'load_reports_page' ) );
}
function load_admin_page() {
@ -82,6 +92,11 @@ class Admin {
add_action( 'admin_enqueue_scripts', array( &$this, 'add_roles_js' ), 90 );
}
function load_reports_page() {
add_action( 'admin_enqueue_scripts', array( &$this, 'add_reports_css' ), 90 );
add_action( 'admin_enqueue_scripts', array( &$this, 'add_reports_js' ), 90 );
}
function login_styles_reset( $style ) {
if ( strpos( $style, 'wp-admin-css' ) !== false ) {
$style = null;
@ -130,6 +145,34 @@ class Admin {
echo "<div id='tainacan-roles-app'></div>";
}
function add_reports_css() {
global $TAINACAN_BASE_URL;
wp_enqueue_style( 'tainacan-fonts', $TAINACAN_BASE_URL . '/assets/css/tainacanicons.css', [], TAINACAN_VERSION );
wp_enqueue_style( 'tainacan-reports-page', $TAINACAN_BASE_URL . '/assets/css/tainacan-reports.css', [], TAINACAN_VERSION );
}
function add_reports_js() {
global $TAINACAN_BASE_URL;
wp_enqueue_script( 'tainacan-reports', $TAINACAN_BASE_URL . '/assets/js/reports.js', ['underscore', 'wp-i18n'], TAINACAN_VERSION, true );
wp_set_script_translations('tainacan-reports', 'tainacan');
$settings = $this->get_admin_js_localization_params();
wp_localize_script( 'tainacan-reports', 'tainacan_plugin', $settings );
wp_enqueue_script('underscore');
wp_enqueue_script('wp-i18n');
do_action('tainacan-enqueue-reports-scripts');
}
function reports_page() {
global $TAINACAN_BASE_URL;
// TODO move it to a separate file and start the Vue project
echo "<div id='tainacan-reports-app'></div>";
}
function add_admin_css() {
global $TAINACAN_BASE_URL;

View File

@ -22,6 +22,9 @@ import tainacan from '../../js/axios.js';
import TainacanBlocksCompatToolbar from '../../js/tainacan-blocks-compat-toolbar.js';
import CollectionModal from '../../tainacan-facets/faceted-search/collection-modal.js';
import DeprecatedBlocks from './item-submission-deprecated.js';
registerBlockType('tainacan/item-submission-form', {
title: __('Tainacan Item Submission Form', 'tainacan'),
icon:
@ -160,6 +163,14 @@ registerBlockType('tainacan/item-submission-form', {
metadataSectionLabel: {
type: String,
default: __( 'Metadata', 'tainacan' )
},
showItemLinkButton: {
type: Boolean,
default: false
},
itemLinkButtonLabel: {
type: String,
default: __( 'Go to the item page', 'tainacan' )
}
},
supports: {
@ -197,7 +208,9 @@ registerBlockType('tainacan/item-submission-form', {
documentSectionLabel,
attachmentsSectionLabel,
thumbnailSectionLabel,
metadataSectionLabel
metadataSectionLabel,
showItemLinkButton,
itemLinkButtonLabel
} = attributes;
const fontSizes = [
@ -315,6 +328,26 @@ registerBlockType('tainacan/item-submission-form', {
setAttributes({ sentFormMessage: sentFormMessage });
} }
/>
<ToggleControl
label={__('Show item link button', 'tainacan') }
help={ showItemLinkButton ? __('Do not show a button that links to the item public page.', 'tainacan') : __('Toggle to show a button that links to the item public page.', 'tainacan')}
checked={ showItemLinkButton }
onChange={ ( isChecked ) => {
showItemLinkButton = isChecked;
setAttributes({ showItemLinkButton: isChecked });
}
}
/>
{ showItemLinkButton ?
<TextControl
label={ __('Label for the item button', 'tainacan') }
value={ itemLinkButtonLabel }
onChange={ ( updatedLinkButtonName ) =>{
itemLinkButtonLabel = updatedLinkButtonName;
setAttributes({ itemLinkButtonLabel: itemLinkButtonLabel });
} }
/>
: null }
</PanelBody>
</InspectorControls>
<InspectorControls>
@ -809,6 +842,8 @@ registerBlockType('tainacan/item-submission-form', {
enabledMetadata,
sentFormHeading,
sentFormMessage,
showItemLinkButton,
itemLinkButtonLabel
} = attributes;
return <div
@ -843,8 +878,11 @@ registerBlockType('tainacan/item-submission-form', {
document-section-label={ documentSectionLabel }
thumbnail-section-label={ thumbnailSectionLabel }
attachments-section-label={ attachmentsSectionLabel }
metadata-section-label={ metadataSectionLabel } >
metadata-section-label={ metadataSectionLabel }
show-item-link-button={ showItemLinkButton ? showItemLinkButton.toString() : 'false' }
item-link-button-label={ itemLinkButtonLabel ? itemLinkButtonLabel : __( 'Go to the item page', 'tainacan' ) } >
</div>
</div>
}
},
deprecated: DeprecatedBlocks
});

View File

@ -0,0 +1,196 @@
const { __ } = wp.i18n;
export default [
/* Deprecated on Tainacan 0.18, due to the introduction of itemLinkButtonLabel and showItemLinkButton */
{
attributes: {
collectionId: {
type: String,
default: undefined
},
isCollectionModalOpen: {
type: Boolean,
default: false
},
hideFileModalButton: {
type: Boolean,
default: false
},
hideTextModalButton: {
type: Boolean,
default: false
},
hideLinkModalButton: {
type: Boolean,
default: false
},
hideThumbnailSection: {
type: Boolean,
default: false
},
hideAttachmentsSection: {
type: Boolean,
default: false
},
hideHelpButtons: {
type: Boolean,
default: false
},
hideMetadataTypes: {
type: Boolean,
default: false
},
showAllowCommentsSection: {
type: Boolean,
default: false
},
hideCollapses: {
type: Boolean,
default: false
},
backgroundColor: {
type: Object,
default: { r: 255, g: 255, b: 255, a: 0}
},
baseFontSize: {
type: Number,
default: 16
},
inputColor: {
type: String,
default: '#1d1d1d'
},
inputBackgroundColor: {
type: String,
default: '#ffffff'
},
inputBorderColor: {
type: String,
default: '#dbdbdb'
},
labelColor: {
type: String,
default: '#454647'
},
infoColor: {
type: String,
default: '#555758'
},
primaryColor: {
type: String,
default: '#d9eced'
},
secondaryColor: {
type: String,
default: '#298596'
},
enabledMetadata: {
type: Array,
default: []
},
collectionMetadata: {
type: Array,
default: []
},
isLoadingCollectionMetadata: {
type: Boolean,
default: false
},
sentFormHeading: {
type: String,
default: __( 'Form submitted!', 'tainacan' )
},
sentFormMessage: {
type: String,
default: __( 'Thank you. Your item was submitted to the collection.', 'tainacan' )
},
documentSectionLabel: {
type: String,
default: __( 'Document', 'tainacan' )
},
attachmentsSectionLabel: {
type: String,
default: __( 'Attachments', 'tainacan' )
},
thumbnailSectionLabel: {
type: String,
default: __( 'Thumbnail', 'tainacan' )
},
metadataSectionLabel: {
type: String,
default: __( 'Metadata', 'tainacan' )
}
},
supports: {
align: ['full', 'wide'],
html: true,
multiple: false
},
save({ attributes, className }){
const {
collectionId,
backgroundColor,
hideFileModalButton,
hideTextModalButton,
hideLinkModalButton,
hideThumbnailSection,
hideAttachmentsSection,
showAllowCommentsSection,
hideHelpButtons,
hideMetadataTypes,
hideCollapses,
documentSectionLabel,
thumbnailSectionLabel,
attachmentsSectionLabel,
metadataSectionLabel,
baseFontSize,
inputColor,
inputBackgroundColor,
inputBorderColor,
labelColor,
infoColor,
primaryColor,
secondaryColor,
enabledMetadata,
sentFormHeading,
sentFormMessage,
} = attributes;
return <div
style={{
'font-size': baseFontSize + 'px',
'--tainacan-base-font-size': baseFontSize + 'px',
'--tainacan-background-color': 'rgba(' + backgroundColor.r + ',' + backgroundColor.g + ',' + backgroundColor.b + ',' + backgroundColor.a + ')',
'--tainacan-input-color': inputColor,
'--tainacan-input-background-color': inputBackgroundColor,
'--tainacan-input-border-color': inputBorderColor,
'--tainacan-label-color': labelColor,
'--tainacan-info-color': infoColor,
'--tainacan-primary': primaryColor,
'--tainacan-secondary': secondaryColor
}}
className={ className }>
<div
id="tainacan-item-submission-form"
collection-id={ collectionId }
hide-file-modal-button={ hideFileModalButton.toString() }
hide-text-modal-button={ hideTextModalButton.toString() }
hide-link-modal-button={ hideLinkModalButton.toString() }
hide-thumbnail-section={ hideThumbnailSection.toString() }
hide-attachments-section={ hideAttachmentsSection.toString() }
show-allow-comments-section={ showAllowCommentsSection.toString() }
hide-help-buttons={ hideHelpButtons.toString() }
hide-metadata-types={ hideMetadataTypes.toString() }
hide-collapses={ hideCollapses.toString() }
enabled-metadata={ enabledMetadata.toString() }
sent-form-heading={ sentFormHeading }
sent-form-message={ sentFormMessage }
document-section-label={ documentSectionLabel }
thumbnail-section-label={ thumbnailSectionLabel }
attachments-section-label={ attachmentsSectionLabel }
metadata-section-label={ metadataSectionLabel } >
</div>
</div>
}
}
]

View File

@ -8,7 +8,7 @@ const { InspectorControls, BlockControls } = ( tainacan_blocks.wp_version < '5.2
import TainacanBlocksCompatToolbar from '../../js/tainacan-blocks-compat-toolbar.js';
import ItemsModal from './items-modal.js';
import DeprecatedBlocks from './items-list-deprecated.js'
import DeprecatedBlocks from './items-list-deprecated.js';
registerBlockType('tainacan/items-list', {
title: __('Tainacan Items List', 'tainacan'),

View File

@ -17,7 +17,10 @@
:document-section-label="$root.documentSectionLabel"
:thumbnail-section-label="$root.thumbnailSectionLabel"
:attachments-section-label="$root.attachmentsSectionLabel"
:metadata-section-label="$root.metadataSectionLabel" />
:metadata-section-label="$root.metadataSectionLabel"
:show-item-link-button="$root.showItemLinkButton"
:item-link-button-label="$root.itemLinkButtonLabel"
/>
</template>
<script>

View File

@ -117,7 +117,9 @@ document.addEventListener("DOMContentLoaded", () => {
documentSectionLabel: '',
thumbnailSectionLabel: '',
attachmentsSectionLabel: '',
metadataSectionLabel: ''
metadataSectionLabel: '',
showItemLinkButton: false,
itemLinkButtonLabel: ''
},
beforeMount () {
// Collection source settings
@ -159,7 +161,11 @@ document.addEventListener("DOMContentLoaded", () => {
this.sentFormHeading = this.$el.attributes['sent-form-heading'].value;
if (this.$el.attributes['sent-form-message'] != undefined)
this.sentFormMessage = this.$el.attributes['sent-form-message'].value;
if (this.$el.attributes['item-link-button-label'] != undefined)
this.itemLinkButtonLabel = this.$el.attributes['item-link-button-label'].value;
if (this.$el.attributes['show-item-link-button'] != undefined)
this.showItemLinkButton = this.isParameterTrue('show-item-link-button');
// List of metadata
if (this.$el.attributes['enabled-metadata'] != undefined && this.$el.attributes['enabled-metadata'].value)
this.enabledMetadata = this.$el.attributes['enabled-metadata'].value.split(',');

View File

@ -403,6 +403,14 @@
</p>
<h2 v-if="sentFormHeading">{{ sentFormHeading }}</h2>
<p v-if="sentFormMessage">{{ sentFormMessage }}</p>
<p v-if="showItemLinkButton && linkToCreatedItem">
<a
style="text-decoration: none"
:href="linkToCreatedItem"
class="button is-secondary">
{{ itemLinkButtonLabel }}
</a>
</p>
<br>
</div>
</section>
@ -453,6 +461,8 @@ export default {
thumbnailSectionLabel: String,
attachmentsSectionLabel: String,
metadataSectionLabel: String,
showItemLinkButton: Boolean,
itemLinkButtonLabel: String
},
data(){
return {
@ -475,7 +485,8 @@ export default {
showThumbnailInput: false,
couldLoadCollection: true,
useCaptcha: 'no',
captchaSiteKey: tainacan_plugin['item_submission_captcha_site_key']
captchaSiteKey: tainacan_plugin['item_submission_captcha_site_key'],
linkToCreatedItem: ''
}
},
computed: {
@ -600,9 +611,11 @@ export default {
if (fakeItemId) {
this.finishItemSubmission({ itemSubmission: this.itemSubmission, fakeItemId: fakeItemId })
.then(() => {
.then((item) => {
this.hasSentForm = true;
this.isUploading = false;
this.linkToCreatedItem = item.url;
})
.catch((errors) => {
if (errors.errors) {

View File

@ -0,0 +1,348 @@
<template>
<div>
<div
:class="{ 'skeleton': isFetchingData || !chartData || isBuildingChart }"
class="postbox">
<div
v-if="currentStart && currentEnd"
class="box-header">
<div class="box-header__item tablenav-pages">
<label for="start_year">
{{ $i18n.get('label_activities_during_year') }}&nbsp;
</label>
<span class="pagination-links">
<span
@click="(!isBuildingChart && currentStart.getFullYear() > (minYear + 1)) ? decreaseYear() : null"
:class="{'tablenav-pages-navspan disabled' : isBuildingChart || currentStart.getFullYear() <= (minYear + 1) }"
class="prev-page button"
aria-hidden="true">
</span>
<select
name="start_year"
id="start_year"
:placeholder="$i18n.get('label_select_a_year')"
:disabled="isBuildingChart"
:value="currentStart.getFullYear()"
@input="($event) => setStartYear($event.target.value)">
<option
v-for="index of (maxYear - minYear)"
:key="index"
:value="index + minYear">
{{ index + minYear }}
</option>
</select>
<span
@click="(!isBuildingChart && currentStart.getFullYear() <= (maxYear - 1)) ? increaseYear() : null"
:class="{ 'tablenav-pages-navspan disabled': isBuildingChart || currentStart.getFullYear() > (maxYear - 1) }"
aria-hidden="true"
class="next-page button">
</span>
</span>
</div>
<div class="box-header__item">
<label>{{ $i18n.get('instruction_filter_activities_date') + ': ' }}</label>
<span class="paging-input">
{{ currentStart.toDateString() }} - {{ currentEnd.toDateString() }}
</span>
</div>
</div>
<template v-if="!isFetchingData && chartData && !isBuildingChart">
<div class="users-charts columns is-multiline">
<div
class="users-charts__card column is-full"
v-for="(chartSeries, index) of chartSeriesByUser"
:key="index">
<div
v-if="chartSeries[0].userId == 0"
class="users-charts__card--header">
<div class="anonymous-user-avatar" />
<div class="users-charts__card--header-text">
<p>{{ $i18n.get('label_anonymous_user') }}</p>
<span>{{ chartSeries[0].total }}</span>
</div>
</div>
<div
v-if="chartSeries[0].userId != 0 && chartSeries[0].userId"
class="users-charts__card--header">
<img :src="chartSeries[0].userImg">
<div class="users-charts__card--header-text">
<p>{{ chartSeries[0].userName }}</p>
<span>{{ chartSeries[0].total }}</span>
</div>
</div>
<apexchart
type="area"
height="140"
:series="chartSeries"
:options="chartOptionsByUser[index]" />
</div>
</div>
<apexchart
type="area"
height="200"
:series="chartSeries"
:options="chartOptions" />
</template>
</div>
<slot />
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { reportsChartMixin } from '../js/reports-mixin';
export default {
mixins: [ reportsChartMixin ],
data() {
return {
chartSeriesByUser: [],
chartOptionsByUser: [],
maxYear: new Date().getFullYear(),
minYear: 2017,
currentStart: '',
currentEnd: ''
}
},
computed: {
...mapGetters('report', {
areaChartOptions: 'getAreaChartOptions',
})
},
watch: {
chartData: {
handler() {
if (this.chartData && this.chartData.totals)
this.buildActivitiesChart();
},
immediate: true
},
},
methods: {
increaseYear() {
this.setStartYear(this.currentEnd.getFullYear());
},
decreaseYear() {
let currentStartDate = new Date(this.currentStart.getTime());
this.setStartYear(new Date(currentStartDate.setFullYear(currentStartDate.getFullYear() - 1)).getFullYear());
},
setStartYear(newStartYear) {
let currentStartDate = new Date(this.currentStart.getTime());
const newStart = new Date(currentStartDate.setFullYear(newStartYear));
this.$emit('time-range-update', newStart.toISOString());
},
getDaysArray(start, end) {
let everyDay = [];
for (let day = new Date(start); day <= end; day.setDate(day.getDate() + 1 ) )
everyDay.push(new Date(day));
return everyDay;
},
buildActivitiesChart() {
this.isBuildingChart = true;
const daysWithActivities = (this.chartData.totals.by_interval && this.chartData.totals.by_interval.general) ? this.chartData.totals.by_interval.general : [];
this.currentStart = new Date(this.chartData.totals.by_interval.start);
this.currentEnd = new Date(this.chartData.totals.by_interval.end);
if (daysWithActivities.length)
this.chartSeries = [{
name: this.$i18n.get('activities'),
data: []
}];
else
this.chartSeries = [];
let maximumOfActivitiesInADay = 0;
let everyDay = this.getDaysArray(this.currentStart, this.currentEnd);
everyDay.forEach((aDayInTheLifeTime) => {
const aDayWithSomeActivityIndex = daysWithActivities.findIndex(activity => new Date(activity.date).toISOString().slice(0,10) == aDayInTheLifeTime.toISOString().slice(0,10));
this.chartSeries[0].data.push({
x: aDayInTheLifeTime.getTime(),
y: aDayWithSomeActivityIndex >= 0 ? parseInt(daysWithActivities[aDayWithSomeActivityIndex].total) : 0
});
if (aDayWithSomeActivityIndex >= 0 && maximumOfActivitiesInADay < parseInt(daysWithActivities[aDayWithSomeActivityIndex].total))
maximumOfActivitiesInADay = parseInt(daysWithActivities[aDayWithSomeActivityIndex].total);
});
this.chartOptions = {
...this.areaChartOptions,
title: {
text: this.$i18n.get('label_all_users'),
style: {
fontSize: '13px'
}
},
noData: {
text: daysWithActivities.length ? this.$i18n.get('label_loading_report') : this.$i18n.get('info_no_activities')
},
chart: {
id: 'generalchart',
height: 200,
type: 'area',
group: 'activities',
toolbar: {
show: true,
tools: {
download: true,
selection: false,
zoom: true,
zoomin: true,
zoomout: true,
pan: true,
}
},
},
xaxis: {
type: 'datetime',
min: this.currentStart.getTime(),
max: this.currentEnd.getTime()
},
yaxis: {
show: daysWithActivities.length,
max: maximumOfActivitiesInADay,
tickAmount: 4,
labels: {
minWidth: 48
}
},
colors: ['#01295c'],
};
const daysWithActivitiesByUser = JSON.parse(JSON.stringify(this.chartData.totals.by_interval.by_user)).sort((a, b) => parseInt(b.total) - parseInt(a.total));
this.chartSeriesByUser = [];
this.chartOptionsByUser = []
daysWithActivitiesByUser.forEach((daysWithActivityByUser) => {
let perUserSeries = [];
everyDay.forEach((aDayInTheLifeTime) => {
const aDayWithSomeActivityIndex = daysWithActivityByUser.by_date.findIndex(activity => new Date(activity.date).toISOString().slice(0,10) == aDayInTheLifeTime.toISOString().slice(0,10));
perUserSeries.push({
x: aDayInTheLifeTime.getTime(),
y: aDayWithSomeActivityIndex >= 0 ? parseInt(daysWithActivityByUser.by_date[aDayWithSomeActivityIndex].total) : 0
});
});
this.chartSeriesByUser.push([{
total: daysWithActivityByUser.total,
userId: daysWithActivityByUser.user_id,
userName: daysWithActivityByUser.user.name,
userImg: daysWithActivityByUser.user.avatar_urls ? daysWithActivityByUser.user.avatar_urls['48'] : '',
name: this.$i18n.get('activities'),
data: perUserSeries
}]);
this.chartOptionsByUser.push({
...this.areaChartOptions,
title: {
text: ''
},
chart: {
id: 'userschart-' + daysWithActivityByUser.user_id,
height: 140,
type: 'area',
group: 'activities',
toolbar: {
show: true,
tools: {
download: true,
selection: false,
zoom: true,
zoomin: true,
zoomout: true,
pan: true,
}
},
},
xaxis: {
type: 'datetime',
min: this.currentStart.getTime(),
max: this.currentEnd.getTime()
},
yaxis: {
max: maximumOfActivitiesInADay,
tickAmount: 4,
labels: {
minWidth: 48
}
}
});
});
setTimeout(() => this.isBuildingChart = false, 500);
}
}
}
</script>
<style lang="scss" scoped>
.postbox {
display: flex;
flex-direction: column;
justify-content: flex-start;
min-height: 280px !important;
.screen-per-page {
width: 6em;
}
}
.users-charts {
order: 3;
padding: 12px;
.users-charts__card {
padding: 20px !important;
.users-charts__card--header {
display: flex;
align-items: center;
padding: 6px 12px 2px 12px;
position: absolute;
top: 4px;
left: 26px;
img,
.anonymous-user-avatar {
margin-right: 0.75em;
border-radius: 2px;
width: 32px;
height: 32px;
background-color: var(--tainacan-gray2, #dbdbdb);
}
.anonymous-user-avatar:before {
content: "?";
color: var(--tainacan-gray5, #454647);
font-size: 1.5em;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.users-charts__card--header-text {
display: flex;
flex-direction: column;
p {
font-weight: bold;
font-size: 1.0em;
margin: 0;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
span {
color: var(--tainacan-secondary, #298596);
}
}
}
}
}
</style>

View File

@ -0,0 +1,153 @@
<template>
<div>
<div
v-if="!isFetchingData && chartData.totals && chartData.totals.by_user && !isBuildingChart"
:style="{
maxHeight: ((120 + (chartData.totals.by_user.length * 58)) <= 800 ? (120 + (chartData.totals.by_user.length * 58)) : 800) + 'px'
}"
class="postbox activities-per-user-box">
<template v-if="chartData.totals && chartData.totals.by_user">
<apexchart
:height="120 + (chartData.totals.by_user.length * 58)"
:series="chartSeries"
:options="chartOptions" />
</template>
</div>
<div
v-else
style="min-height=800px"
class="skeleton postbox activities-per-user-box" />
<slot />
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { reportsChartMixin } from '../js/reports-mixin';
export default {
mixins: [ reportsChartMixin ],
computed: {
...mapGetters('report', {
horizontalBarChartOptions: 'getHorizontalBarChartOptions',
})
},
watch: {
chartData: {
handler() {
this.buildActivitiesPerUserChart();
},
immediate: true,
}
},
methods: {
buildActivitiesPerUserChart() {
this.isBuildingChart = true;
if (this.chartData.totals && this.chartData.totals.by_user) {
// Building Activity Per User Bar chart
const orderedActivitiesPerUsers = JSON.parse(JSON.stringify(this.chartData.totals.by_user)).sort((a, b) => b.total - a.total );
let activityPerUserValues = [];
let activityPerUserLabels = [];
const userCount = 100 + (this.chartData.totals.by_user.length * 58);
const validActions = [
"update-metadata-value",
"update",
"create",
"trash",
"new-attachment",
"update-document",
"delete",
"delete-attachment",
"update-thumbnail"
];
// Create empty series for each possible action
validActions.forEach((action) => {
activityPerUserValues.push({
id: action,
name: this.$i18n.get('action_' + action),
data: []
})
});
activityPerUserValues.push({
id: 'others',
name: this.$i18n.get('action_others'),
data: []
});
orderedActivitiesPerUsers.forEach(activityPerUser => {
activityPerUserLabels.push(activityPerUser.user_id == 0 ? this.$i18n.get('label_anonymous_user') : activityPerUser.user.name);
activityPerUserValues.forEach((activity) => {
if (activity.id == 'others') {
let otherActionsTotal = 0;
Object.keys(activityPerUser.by_action).forEach((action) => {
if (validActions.indexOf(action) < 0)
otherActionsTotal += (activityPerUser.by_action[action] ? activityPerUser.by_action[action] : 0);
});
activity.data.push(otherActionsTotal);
} else {
activity.data.push( activityPerUser.by_action[activity.id] ? activityPerUser.by_action[activity.id] : 0 );
}
});
})
this.chartSeries = activityPerUserValues;
this.chartOptions = {
...this.horizontalBarChartOptions,
...{
chart: {
type: 'bar',
height: userCount,
stacked: true,
toolbar: {
show: true
},
zoom: {
type: 'y',
enabled: true,
autoScaleYaxis: true,
}
},
title: {
text: this.$i18n.get('label_activitiy_per_user')
},
labels: activityPerUserLabels,
yaxis: {
title: {
text: ''
},
labels: {
maxWidth: 100
},
tooltip: { enabled: true }
},
tooltip: {
custom: ({ series, seriesIndex, dataPointIndex, w }) => {
return '<div class="tainacan-custom-tooltip"><div class="tainacan-custom-tooltip__header">' +
(orderedActivitiesPerUsers[dataPointIndex].user_id != 0 ? ('<img src="' + orderedActivitiesPerUsers[dataPointIndex].user.avatar_urls['24'] + '">&nbsp;') : '') +
"<span><strong>" + w.globals.labels[dataPointIndex] + '</strong></span></div><div class="tainacan-custom-tooltip__body">' +
w.globals.seriesNames[seriesIndex] + ":&nbsp; <strong>" +
series[seriesIndex][dataPointIndex] +
"</strong></div></div>"
}
}
}
}
}
setTimeout(() => this.isBuildingChart = false, 300);
}
}
}
</script>
<style lang="scss" scoped>
.postbox.activities-per-user-box {
margin: 0.75rem !important;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,105 @@
<template>
<div>
<apexchart
v-if="!isBuildingChart && chartData && Object.values(chartData).length"
height="380px"
class="postbox"
:series="chartSeries"
:options="chartOptions" />
<div
v-else
style="min-height=380px"
class="skeleton postbox" />
<slot />
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { reportsChartMixin } from '../js/reports-mixin';
export default {
mixins: [ reportsChartMixin ],
computed: {
...mapGetters('report', {
stackedBarChartOptions: 'getStackedBarChartOptions',
}),
},
watch: {
chartData: {
handler() {
this.buildCollectionsList();
},
immediate: true
}
},
methods: {
buildCollectionsList() {
this.isBuildingChart = true;
// Building Collections items chart
const orderedCollections = Object.values(this.chartData).sort((a, b) => b.items.total - a.items.total);
let privateItems = [];
let publicItems = [];
let trashItems = [];
let draftItems = [];
let collectionsLabels = [];
orderedCollections.forEach(collection => {
privateItems.push(collection.items.private);
publicItems.push(collection.items.publish);
draftItems.push(collection.items.draft);
trashItems.push(collection.items.trash);
collectionsLabels.push(collection.name);
});
this.chartSeries = [
{
name: this.$i18n.get('status_publish'),
data: publicItems
},
{
name: this.$i18n.get('status_private'),
data: privateItems
},
{
name: this.$i18n.get('status_draft'),
data: draftItems
},
{
name: this.$i18n.get('status_trash'),
data: trashItems
}
];
this.chartOptions = {
...this.stackedBarChartOptions,
...{
title: {
text: this.$i18n.get('label_items_per_collection')
},
xaxis: {
type: 'category',
tickPlacement: 'on',
categories: collectionsLabels,
labels: {
show: true,
trim: true,
hideOverlappingLabels: false
},
tooltip: { enabled: true }
},
yaxis: {
title: {
text: this.$i18n.get('items')
}
}
}
}
this.isBuildingChart = false;
}
}
}
</script>

View File

@ -0,0 +1,243 @@
<template>
<div v-if="taxonomiesList != undefined">
<div
:class="{ 'skeleton': isFetchingData || isBuildingChart || isFetchingTaxonomyTerms || !selectedTaxonomy || !selectedTaxonomy.id }"
class="postbox">
<div class="box-header">
<div class="box-header__item">
<label
v-if="!isFetchingData"
for="select_taxonomies">
{{ $i18n.get('label_items_per_term_from_taxonomy') }}&nbsp;
</label>
<select
v-if="!isFetchingData"
name="select_taxonomies"
id="select_taxonomies"
:placeholder="$i18n.get('label_select_a_taxonomy')"
v-model="selectedTaxonomy">
<option
v-for="(taxonomy, index) of taxonomiesListArray"
:key="index"
:value="taxonomy">
{{ taxonomy.name + ' (' + taxonomy.total_terms + ' ' + ( taxonomy.total_terms == 1 ? $i18n.get('term') : $i18n.get('terms') ) + ')' }}
</option>
</select>
</div>
<div
v-if="selectedTaxonomy && selectedTaxonomy.id && currentTotalTerms >= 56"
class="box-header__item">
<label for="max_terms">{{ $i18n.get('label_terms_per_page') }}</label>
<input
type="number"
step="1"
min="1"
max="999"
class="screen-per-page"
name="max_terms"
id="max_terms"
maxlength="3"
:disabled="isBuildingChart"
v-model.number="maxTermsToDisplay">
</div>
<div
v-if="selectedTaxonomy && selectedTaxonomy.id && currentTotalTerms >= 56"
class="box-header__item tablenav-pages">
<span class="displaying-num">{{ currentTotalTerms + ' ' + $i18n.get('terms') }}</span>
<span class="pagination-links">
<span
@click="!isBuildingChart ? termsDisplayedPage = 1 : null"
:class="{'tablenav-pages-navspan disabled' : termsDisplayedPage <= 1 || isBuildingChart}"
class="first-page button"
aria-hidden="true">
«
</span>
<span
@click="(termsDisplayedPage > 1 && !isBuildingChart) ? termsDisplayedPage-- : null"
:class="{'tablenav-pages-navspan disabled' : termsDisplayedPage <= 1 || isBuildingChart}"
class="prev-page button"
aria-hidden="true">
</span>
<span class="paging-input">
<label
for="current-page-selector"
class="screen-reader-text">
{{ $i18n.get('label_current_page') }}
</label>
<input
class="current-page"
id="current-page-selector"
type="number"
step="1"
min="1"
:disabled="isBuildingChart || maxTermsToDisplay >= currentTotalTerms"
:max="Math.ceil(currentTotalTerms/maxTermsToDisplay)"
name="paged"
v-model.number="termsDisplayedPage"
size="1"
aria-describedby="table-paging">
<span class="tablenav-paging-text"> de <span class="total-pages">{{ Math.ceil(currentTotalTerms/maxTermsToDisplay) }}</span></span>
</span>
<span
@click="(!isBuildingChart && termsDisplayedPage < Math.ceil(currentTotalTerms/maxTermsToDisplay)) ? termsDisplayedPage++ : null"
:class="{'tablenav-pages-navspan disabled' : isBuildingChart || termsDisplayedPage >= Math.ceil(currentTotalTerms/maxTermsToDisplay) }"
aria-hidden="true"
class="next-page button">
</span>
<span
@click="!isBuildingChart ? termsDisplayedPage = Math.ceil(currentTotalTerms/maxTermsToDisplay) : null"
:class="{'tablenav-pages-navspan disabled': isBuildingChart || termsDisplayedPage >= Math.ceil(currentTotalTerms/maxTermsToDisplay) }"
class="last-page button"
aria-hidden="true">
»
</span>
</span>
</div>
</div>
<apexchart
v-if="!isFetchingData && !isBuildingChart && !isFetchingTaxonomyTerms && selectedTaxonomy && selectedTaxonomy.id"
height="380px"
:series="chartSeries"
:options="chartOptions" />
</div>
<div
v-if="taxonomyTermsLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(taxonomyTermsLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadTaxonomyTerms(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import { reportsChartMixin } from '../js/reports-mixin';
export default {
mixins: [ reportsChartMixin ],
data() {
return {
isFetchingTaxonomyTerms: false,
selectedTaxonomy: {},
maxTermsToDisplay: 56,
termsDisplayedPage: 1
}
},
computed: {
...mapGetters('report', {
taxonomiesList: 'getTaxonomiesList',
stackedBarChartOptions: 'getStackedBarChartOptions',
reportsLatestCachedOn: 'getReportsLatestCachedOn'
}),
taxonomiesListArray() {
return this.taxonomiesList && this.taxonomiesList != undefined ? Object.values(this.taxonomiesList) : [];
},
taxonomyTermsLatestCachedOn() {
return this.reportsLatestCachedOn['taxonomy-terms-' + this.selectedTaxonomy.id];
},
currentTotalTerms() {
return Array.isArray(this.chartData) ? this.chartData.length : 0
}
},
watch: {
taxonomiesListArray: {
handler() {
if (this.taxonomiesListArray && this.taxonomiesListArray.length)
this.selectedTaxonomy = this.taxonomiesListArray[0];
},
immediate: true
},
selectedTaxonomy: {
handler() {
this.termsDisplayedPage = 1;
if (this.selectedTaxonomy && this.selectedTaxonomy.id)
this.loadTaxonomyTerms();
},
immediate: true
},
termsDisplayedPage() {
this.buildTaxonomyTermsChart();
},
maxTermsToDisplay() {
this.termsDisplayedPage = 1;
this.buildTaxonomyTermsChart();
}
},
methods: {
...mapActions('report', [
'fetchTaxonomyTerms'
]),
buildTaxonomyTermsChart() {
this.isBuildingChart = true;
// Building Taxonomy term usage chart
let orderedTerms = JSON.parse(JSON.stringify(this.chartData)).sort((a, b) => b.count - a.count);
orderedTerms = orderedTerms.slice((this.termsDisplayedPage - 1) * this.maxTermsToDisplay, ((this.termsDisplayedPage - 1) * this.maxTermsToDisplay) + this.maxTermsToDisplay);
let termsValues = [];
let termsLabels = [];
orderedTerms.forEach(term => {
termsValues.push(term.count);
termsLabels.push(term.name);
});
this.chartSeries = [
{
name: this.$i18n.get('label_items_per_term'),
data: termsValues
}
];
this.chartOptions = {
...this.stackedBarChartOptions,
...{
title: {},
xaxis: {
type: 'category',
tickPlacement: 'on',
categories: termsLabels,
labels: {
show: true,
trim: true,
hideOverlappingLabels: false
},
tooltip: { enabled: true }
},
yaxis: {
title: {
text: this.$i18n.get('label_number_of_items')
}
},
animations: {
enabled: orderedTerms.length <= 40
},
colors: ['#01295c'],
}
}
setTimeout(() => this.isBuildingChart = false, 500);
},
loadTaxonomyTerms(force) {
this.isFetchingTaxonomyTerms = true;
this.fetchTaxonomyTerms({ taxonomyId: this.selectedTaxonomy.id, force: force })
.then(() => {
this.buildTaxonomyTermsChart();
this.isFetchingTaxonomyTerms = false;
})
.catch(() => this.isFetchingTaxonomyTerms = false);
}
}
}
</script>

View File

@ -0,0 +1,776 @@
<template>
<div v-if="metadataList != undefined">
<div
:class="{ 'skeleton': isFetchingData || isBuildingChart || isFetchingMetadatumTerms || !selectedMetadatum || !selectedMetadatum.id }"
class="postbox">
<div
:style="!isChildColumnCollapsed ? 'margin-left: 0px;' : ''"
:class="!isChildColumnCollapsed ? 'columns is-6' : ''">
<div :class="!isChildColumnCollapsed ? 'column is-half is-full-tablet' : ''">
<div class="box-header">
<div
v-if="selectedParentTerm.length <= 1"
class="box-header__item">
<label
v-if="!isFetchingData"
for="select_metadata_for_terms">
{{ $i18n.get('label_items_per_term_from_taxonomy_metadatum') }}&nbsp;
</label>
<select
v-if="!isFetchingData"
name="select_metadata_for_terms"
id="select_metadata_for_terms"
:placeholder="$i18n.get('label_select_a_taxonomy_metadatum')"
v-model="selectedMetadatum">
<option
v-for="(metadatum, index) of metadataListArray"
:key="index"
:value="metadatum">
{{ metadatum.name }}
</option>
</select>
<div class="graph-mode-switch">
<button
@click="itemsPerTermChartMode = 'bar'"
:class="{ 'current': itemsPerTermChartMode == 'bar' }">
<span class="screen-reader-text">
{{ $i18n.get('label_bar_chart') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-text tainacan-icon-rotate-270" />
</span>
</button>
<button
@click="itemsPerTermChartMode = 'treemap'"
:class="{ 'current': itemsPerTermChartMode == 'treemap' }">
<span class="screen-reader-text">
{{ $i18n.get('label_tree_map') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-viewmasonry tainacan-icon-rotate-270" />
</span>
</button>
</div>
</div>
<div
v-else
class="box-header__item"
style="display: flex; align-items: baseline;">
<button
class="button button-secondary"
@click="backToParentTerm">
{{ $i18n.get('label_parent_term') }}
</button>&nbsp;
<span
v-if="!isFetchingMetadatumChildTerms">
&nbsp;{{ $i18n.get('label_items_per_child_terms_of') }}&nbsp; <strong>{{ selectedParentTerm[selectedParentTerm.length - 2].label }}</strong>
</span>
</div>
<div
v-if="selectedMetadatum && selectedMetadatum.id && currentTotalTerms >= 56"
class="box-header__item">
<label for="max_terms">{{ $i18n.get('label_terms_per_page') }}</label>
<input
type="number"
step="1"
min="1"
max="999"
class="screen-per-page"
name="max_terms"
id="max_terms"
maxlength="3"
:disabled="isBuildingChart"
v-model.number="maxTermsToDisplay">
</div>
<div
v-if="selectedMetadatum && selectedMetadatum.id && currentTotalTerms >= 56"
class="box-header__item tablenav-pages">
<span class="displaying-num">{{ currentTotalTerms + ' ' + $i18n.get('terms') }}</span>
<span class="pagination-links">
<span
@click="!isBuildingChart ? termsDisplayedPage = 1 : null"
:class="{'tablenav-pages-navspan disabled' : termsDisplayedPage <= 1 || isBuildingChart}"
class="first-page button"
aria-hidden="true">
«
</span>
<span
@click="(termsDisplayedPage > 1 && !isBuildingChart) ? termsDisplayedPage-- : null"
:class="{'tablenav-pages-navspan disabled' : termsDisplayedPage <= 1 || isBuildingChart}"
class="prev-page button"
aria-hidden="true">
</span>
<span class="paging-input">
<label
for="current-page-selector"
class="screen-reader-text">
{{ $i18n.get('label_current_page') }}
</label>
<input
class="current-page"
id="current-page-selector"
type="number"
step="1"
min="1"
:disabled="isBuildingChart || maxTermsToDisplay >= currentTotalTerms"
:max="Math.ceil(currentTotalTerms/maxTermsToDisplay)"
name="paged"
v-model.number="termsDisplayedPage"
size="1"
aria-describedby="table-paging">
<span class="tablenav-paging-text"> {{ $i18n.get('info_of') }} <span class="total-pages">{{ Math.ceil(currentTotalTerms/maxTermsToDisplay) }}</span></span>
</span>
<span
@click="(!isBuildingChart && termsDisplayedPage < Math.ceil(currentTotalTerms/maxTermsToDisplay)) ? termsDisplayedPage++ : null"
:class="{'tablenav-pages-navspan disabled' : isBuildingChart || termsDisplayedPage >= Math.ceil(currentTotalTerms/maxTermsToDisplay) }"
aria-hidden="true"
class="next-page button">
</span>
<span
@click="!isBuildingChart ? termsDisplayedPage = Math.ceil(currentTotalTerms/maxTermsToDisplay) : null"
:class="{'tablenav-pages-navspan disabled': isBuildingChart || termsDisplayedPage >= Math.ceil(currentTotalTerms/maxTermsToDisplay) }"
class="last-page button"
aria-hidden="true">
»
</span>
</span>
</div>
</div>
<apexchart
v-if="!isFetchingData && !isBuildingChart && !isFetchingMetadatumTerms && selectedMetadatum && selectedMetadatum.id"
height="380px"
:series="chartSeries"
:options="chartOptions" />
<button
v-if=" !isFetchingData && !isFetchingMetadatumTerms && selectedMetadatum"
@click="isChildColumnCollapsed = !isChildColumnCollapsed"
class="button-secondary hide-column-button">
<span class="icon">
<i
:class="isChildColumnCollapsed ? 'tainacan-icon-arrowleft' : 'tainacan-icon-arrowright'"
class="tainacan-icon tainacan-icon-1-25em" />
</span>
</button>
</div>
<div
v-if="!isChildColumnCollapsed && !isFetchingData && !isFetchingMetadatumTerms && selectedMetadatum"
class="child-term-column column is-half is-full-tablet">
<div v-if="selectedParentTerm[selectedParentTerm.length - 1]">
<div class="box-header">
<div class="box-header__item">
<span
v-if="!isFetchingMetadatumChildTerms">
{{ $i18n.get('label_items_per_child_terms_of') }}&nbsp; <strong>{{ selectedParentTerm[selectedParentTerm.length - 1].label }}</strong>
</span>
</div>
<div
v-if="currentTotalChildTerms >= 56"
class="box-header__item">
<label for="max_terms">{{ $i18n.get('label_terms_per_page') }}</label>
<input
type="number"
step="1"
min="1"
max="999"
class="screen-per-page"
name="max_terms"
id="max_terms"
maxlength="3"
:disabled="isBuildingChildrenChart"
v-model.number="maxChildTermsToDisplay">
</div>
<div
v-if="currentTotalChildTerms >= 56"
class="box-header__item tablenav-pages">
<span class="displaying-num">{{ currentTotalChildTerms + ' ' + $i18n.get('terms') }}</span>
<span class="pagination-links">
<span
@click="!isBuildingChildrenChart ? childTermsDisplayedPage = 1 : null"
:class="{'tablenav-pages-navspan disabled' : childTermsDisplayedPage <= 1 || isBuildingChildrenChart}"
class="first-page button"
aria-hidden="true">
«
</span>
<span
@click="(childTermsDisplayedPage > 1 && !isBuildingChildrenChart) ? childTermsDisplayedPage-- : null"
:class="{'tablenav-pages-navspan disabled' : childTermsDisplayedPage <= 1 || isBuildingChildrenChart}"
class="prev-page button"
aria-hidden="true">
</span>
<span class="paging-input">
<label
for="current-page-selector"
class="screen-reader-text">
{{ $i18n.get('label_current_page') }}
</label>
<input
class="current-page"
id="current-page-selector"
type="number"
step="1"
min="1"
:disabled="isBuildingChildrenChart || maxChildTermsToDisplay >= currentTotalChildTerms"
:max="Math.ceil(currentTotalChildTerms/maxChildTermsToDisplay)"
name="paged"
v-model.number="childTermsDisplayedPage"
size="1"
aria-describedby="table-paging">
<span class="tablenav-paging-text"> {{ $i18n.get('info_of') }} <span class="total-pages">{{ Math.ceil(currentTotalChildTerms/maxChildTermsToDisplay) }}</span></span>
</span>
<span
@click="(!isBuildingChildrenChart && childTermsDisplayedPage < Math.ceil(currentTotalChildTerms/maxChildTermsToDisplay)) ? childTermsDisplayedPage++ : null"
:class="{'tablenav-pages-navspan disabled' : isBuildingChildrenChart || childTermsDisplayedPage >= Math.ceil(currentTotalChildTerms/maxChildTermsToDisplay) }"
aria-hidden="true"
class="next-page button">
</span>
<span
@click="!isBuildingChildrenChart ? childTermsDisplayedPage = Math.ceil(currentTotalChildTerms/maxChildTermsToDisplay) : null"
:class="{'tablenav-pages-navspan disabled': isBuildingChildrenChart || childTermsDisplayedPage >= Math.ceil(currentTotalChildTerms/maxChildTermsToDisplay) }"
class="last-page button"
aria-hidden="true">
»
</span>
</span>
</div>
</div>
<apexchart
v-if="!isBuildingChildrenChart && !isFetchingMetadatumChildTerms"
height="380px"
:series="childrenChartSeries"
:options="childrenChartOptions" />
</div>
<div
v-else
class="child-term-placeholder">
<p class="title is-4">
<span class="icon has-text-gray">
<i class="tainacan-icon tainacan-icon-taxonomies tainacan-icon-1-125em" />
</span>
&nbsp;{{ $i18n.get('label_children_terms') }}
</p>
<br>
<p class="subtitle is-6">{{ $i18n.get('info_child_terms_chart') }}</p>
</div>
</div>
</div>
</div>
<div
v-if="metadatumTermsLatestCachedOn"
style="left: calc(1px + 0.75rem); right: auto;"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(metadatumTermsLatestCachedOn).toLocaleString() }}</span>
<button @click="loadMetadatumTerms(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
<div
v-if="!isChildColumnCollapsed && !isFetchingData && !isFetchingMetadatumTerms && selectedMetadatum && metadatumChildTermsLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(metadatumChildTermsLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadMetadatumChildTerms(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</div>
</template>
<script>
import { mapActions, mapMutations, mapGetters } from 'vuex';
import { reportsChartMixin } from '../js/reports-mixin';
export default {
mixins: [ reportsChartMixin ],
props: {
collectionId: ''
},
data() {
return {
isFetchingMetadatumTerms: false,
selectedMetadatum: {},
maxTermsToDisplay: 56,
termsDisplayedPage: 1,
selectedParentTerm: [],
isFetchingMetadatumChildTerms: false,
isBuildingChildrenChart: false,
childrenChartSeries: [],
childrenChartOptions: {},
maxChildTermsToDisplay: 56,
childTermsDisplayedPage: 1,
isChildColumnCollapsed: false,
itemsPerTermChartMode: 'bar'
}
},
computed: {
...mapGetters('report', {
metadataList: 'getMetadataList',
taxonomyTerms: 'getTaxonomyTerms',
taxonomyChildTerms: 'getTaxonomyChildTerms',
stackedBarChartOptions: 'getStackedBarChartOptions',
treeMapChartOptions: 'getTreeMapChartOptions',
reportsLatestCachedOn: 'getReportsLatestCachedOn'
}),
metadataListArray() {
return this.metadataList && Array.isArray(this.metadataList) ? this.metadataList : [];
},
metadatumTermsLatestCachedOn() {
return this.reportsLatestCachedOn['taxonomy-terms-' + (this.collectionId ? this.collectionId : 'default') + '-' + this.selectedMetadatum.id + (this.selectedParentTerm.length > 2 && this.selectedParentTerm[this.selectedParentTerm.length - 2] && this.selectedParentTerm[this.selectedParentTerm.length - 2].id ? '-' + this.selectedParentTerm[this.selectedParentTerm.length - 1].id : '')];
},
metadatumChildTermsLatestCachedOn() {
return this.reportsLatestCachedOn['taxonomy-terms-' + (this.collectionId ? this.collectionId : 'default') + '-' + this.selectedMetadatum.id + (this.selectedParentTerm[this.selectedParentTerm.length - 1] && this.selectedParentTerm[this.selectedParentTerm.length - 1].id ? '-' + this.selectedParentTerm[this.selectedParentTerm.length - 1].id : '') + '-is-child-chart'];
},
currentTotalTerms() {
return Array.isArray(this.taxonomyTerms) ? this.taxonomyTerms.length : 0
},
currentTotalChildTerms() {
return Array.isArray(this.taxonomyChildTerms) ? this.taxonomyChildTerms.length : 0
}
},
watch: {
metadataListArray: {
handler() {
if (this.metadataListArray && this.metadataListArray.length)
this.selectedMetadatum = this.metadataListArray[0];
},
immediate: true
},
selectedMetadatum: {
handler() {
this.termsDisplayedPage = 1;
if (this.selectedMetadatum && this.selectedMetadatum.id) {
this.selectedParentTerm = [];
this.loadMetadatumTerms();
}
},
immediate: true
},
termsDisplayedPage() {
this.buildMetadatumTermsChart();
},
maxTermsToDisplay() {
this.termsDisplayedPage = 1;
this.buildMetadatumTermsChart();
},
selectedParentTerm() {
if (this.selectedParentTerm[this.selectedParentTerm.length - 1] && this.selectedParentTerm[this.selectedParentTerm.length - 1].id) {
this.loadMetadatumChildTerms();
}
},
itemsPerTermChartMode() {
this.termsDisplayedPage = 1;
this.loadMetadatumTerms();
},
},
methods: {
...mapActions('report', [
'fetchTaxonomyTerms'
]),
...mapMutations('report', [
'setTaxonomyTerms',
'setReportLatestCachedOn'
]),
...mapActions('metadata', [
'fetchMetadata'
]),
buildMetadatumTermsChart() {
this.isBuildingChart = true;
// Building Taxonomy term usage chart
let orderedTerms = JSON.parse(JSON.stringify(this.taxonomyTerms)).sort((a, b) => b.total_items - a.total_items );
orderedTerms = orderedTerms.slice((this.termsDisplayedPage - 1) * this.maxTermsToDisplay, ((this.termsDisplayedPage - 1) * this.maxTermsToDisplay) + this.maxTermsToDisplay);
if (this.itemsPerTermChartMode == 'treemap') {
this.chartSeries = [
{
name: this.$i18n.get('label_items_per_term'),
data: orderedTerms.map((aTerm) => { return {
x: aTerm.label,
y: aTerm.total_items
} })
}
];
this.chartOptions = {
...this.treeMapChartOptions,
...{
title: {},
chart: {
type: 'treemap',
height: 350,
toolbar: {
show: true
},
zoom: {
enabled: false
},
events: {
dataPointSelection: (event, chartContext, config) => {
if (config.dataPointIndex >= 0 && orderedTerms[config.dataPointIndex]) {
this.selectedParentTerm.push({
id: orderedTerms[config.dataPointIndex].value,
label: orderedTerms[config.dataPointIndex].label
})
}
}
},
},
dataLabels: {
enabled: true,
formatter: function(text, op) {
return [text, op.value]
},
offsetY: -4
},
tooltip: {
custom: ({ dataPointIndex }) => {
return `<div class="tainacan-custom-tooltip">
<div class="tainacan-custom-tooltip__header">` + orderedTerms[dataPointIndex].label + `</div>
<div class="tainacan-custom-tooltip__body">
<span>` + this.$i18n.get('label_items_per_term') + `: <strong>` + orderedTerms[dataPointIndex].total_items + `</strong></span>
`+ (orderedTerms[dataPointIndex].total_children
? (`<span>` + this.$i18n.getWithVariables(orderedTerms[dataPointIndex].total_children > 1 ? 'instruction_click_to_see_%s_child_terms' : 'instruction_click_to_see_%s_child_term', [ orderedTerms[dataPointIndex].total_children ]) + `</span>`)
: ``
) +
`</div></div>`;
}
},
noData: {
text: '0 ' + this.$i18n.get('label_items_with_this_metadum_value')
}
}
}
} else {
let termsValues = [];
let termsLabels = [];
orderedTerms.forEach(term => {
termsValues.push(term.total_items);
termsLabels.push(term.label);
});
this.chartSeries = [
{
name: this.$i18n.get('label_items_per_term'),
data: termsValues
}
];
this.chartOptions = {
...this.stackedBarChartOptions,
...{
title: {},
xaxis: {
type: 'category',
tickPlacement: 'on',
categories: termsLabels,
labels: {
show: true,
trim: true,
hideOverlappingLabels: false
},
tooltip: { enabled: true }
},
chart: {
type: 'bar',
height: 350,
stacked: true,
toolbar: {
show: true
},
zoom: {
enabled: true,
autoScaleYaxis: true,
},
events: {
dataPointSelection: (event, chartContext, config) => {
if (config.dataPointIndex >=0 && orderedTerms[config.dataPointIndex]) {
this.selectedParentTerm.push({
id: orderedTerms[config.dataPointIndex].value,
label: orderedTerms[config.dataPointIndex].label
})
}
}
},
},
tooltip: {
custom: ({ dataPointIndex }) => {
return `<div class="tainacan-custom-tooltip">
<div class="tainacan-custom-tooltip__header">` + orderedTerms[dataPointIndex].label + `</div>
<div class="tainacan-custom-tooltip__body">
<span>` + this.$i18n.get('label_items_per_term') + `: <strong>` + orderedTerms[dataPointIndex].total_items + `</strong></span>
`+ (orderedTerms[dataPointIndex].total_children
? (`<span>` + this.$i18n.getWithVariables(orderedTerms[dataPointIndex].total_children > 1 ? 'instruction_click_to_see_%s_child_terms' : 'instruction_click_to_see_%s_child_term', [ orderedTerms[dataPointIndex].total_children ]) + `</span>`)
: ``
) +
`</div></div>`;
}
},
yaxis: {
title: {
text: this.$i18n.get('label_number_of_items')
}
},
animations: {
enabled: orderedTerms.length <= 40
},
noData: {
text: '0 ' + this.$i18n.get('label_items_with_this_metadum_value')
}
}
}
}
setTimeout(() => this.isBuildingChart = false, 500);
},
buildMetadatumChildTermsChart() {
this.isBuildingChildrenChart = true;
// Building Taxonomy term usage chart
let orderedTerms = JSON.parse(JSON.stringify(this.taxonomyChildTerms)).sort((a, b) => b.total_items - a.total_items );
orderedTerms = orderedTerms.slice((this.termsDisplayedPage - 1) * this.maxTermsToDisplay, ((this.termsDisplayedPage - 1) * this.maxTermsToDisplay) + this.maxTermsToDisplay);
if (this.itemsPerTermChartMode == 'treemap') {
this.childrenChartSeries = [
{
name: this.$i18n.get('label_items_per_term'),
data: orderedTerms.map((aTerm) => { return {
x: aTerm.label,
y: aTerm.total_items
} })
}
];
this.childrenChartOptions = {
...this.treeMapChartOptions,
...{
title: {},
chart: {
type: 'treemap',
height: 350,
toolbar: {
show: true
},
zoom: {
enabled: false
},
events: {
dataPointSelection: (event, chartContext, config) => {
if (config.dataPointIndex >= 0 && orderedTerms[config.dataPointIndex]) {
const previousMetadatumChildTermsLatestCachedOn = this.metadatumChildTermsLatestCachedOn ? this.metadatumChildTermsLatestCachedOn.replace('-is-child-chart', '') : '';
this.selectedParentTerm.push({
id: orderedTerms[config.dataPointIndex].value,
label: orderedTerms[config.dataPointIndex].label
});
this.setTaxonomyTerms(this.taxonomyChildTerms);
this.setReportLatestCachedOn({
report: 'taxonomy-terms-' + (this.collectionId ? this.collectionId : 'default') + '-' + this.selectedMetadatum.id + (this.selectedParentTerm.length > 2 && this.selectedParentTerm[this.selectedParentTerm.length - 2] && this.selectedParentTerm[this.selectedParentTerm.length - 2].id ? '-' + this.selectedParentTerm[this.selectedParentTerm.length - 1].id : ''),
reportLatestCachedOn: previousMetadatumChildTermsLatestCachedOn
});
this.buildMetadatumTermsChart();
}
}
},
},
dataLabels: {
enabled: true,
formatter: function(text, op) {
return [text, op.value]
},
offsetY: -4
},
tooltip: {
custom: ({ dataPointIndex }) => {
return `<div class="tainacan-custom-tooltip">
<div class="tainacan-custom-tooltip__header">` + orderedTerms[dataPointIndex].label + `</div>
<div class="tainacan-custom-tooltip__body">
<span>` + this.$i18n.get('label_items_per_term') + `: <strong>` + orderedTerms[dataPointIndex].total_items + `</strong></span>
`+ (orderedTerms[dataPointIndex].total_children
? (`<span>` + this.$i18n.getWithVariables(orderedTerms[dataPointIndex].total_children > 1 ? 'instruction_click_to_see_%s_child_terms' : 'instruction_click_to_see_%s_child_term', [ orderedTerms[dataPointIndex].total_children ]) + `</span>`)
: ``
) +
`</div></div>`;
}
},
noData: {
text: '0 ' + this.$i18n.get('label_items_with_this_metadum_value')
}
}
}
} else {
let termsValues = [];
let termsLabels = [];
orderedTerms.forEach(term => {
termsValues.push(term.total_items);
termsLabels.push(term.label);
});
this.childrenChartSeries = [
{
name: this.$i18n.get('label_items_per_term'),
data: termsValues
}
];
this.childrenChartOptions = {
...this.stackedBarChartOptions,
...{
title: {},
xaxis: {
type: 'category',
tickPlacement: 'on',
categories: termsLabels,
labels: {
show: true,
trim: true,
hideOverlappingLabels: false
},
tooltip: { enabled: true }
},
chart: {
type: 'bar',
height: 350,
stacked: true,
toolbar: {
show: true
},
zoom: {
enabled: true,
autoScaleYaxis: true,
},
events: {
dataPointSelection: (event, chartContext, config) => {
if (config.dataPointIndex >= 0 && orderedTerms[config.dataPointIndex]) {
const previousMetadatumChildTermsLatestCachedOn = this.metadatumChildTermsLatestCachedOn ? this.metadatumChildTermsLatestCachedOn.replace('-is-child-chart', '') : '';
this.selectedParentTerm.push({
id: orderedTerms[config.dataPointIndex].value,
label: orderedTerms[config.dataPointIndex].label
});
this.setTaxonomyTerms(this.taxonomyChildTerms);
this.setReportLatestCachedOn({
report: 'taxonomy-terms-' + (this.collectionId ? this.collectionId : 'default') + '-' + this.selectedMetadatum.id + (this.selectedParentTerm.length > 2 && this.selectedParentTerm[this.selectedParentTerm.length - 2] && this.selectedParentTerm[this.selectedParentTerm.length - 2].id ? '-' + this.selectedParentTerm[this.selectedParentTerm.length - 1].id : ''),
reportLatestCachedOn: previousMetadatumChildTermsLatestCachedOn
});
this.buildMetadatumTermsChart();
}
}
},
},
yaxis: {
title: {
text: this.$i18n.get('label_number_of_items')
}
},
tooltip: {
custom: ({ dataPointIndex }) => {
return `<div class="tainacan-custom-tooltip">
<div class="tainacan-custom-tooltip__header">` + orderedTerms[dataPointIndex].label + `</div>
<div class="tainacan-custom-tooltip__body">
<span>` + this.$i18n.get('label_items_per_term') + `: <strong>` + orderedTerms[dataPointIndex].total_items + `</strong></span>
`+ (orderedTerms[dataPointIndex].total_children
? (`<span>` + this.$i18n.getWithVariables(orderedTerms[dataPointIndex].total_children > 1 ? 'instruction_click_to_see_%s_child_terms' : 'instruction_click_to_see_%s_child_term', [ orderedTerms[dataPointIndex].total_children ]) + `</span>`)
: ``
) +
`</div></div>`;
}
},
animations: {
enabled: orderedTerms.length <= 40
},
noData: {
text: this.$i18n.get('label_items_with_this_metadum_value')
}
}
}
}
setTimeout(() => this.isBuildingChildrenChart = false, 500);
},
loadMetadatumTerms(force) {
this.isFetchingMetadatumTerms = true;
this.fetchTaxonomyTerms({
taxonomyId: this.selectedMetadatum.id,
collectionId: this.collectionId,
parentTerm: this.selectedParentTerm.length > 1 ? this.selectedParentTerm[this.selectedParentTerm.length - 2].id : null,
force: force
})
.then(() => {
this.buildMetadatumTermsChart();
this.isFetchingMetadatumTerms = false;
})
.catch(() => this.isFetchingMetadatumTerms = false);
},
loadMetadatumChildTerms(force) {
this.isFetchingMetadatumChildTerms = true;
this.fetchTaxonomyTerms({
taxonomyId: this.selectedMetadatum.id,
collectionId: this.collectionId,
parentTerm: this.selectedParentTerm[this.selectedParentTerm.length - 1].id,
isChildChart: true,
force: force
})
.then(() => {
this.buildMetadatumChildTermsChart();
this.isFetchingMetadatumChildTerms = false;
})
.catch(() => this.isFetchingMetadatumChildTerms = false);
},
backToParentTerm() {
this.selectedParentTerm.pop();
this.loadMetadatumTerms();
}
}
}
</script>
<style lang="scss" scoped>
.child-term-column {
border-left: 1px dashed var(--tainacan-block-gray3, #cbcbcb);
&>* {
margin-left: 1.25rem;
}
}
.hide-column-button {
position: absolute;
right: 0;
top: calc(50% - 1rem);
margin: 0;
margin-right: -0.875rem;
padding: 0px;
border: 1px solid;
background-color: white;
z-index: 9;
}
.child-term-placeholder {
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
min-height: 380px;
flex-direction: column;
opacity: 0.75;
p {
color: var(--tainacan-block-gray4, #555758);
}
}
</style>

View File

@ -0,0 +1,147 @@
<template>
<div>
<div
v-if="!isFetchingData && chartData.totals && chartData.totals.metadata && !isBuildingChart"
:style="{
maxHeight: ((170 + (chartData.totals.metadata.total * 36)) <= 690 ? (170 + (chartData.totals.metadata.total * 36)) : 690) + 'px'
}"
class="postbox metadata-distribution-box">
<apexchart
:height="100 + (chartData.totals.metadata.total * 36)"
:series="chartSeries"
:options="chartOptions" />
</div>
<div
v-else
style="min-height=740px"
class="skeleton postbox metadata-distribution-box" />
<slot />
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { reportsChartMixin } from '../js/reports-mixin';
export default {
mixins: [ reportsChartMixin ],
computed: {
...mapGetters('report', {
horizontalBarChartOptions: 'getHorizontalBarChartOptions',
})
},
watch: {
chartData: {
handler() {
this.buildMetadataDistributionChart();
},
immediate: true,
}
},
methods: {
buildMetadataDistributionChart() {
this.isBuildingChart = true;
if (this.chartData.distribution) {
// Building Metadata Distribution Bar chart
const orderedMetadataDistributions = Object.values(this.chartData.distribution).sort((a, b) => b.fill_percentage - a.fill_percentage );
let metadataDistributionValues = [];
let metadataDistributionValuesInverted = [];
let metadataDistributionLabels = [];
const metadataCount = 100 + (this.chartData.totals.metadata.total * 36);
orderedMetadataDistributions.forEach(metadataDistribution => {
metadataDistributionValues.push(parseFloat(metadataDistribution.fill_percentage));
metadataDistributionValuesInverted.push(100.0000 - parseFloat(metadataDistribution.fill_percentage).toFixed(4));
metadataDistributionLabels.push(metadataDistribution.name);
})
// Sets first metadatum as the selected one
if (orderedMetadataDistributions.length)
this.selectedMetadatum = orderedMetadataDistributions[0].id;
this.chartSeries = [
{
name: this.$i18n.get('label_filled'),
data: metadataDistributionValues
},
{
name: this.$i18n.get('label_not_filled'),
data: metadataDistributionValuesInverted
}
];
this.chartOptions = {
...this.horizontalBarChartOptions,
...{
chart: {
type: 'bar',
height: metadataCount,
stacked: true,
stackType: '100%',
toolbar: {
show: true
},
zoom: {
type: 'y',
enabled: true,
autoScaleYaxis: true,
}
},
title: {
text: this.$i18n.get('label_metadata_fill_distribution')
},
labels: metadataDistributionLabels,
tooltip: {
y: {
formatter: (val) => val + "%"
}
},
yaxis: {
title: {
text: ''
},
labels: {
maxWidth: 100
},
tooltip: { enabled: true }
},
colors: ['#298596', '#dbdbdb'],
fill: {
colors: ['#298596', '#dbdbdb']
},
dataLabels: {
style: {
colors: ['#ffffff', '#454647']
}
},
states: {
normal: {
filter: {
type: 'none',
value: 0,
}
},
hover: {
filter: {
type: 'darken',
value: 0.85,
}
},
}
}
}
}
setTimeout(() => this.isBuildingChart = false, 300);
}
}
}
</script>
<style lang="scss" scoped>
.postbox.metadata-distribution-box {
margin: 0px 0px 0.75rem 1.5rem !important;
padding-bottom: 2rem;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,132 @@
<template>
<div>
<div
v-if="!isFetchingData && chartData && chartData.totals && chartData.totals.metadata_per_type && !isBuildingChart"
class="postbox">
<label>{{ $i18n.get('metadata_types') }}&nbsp;</label>
<div class="graph-mode-switch">
<button
@click="metadataTypeChartMode = 'bar'"
:class="{ 'current': metadataTypeChartMode == 'bar' }">
<span class="screen-reader-text">
{{ $i18n.get('label_bar_chart') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-text tainacan-icon-rotate-270" />
</span>
</button>
<button
@click="metadataTypeChartMode = 'circle'"
:class="{ 'current': metadataTypeChartMode == 'circle' }">
<span class="screen-reader-text">
{{ $i18n.get('label_pie_chart') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-waiting tainacan-icon-rotate-270" />
</span>
</button>
</div>
<apexchart
height="380px"
:series="chartSeries"
:options="chartOptions" />
</div>
<div
v-else
style="min-height=380px"
class="skeleton postbox" />
<slot />
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { reportsChartMixin } from '../js/reports-mixin';
export default {
mixins: [ reportsChartMixin ],
data() {
return {
metadataTypeChartMode: 'bar'
}
},
computed: {
...mapGetters('report', {
donutChartOptions: 'getDonutChartOptions',
stackedBarChartOptions: 'getStackedBarChartOptions',
})
},
watch: {
metadataTypeChartMode() {
this.buildMetadataTypeChart();
},
chartData: {
handler() {
if (this.chartData && this.chartData.totals)
this.buildMetadataTypeChart();
},
immediate: true
}
},
methods: {
buildMetadataTypeChart() {
this.isBuildingChart = true;
// Building Metadata Type Donut Chart
let metadataTypeValues = [];
let metadataTypeLabels = [];
const orderedMetadataPerType = Object.values(this.chartData.totals.metadata_per_type).sort((a, b) => b.count - a.count);
orderedMetadataPerType.forEach((metadataPerType) => {
metadataTypeValues.push(metadataPerType.count ? metadataPerType.count : 0);
metadataTypeLabels.push(metadataPerType.name ? metadataPerType.name : '');
});
this.chartSeries = this.metadataTypeChartMode == 'circle' ? metadataTypeValues : [ { name: this.$i18n.get('label_amount_of_metadata_of_type'), data: metadataTypeValues } ];
if (this.metadataTypeChartMode == 'circle') {
this.chartOptions = JSON.parse(JSON.stringify({
...this.donutChartOptions,
...{
title: {},
labels: metadataTypeLabels,
}
}));
} else {
this.chartOptions = JSON.parse(JSON.stringify({
...this.stackedBarChartOptions,
...{
chart: {
type: 'bar',
height: 350,
stacked: false,
toolbar: {
show: true
},
zoom: {
enabled: true,
autoScaleYaxis: true,
}
},
title: {},
xaxis: {
type: 'category',
tickPlacement: 'on',
categories: metadataTypeLabels,
labels: {
show: true,
trim: true,
hideOverlappingLabels: false
},
tooltip: { enabled: true }
}
}
}));
}
setTimeout(() => { this.isBuildingChart = false; }, 500);
},
}
}
</script>

View File

@ -0,0 +1,103 @@
<template>
<div class="number-block">
<p class="title is-2">
<i-count-up
:delay="750"
:end-val="total"
:options="{ separator: ' ' }" />
</p>
<p class="subtitle is-3">
<span class="icon has-text-gray">
<i
class="tainacan-icon tainacan-icon-1-125em"
:class="'tainacan-icon-' + entityType" />
</span>
&nbsp;{{ $i18n.get(entityType) }}
</p>
<p
v-if="summary.totals && summary.totals[entityType] && entityType == 'taxonomies'"
class="subtitle is-6">
{{ $i18n.get('label_used') + ': ' + summary.totals[entityType].used + ' | ' + $i18n.get('label_not_used') + ': ' + summary.totals[entityType].not_used }}
</p>
<ul class="has-text-gray status-list">
<li
v-for="(statusOption, index) of $statusHelper.getStatuses()"
:key="index"
v-if="(statusOption.slug != 'private' || (statusOption.slug == 'private' && $userCaps.hasCapability('tnc_rep_read_private_collections')) && totalByStatus[statusOption.slug])">
<span class="value">
<i-count-up
:delay="750"
:end-val="totalByStatus[statusOption.slug]"
:options="{ separator: ' ' }" />
&nbsp;
</span>
<span
v-if="$statusHelper.hasIcon(statusOption.slug)"
class="icon has-text-gray">
<i
class="tainacan-icon tainacan-icon-1-125em"
:class="$statusHelper.getIcon(statusOption.slug)"
/>
</span>
<!-- {{ statusOption.name }} -->
</li>
</ul>
</div>
</template>
<script>
import ICountUp from 'vue-countup-v2';
export default {
components: {
ICountUp
},
props: {
entityType: String,
summary: Object
},
computed: {
total() {
return this.summary && this.summary.totals && this.summary.totals[this.entityType] && this.summary.totals[this.entityType].total ? this.summary.totals[this.entityType].total : 0;
},
totalByStatus() {
return {
'publish': this.summary && this.summary.totals && this.summary.totals[this.entityType] && this.summary.totals[this.entityType].publish ? this.summary.totals[this.entityType].publish : 0,
'private': this.summary && this.summary.totals && this.summary.totals[this.entityType] && this.summary.totals[this.entityType].private ? this.summary.totals[this.entityType].private : 0,
'draft': this.summary && this.summary.totals && this.summary.totals[this.entityType] && this.summary.totals[this.entityType].draft ? this.summary.totals[this.entityType].draft : 0,
'trash': this.summary && this.summary.totals && this.summary.totals[this.entityType] && this.summary.totals[this.entityType].trash ? this.summary.totals[this.entityType].trash : 0
}
}
}
}
</script>
<style lang="scss" scoped>
.number-block {
min-height: 210px !important;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
.title {
margin-top: 0.25em;
}
.subtitle {
padding-left: 0;
padding-right: 0;
}
.subtitle.is-6 {
margin-bottom: 0px;
}
.status-list {
display: flex;
justify-content: center;
font-size: 0.875rem;
li {
margin: 0 1em;
}
}
}
</style>

View File

@ -0,0 +1,119 @@
<template>
<div v-if="chartData != undefined">
<apexchart
v-if="!isFetchingData && taxonomiesListArray && taxonomiesListArray.length && !isBuildingChart"
height="380px"
class="postbox"
:series="chartSeries"
:options="chartOptions" />
<div
v-else
style="min-height=380px"
class="skeleton postbox" />
<slot />
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { reportsChartMixin } from '../js/reports-mixin';
export default {
mixins: [ reportsChartMixin ],
computed: {
...mapGetters('report', {
stackedBarChartOptions: 'getStackedBarChartOptions',
}),
taxonomiesListArray() {
return this.chartData && this.chartData != undefined ? Object.values(this.chartData) : [];
},
},
watch: {
taxonomiesListArray: {
handler() {
this.buildTaxonomiesList();
},
immediate: true
}
},
methods: {
buildTaxonomiesList() {
// Building Taxonomy term usage chart
this.isBuildingChart = true;
const orderedTaxonomies = this.taxonomiesListArray.sort((a, b) => b.total_terms - a.total_terms);
let termsUsed = [];
let termsNotUsed = [];
let taxonomiesLabels = [];
orderedTaxonomies.forEach(taxonomy => {
termsUsed.push(taxonomy.total_terms_used);
termsNotUsed.push(taxonomy.total_terms_not_used);
taxonomiesLabels.push(taxonomy.name);
});
this.chartSeries = [
{
name: this.$i18n.get('label_terms_used'),
data: termsUsed
},
{
name: this.$i18n.get('label_terms_not_used'),
data: termsNotUsed
}
];
this.chartOptions = {
...this.stackedBarChartOptions,
...{
title: {
text: this.$i18n.get('label_usage_of_terms_per_taxonomy')
},
xaxis: {
type: 'category',
tickPlacement: 'on',
categories: taxonomiesLabels,
labels: {
show: true,
trim: true,
hideOverlappingLabels: false
},
tooltip: { enabled: true }
},
yaxis: {
title: {
text: this.$i18n.get('label_number_of_terms')
}
},
colors: ['#298596', '#dbdbdb'],
fill: {
colors: ['#298596', '#dbdbdb']
},
dataLabels: {
style: {
colors: ['#ffffff', '#454647']
}
},
states: {
normal: {
filter: {
type: 'none',
value: 0,
}
},
hover: {
filter: {
type: 'darken',
value: 0.9,
}
},
}
}
}
setTimeout(() => this.isBuildingChart = false, 500);
}
}
}
</script>

View File

@ -0,0 +1,104 @@
import Vue from 'vue';
import store from '../../admin/js/store/store';
import router from './reports-router';
import VTooltip from 'v-tooltip';
import { Snackbar, Modal } from 'buefy';
import VueApexCharts from 'vue-apexcharts';
import cssVars from 'css-vars-ponyfill';
import {
I18NPlugin,
UserCapabilitiesPlugin,
StatusHelperPlugin,
} from '../../admin/js/admin-utilities';
// Vue Dev Tools!
Vue.config.devtools = process && process.env && process.env.NODE_ENV === 'development';
import ReportsPage from '../reports.vue';
import NumberBlock from '../components/number-block.vue';
import ItemsPerTermBlock from '../components/items-per-term-block.vue';
import ItemsPerTermCollectionBlock from '../components/items-per-term-collection-block.vue';
import TermsPerTaxonomyBlock from '../components/terms-per-taxonomy-block.vue';
import MetadataTypesBlock from '../components/metadata-types-block.vue';
import MetadataDistributionBlock from '../components/metadata-distribution-block.vue';
import CollectionsListBlock from '../components/collections-list-block.vue';
import ActivitiesBlock from '../components/activities-block.vue';
import ActivitiesPerUserBlock from '../components/activities-per-user-block.vue';
Vue.use(VueApexCharts)
Apex.colors = [
'#298596', // Tainacan Turquoise
'#01295c', // Tainacan Blue
'#25a189', // Tainacan Green
'#e69810', // Tainacan Yellow
'#a23939', // Tainacan Red
'#592570', // Tainacan Purple
'#ed4f63', // Tainacan Pink
'#b46659', // Tainacan Brown
'#e5721c', // Tainacan Orange
'#04a5ff', // Tainacan Other Blue
'#454647' // Tainacan Dark Gray
];
Vue.use(I18NPlugin);
Vue.use(UserCapabilitiesPlugin);
Vue.use(StatusHelperPlugin);
Vue.use(VTooltip);
Vue.use(Snackbar);
Vue.use(Modal);
Vue.component('number-block', NumberBlock);
Vue.component('items-per-term-block', ItemsPerTermBlock);
Vue.component('items-per-term-collection-block', ItemsPerTermCollectionBlock);
Vue.component('terms-per-taxonomy-block', TermsPerTaxonomyBlock);
Vue.component('metadata-types-block', MetadataTypesBlock);
Vue.component('metadata-distribution-block', MetadataDistributionBlock);
Vue.component('collections-list-block', CollectionsListBlock);
Vue.component('activities-block', ActivitiesBlock);
Vue.component('activities-per-user-block', ActivitiesPerUserBlock);
Vue.component('apexchart', VueApexCharts);
// Changing title of pages
router.beforeEach((to, from, next) => {
document.title = to.meta.title;
if (next() != undefined)
next();
});
new Vue({
el: '#tainacan-reports-app',
store,
router,
render: h => h(ReportsPage)
});
listen("load", window, function() {
var iconsStyle = document.createElement("style");
iconsStyle.setAttribute('type', 'text/css');
iconsStyle.innerText = '.tainacan-icon::before{ opacity: 1.0 !important; }';
document.head.appendChild(iconsStyle);
});
// Initialize Ponyfill for Custom CSS properties
cssVars({
// Options...
});
// Display Icons only once everything is loaded
function listen(evnt, elem, func) {
if (elem.addEventListener) // W3C DOM
elem.addEventListener(evnt,func,false);
else if (elem.attachEvent) { // IE DOM
var r = elem.attachEvent("on"+evnt, func);
return r;
} else if (document.head) {
var iconHideStyle = document.createElement("style");
iconHideStyle.innerText = '.tainacan-icon::before{ opacity: 0.0 !important; }';
document.head.appendChild(iconHideStyle);
} else {
var iconHideStyle = document.createElement("style");
iconHideStyle.innerText = '.tainacan-icon::before{ opacity: 0.0 !important; }';
document.getElementsByTagName("head")[0].appendChild(iconHideStyle);
}
}

View File

@ -0,0 +1,13 @@
export const reportsChartMixin = {
props: {
chartData: {},
isFetchingData: false
},
data () {
return {
isBuildingChart: false,
chartSeries: [],
chartOptions: {}
}
}
};

View File

@ -0,0 +1,29 @@
import Vue from 'vue';
import VueRouter from 'vue-router'
import qs from 'qs';
import ReportsList from '../pages/reports-list.vue';
const { __ } = wp.i18n;
Vue.use(VueRouter);
const routes = [
{ path: '/', redirect:'/reports' },
{ path: '/reports', name: 'ReportsList', component: ReportsList, meta: { title: __('Tainacan Reports') } },
{ path: '*', redirect: '/'}
];
export default new VueRouter ({
routes,
// set custom query resolver
parseQuery(query) {
return qs.parse(query);
},
stringifyQuery(query) {
let result = qs.stringify(query);
return result ? ('?' + result) : '';
}
});

View File

@ -0,0 +1,389 @@
<template>
<div>
<h1 class="wp-heading-inline">{{ $route.meta.title }}</h1>
<select
name="select_collections"
id="select_collections"
@input="(inputEvent) => $router.push({ query: { collection: inputEvent.target.value } })"
:value="selectedCollection">
<option value="default">
{{ $i18n.get('repository') }}
</option>
<option
v-for="(collection, index) of collections"
:key="index"
:value="collection.id">
{{ collection.name }}
</option>
</select>
<div class="columns is-multiline">
<div
:class="{ 'is-three-fifths-desktop': !isRepositoryLevel }"
style="margin-bottom: 0px;"
class="column is-full columns is-multiline">
<div
v-if="isRepositoryLevel"
class="column is-full is-one-third-tablet has-text-centered">
<number-block
:class="{ 'skeleton': isFetchingSummary }"
class="postbox"
:summary="summary"
entity-type="collections" />
<div
v-if="summaryLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(summaryLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadSummary(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</div>
<div
:class="{ 'is-one-third-tablet': isRepositoryLevel }"
class="column is-full is-half-tablet has-text-centered">
<number-block
:class="{ 'skeleton': isFetchingSummary }"
class="postbox"
:summary="summary"
entity-type="items"/>
<div
v-if="summaryLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(summaryLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadSummary(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</div>
<div
v-if="isRepositoryLevel"
class="column is-full is-one-third-tablet has-text-centered">
<number-block
:class="{ 'skeleton': isFetchingSummary }"
class="postbox"
:summary="summary"
entity-type="taxonomies" />
<div
v-if="summaryLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(summaryLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadSummary(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</div>
<div
v-else
class="column is-full is-half-tablet has-text-centered">
<number-block
:class="{ 'skeleton': isFetchingMetadata }"
class="postbox"
:summary="metadata"
entity-type="metadata" />
<div
v-if="summaryLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(summaryLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadSummary(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</div>
<collections-list-block
class="column is-full"
:chart-data="collectionsList"
:is-fetching-data="isFetchingCollectionsList"
v-if="isRepositoryLevel">
<div
v-if="collectionsLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(collectionsLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadCollectionsList(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</collections-list-block>
<metadata-types-block
class="column is-full"
:chart-data="metadata"
:is-fetching-data="isFetchingMetadata"
v-if="!isRepositoryLevel">
<div
v-if="metadataLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(metadataLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadMetadata(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</metadata-types-block>
<terms-per-taxonomy-block
class="column is-full"
:chart-data="taxonomyList"
:is-fetching-data="isFetchingTaxonomiesList"
v-if="isRepositoryLevel">
<div
v-if="taxonomiesLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(taxonomiesLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadTaxonomiesList(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</terms-per-taxonomy-block>
</div>
<metadata-distribution-block
class="column is-full is-two-fifths-desktop"
:chart-data="metadata"
:is-fetching-data="isFetchingMetadata"
v-if="!isRepositoryLevel">
<div
v-if="metadataLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(metadataLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadMetadata(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</metadata-distribution-block>
<items-per-term-block
v-if="isRepositoryLevel"
class="column is-full"
:chart-data="taxonomyTerms"
:is-fetching-data="isFetchingTaxonomiesList" />
<items-per-term-collection-block
v-else
class="column is-full"
:chart-data="taxonomyTerms"
:is-fetching-data="isFetchingMetadataList"
:collection-id="selectedCollection" />
<activities-per-user-block
class="column is-full is-two-fifths-tablet"
:chart-data="activities"
:is-fetching-data="isFetchingActivities">
<div
v-if="activitiesLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(activitiesLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadActivities(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</activities-per-user-block>
<activities-block
class="column is-full is-three-fifths-tablet"
:chart-data="activities"
:is-fetching-data="isFetchingActivities"
@time-range-update="loadActivities">
<div
v-if="activitiesLatestCachedOn"
class="box-last-cached-on">
<span>{{ $i18n.get('label_report_generated_on') + ': ' + new Date(activitiesLatestCachedOn).toLocaleString() }}</span>
<button
@click="loadActivities(true)">
<span class="screen-reader-text">
{{ $i18n.get('label_get_latest_report') }}
</span>
<span class="icon">
<i class="tainacan-icon tainacan-icon-1-25em tainacan-icon-updating tainacan-icon-rotate-270" />
</span>
</button>
</div>
</activities-block>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
export default {
name: "ReportsList",
data() {
return {
selectedCollection: 'default',
isFetchingCollections: false,
isFetchingSummary: false,
isFetchingCollectionsList: false,
isFetchingMetadata: false,
isFetchingActivities: false,
isFetchingTaxonomiesList: false,
activitiesStartDate: ''
}
},
computed: {
...mapGetters('collection', {
collections: 'getCollections',
}),
...mapGetters('report', {
summary: 'getSummary',
metadata: 'getMetadata',
collectionsList: 'getCollectionsList',
metadataList: 'getMetadataList',
taxonomyTerms: 'getTaxonomyTerms',
activities: 'getActivities',
taxonomyList: 'getTaxonomiesList',
reportsLatestCachedOn: 'getReportsLatestCachedOn'
}),
isRepositoryLevel() {
return !this.selectedCollection || this.selectedCollection == 'default';
},
summaryLatestCachedOn() {
return this.reportsLatestCachedOn['summary-' + (this.collectionId ? this.collectionId : 'default')];
},
metadataLatestCachedOn() {
return this.reportsLatestCachedOn['metadata-' + (this.collectionId ? this.collectionId : 'default')];
},
collectionsLatestCachedOn() {
return this.reportsLatestCachedOn['collections'];
},
taxonomiesLatestCachedOn() {
return this.reportsLatestCachedOn['taxonomies'];
},
activitiesLatestCachedOn() {
return this.reportsLatestCachedOn['activities-' + (this.collectionId ? this.collectionId : 'default') + (this.activitiesStartDate ? '-' + this.activitiesStartDate : '')];
}
},
watch: {
'$route.query': {
handler(to) {
this.selectedCollection = to['collection'] ? to['collection'] : 'default';
this.loadSummary();
this.loadMetadata();
this.loadActivities();
if (this.isRepositoryLevel) {
this.loadCollectionsList();
this.loadTaxonomiesList();
} else {
this.loadMetadataList();
}
},
immediate: true
}
},
created() {
// Obtains colleciton id from query, if passed
this.selectedCollection = this.$route.query['collection'] ? this.$route.query['collection'] : 'default';
// Loads collection for the select input
this.loadCollections();
},
methods: {
...mapActions('collection', [
'fetchAllCollectionNames'
]),
...mapActions('report', [
'fetchSummary',
'fetchCollectionsList',
'fetchMetadata',
'fetchMetadataList',
'fetchTaxonomiesList',
'fetchActivities'
]),
loadCollections() {
this.isFetchingCollections = true;
this.fetchAllCollectionNames()
.then(() => this.isFetchingCollections = false)
.catch(() => this.isFetchingCollections = false);
},
loadSummary(force) {
this.isFetchingSummary = true;
this.fetchSummary({ collectionId: this.selectedCollection, force: force })
.then(() => this.isFetchingSummary = false)
.catch(() => this.isFetchingSummary = false);
},
loadMetadata(force) {
this.isFetchingMetadata = true;
this.fetchMetadata({ collectionId: this.selectedCollection, force: force })
.then(() => this.isFetchingMetadata = false)
.catch(() => this.isFetchingMetadata = false);
},
loadCollectionsList(force) {
this.isFetchingCollectionsList = true;
this.fetchCollectionsList(force)
.then(() => this.isFetchingCollectionsList = false)
.catch(() => this.isFetchingCollectionsList = false);
},
loadTaxonomiesList(force) {
this.isFetchingTaxonomiesList = true;
this.fetchTaxonomiesList(force)
.then(() => this.isFetchingTaxonomiesList = false)
.catch(() => this.isFetchingTaxonomiesList = false);
},
loadMetadataList() {
this.isFetchingMetadataList = true;
this.fetchMetadataList({ collectionId: this.selectedCollection, onlyTaxonomies: true })
.then(() => this.isFetchingMetadataList = false)
.catch(() => this.isFetchingMetadataList = false);
},
loadActivities(startDate) {
this.isFetchingActivities = true;
this.fetchActivities({ collectionId: this.selectedCollection, startDate: startDate })
.then(() => this.isFetchingActivities = false)
.catch(() => this.isFetchingActivities = false);
this.activitiesStartDate = startDate;
}
}
}
</script>

View File

@ -0,0 +1,27 @@
<template>
<div
id="tainacan-reports-app"
class="wrap">
<router-view />
</div>
</template>
<script>
export default {
name: "ReportsPage",
}
</script>
<style lang="scss">
// TAINACAN Variables
@import "../admin/scss/_animations.scss";
@import "../admin/scss/_variables.scss";
// Bulma imports
@import "./scss/reports-basics.sass";
.tainacan_page_tainacan_reports #wpbody {
overflow-x: hidden;
}
</style>

View File

@ -0,0 +1,14 @@
@import "../../../../node_modules/bulma/sass/utilities/_all.sass"
@import "../../../../node_modules/bulma/sass/helpers/_all.sass"
@import "../../../../node_modules/bulma/sass/form/_all.sass"
@import "../../../../node_modules/bulma/sass/grid/_all.sass"
@import "../../../../node_modules/bulma/sass/components/pagination.sass"
@import "../../../../node_modules/bulma/sass/components/card.sass"
@import "../../../../node_modules/bulma/sass/elements/icon.sass"
@import "../../../../node_modules/bulma/sass/elements/tag.sass"
@import "../../../../node_modules/bulma/sass/elements/title.sass"
@import "../../../../node_modules/bulma/sass/elements/notification.sass"
@import "../../../../node_modules/bulma/sass/components/tabs.sass"
@import "../../../../node_modules/bulma/sass/elements/button.sass"
@import "../../../../node_modules/bulma/sass/components/dropdown.sass"
@import "../../../../node_modules/bulma/sass/components/modal.sass"

View File

@ -0,0 +1,135 @@
#tainacan-reports-app {
padding: 10px 2px 10px 2px;
margin: 0;
// Tainacan Loading
.tainacan-icon::before {
opacity: 0.0; // Will make it 1 once window.load is done;
}
a:hover {
cursor: pointer;
}
.wp-heading-inline {
margin-bottom: 2rem;
}
.columns {
max-width: 100%;
align-items: flex-start;
justify-content: center;
.column {
max-width: 100%;
padding: 0;
position: relative;
box-sizing: content-box;
.postbox {
margin: 0.75rem;
}
.box-last-cached-on {
position: absolute;
bottom: calc(1px + 0.75rem);
right: calc(1px + 0.75rem);
display: inline-block;
padding: 0px 10px;
background-color: var(--tainacan-block-gray2, #dbdbdb);
color: var(--tainacan-block-gray4, #555758);
font-size: 0.875em;
border-top-left-radius: 3px;
opacity: 0.0;
transition: opacity 0.3s ease;
button {
border: none;
background: none;
cursor: pointer;
&:hover {
color: var(--wp-admin-theme-color, #007cba);
}
}
}
.postbox:hover+.box-last-cached-on,
.postbox:hover+.box-last-cached-on+.box-last-cached-on,
.box-last-cached-on:hover {
opacity: 1.0;
}
}
}
.postbox {
padding: 1.125rem 1.25rem;
margin-bottom: 0;
height: 100%;
min-height: 380px;
background-color: var(--tainacan-block-gray0, #f6f6f6);
label {
font-weight: bold;
font-size: 0.875rem;
}
.box-header {
display: flex;
align-items: baseline;
justify-content: space-between;
flex-wrap: wrap;
.box-header__item {
margin-bottom: 10px;
line-height: 2rem;
}
}
}
.graph-mode-switch {
display: inline-block;
button {
border: none !important;
background: none !important;
box-shadow: none !important;
padding: 0;
cursor: pointer;
&.current {
color: var(--wp-admin-theme-color, #007cba);
}
}
}
.tainacan-custom-tooltip {
padding: 0;
display: flex;
justify-content: center;
align-items: flex-start;
flex-direction: column;
.tainacan-custom-tooltip__header {
background-color: var(--tainacan-block-gray1, #f2f2f2);
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
padding: 6px 10px 4px 10px;
}
.tainacan-custom-tooltip__header+.tainacan-custom-tooltip__body {
padding: 4px 10px 6px 10px;
}
.tainacan-custom-tooltip__body {
width: 100%;
padding: 6px 10px;
display: flex;
justify-content: center;
align-items: flex-start;
flex-direction: column;
p {
margin-bottom: 4px;
font-size: 0.85rem;
}
}
}
}

View File

@ -22,6 +22,7 @@ return apply_filters( 'tainacan-admin-i18n', [
'items' => __( 'Items', 'tainacan' ),
/* translators: Plural, a list of metadata */
'metadata' => __( 'Metadata', 'tainacan' ),
'metadata_types' => __( 'Metadata types', 'tainacan' ),
'filters' => __( 'Filters', 'tainacan' ),
'taxonomies' => __( 'Taxonomies', 'tainacan' ),
'activities' => __( 'Activities', 'tainacan' ),
@ -194,6 +195,7 @@ return apply_filters( 'tainacan-admin-i18n', [
'label_button_delete_thumb' => __( 'Button Delete Thumbnail', 'tainacan' ),
'label_collections_per_page' => __( 'Collections per Page:', 'tainacan' ),
'label_taxonomies_per_page' => __( 'Taxonomies per Page:', 'tainacan' ),
'label_terms_per_page' => __( 'Terms per Page:', 'tainacan' ),
'label_activities_per_page' => __( 'Activities per Page:', 'tainacan' ),
'label_items_per_page' => __( 'Items per Page:', 'tainacan' ),
'label_attachments_per_page' => __( 'Attachments per Page:', 'tainacan' ),
@ -536,10 +538,42 @@ return apply_filters( 'tainacan-admin-i18n', [
'label_switch_document_type' => __( 'Switch document type', 'tainacan' ),
'label_sending_form' => __( 'Sending form...', 'tainacan' ),
'label_form_not_loaded' => __( 'This form could not be loaded', 'tainacan' ),
'label_terms_not_used' => __( 'Terms not used', 'tainacan' ),
'label_terms_used' => __( 'Terms used', 'tainacan' ),
'label_number_of_terms' => __( 'Number of terms', 'tainacan' ),
'label_number_of_items' => __( 'Number of items', 'tainacan' ),
'label_usage_of_terms_per_taxonomy' => __( 'Usage of terms per taxonomy', 'tainacan' ),
'label_items_per_term' => __( 'Items per term', 'tainacan' ),
'label_items_per_term_from_taxonomy' => __( 'Items per term from taxonomy:', 'tainacan' ),
'label_items_per_term_from_taxonomy_metadatum' => __( 'Items per term from taxonomy metadatum:', 'tainacan' ),
'label_items_per_child_terms_of' => __( 'Items per child terms of:', 'tainacan' ),
'label_items_per_collection' => __( 'Items per collection', 'tainacan' ),
'label_loading_report' => __( 'Loading report...', 'tainacan' ),
'label_metadata_fill_distribution' => __( 'Metadata fill distribution', 'tainacan' ),
'label_not_filled' => __( 'Not filled yet', 'tainacan' ),
'label_filled' => __( 'Already filled', 'tainacan' ),
/* translators: To be displayed with the number of Taxonomies not used in the colllection */
'label_not_used' => __( 'Not used', 'tainacan' ),
/* translators: To be displayed with the number of Taxonomies used in the colllection */
'label_select_a_taxonomy' => __( 'Select a taxonomy', 'tainacan' ),
'label_used' => __( 'Used', 'tainacan' ),
'label_select_a_taxonomy_metadatum' => __( 'Select a taxonomy metadatum', 'tainacan' ),
'label_items_with_this_metadum_value' => __( 'Items with this metadatum value', 'tainacan' ),
'label_amount_of_items_per_metadatum_value' => __( 'Amount of items per metadatum value', 'tainacan' ),
'label_activities_during_year' => __( 'Activities during the year', 'tainacan' ),
'label_compact_list' => __( 'Compact list', 'tainacan'),
'label_detailed_list' => __( 'Detailed list', 'tainacan'),
'label_view_metadata_details' => __( 'View metadata details', 'tainacan'),
'label_filter_by_metadata_type' => __( 'Filter by metadatum type', 'tainacan'),
'label_pie_chart' => __( 'Pie chart', 'tainacan'),
'label_bar_chart' => __( 'Bar chart', 'tainacan'),
'label_terms_per_page' => __( 'Terms per page', 'tainacan'),
'label_anonymous_user' => __( 'Anonymous User', 'tainacan'),
'label_select_a_year' => __( 'Select a year', 'tainacan'),
'label_all_users' => __( 'All users', 'tainacan'),
'label_activitiy_per_user' => __( 'Activities per user', 'tainacan'),
'label_report_generated_on' => __( 'Report generated on', 'tainacan'),
'label_get_latest_report' => __( 'Get latest report', 'tainacan'),
'label_decrease' => __( 'Decrease', 'tainacan'),
'label_increase' => __( 'Increase', 'tainacan'),
@ -602,7 +636,9 @@ return apply_filters( 'tainacan-admin-i18n', [
'instruction_click_error_to_go_to_metadata' => __( 'Click on the error to go to the metadata:', 'tainacan'),
'instruction_click_to_see_or_search' => __( 'Click to see options or type to search...', 'tainacan'),
'instruction_select_one_or_more_collections' => __( 'Select one or more collections', 'tainacan'),
'instruction_thumbnail_alt' => __( 'Type here a descriptive text for the image thumbnail...', 'tainacan'),
'instruction_thumbnail_alt' => __( 'Type here a descriptive text for the image thumbnail...', 'tainacan'),
'instruction_click_to_see_%s_child_terms' => __( 'Click to see %s child terms', 'tainacan'),
'instruction_click_to_see_%s_child_term' => __( 'Click to see %s child term', 'tainacan'),
// Info. Other feedback to user.
'info_items_tab_all' => __( 'Every published item, including those visible only to editors.', 'tainacan' ),
@ -708,7 +744,7 @@ return apply_filters( 'tainacan-admin-i18n', [
'info_warning_taxonomy_not_saved' => __( 'Are you sure? The taxonomy is not saved, changes will be lost.', 'tainacan' ),
'info_warning_terms_not_saved' => __( 'Are you sure? There are terms not saved, changes will be lost.', 'tainacan' ),
'info_warning_orphan_terms' => __( 'Are you sure? This term is parent of other terms. These will be converted to root terms.', 'tainacan' ),
'info_no_activities' => __( 'No activities yet.', 'tainacan' ),
'info_no_activities' => __( 'No activities found.', 'tainacan' ),
'info_logs_before' => __( 'Before', 'tainacan' ),
'info_logs_after' => __( 'After', 'tainacan' ),
'info_there_is_no_metadatum' => __( 'There is no metadata here yet.', 'tainacan' ),
@ -821,6 +857,23 @@ return apply_filters( 'tainacan-admin-i18n', [
'info_recaptcha_link_%s' => __( 'Remember to configure your website reCAPTCHA keys on <a href="%s" target="_blank">the item submission repository page</a>.', 'tainacan'),
'info_form_not_loaded' => __( 'There are probably not enought permissions to display it here.', 'tainacan'),
'info_validating_slug' => __( 'Validating slug...', 'tainacan'),
'label_amount_of_metadata_of_type' => __( 'Amount of metadata of this type', 'tainacan'),
'info_child_terms_chart' => __( 'Click on the term bar on the chart aside to see its child terms (if any) in this panel', 'tainacan' ),
/* Activity actions */
'action_update-metadata-value' => __( 'Item Metadata Value Updates', 'tainacan'),
'action_update' => __( 'General Updates', 'tainacan'),
'action_create' => __( 'General Creations', 'tainacan'),
'action_update-metadata-order' => __( 'Metadata Order Updates', 'tainacan'),
'action_trash' => __( 'Trashing', 'tainacan'),
'action_new-attachment' => __( 'Addition of Attachments', 'tainacan'),
'action_update-filters-order' => __( 'Filter Order Updates', 'tainacan'),
'action_update-document' => __( 'Document Updates', 'tainacan'),
'action_delete' => __( 'General Deletions', 'tainacan'),
'action_delete-attachment' => __( 'Deletion of Attachments', 'tainacan'),
'action_update-thumbnail' => __( 'Thumbnail Updates', 'tainacan'),
'action_others' => __( 'Other Actions', 'tainacan'),
'info_applied_filters' => __( 'filters applied', 'tainacan'),
'info_items_found' => __( 'items found', 'tainacan'),
'info_applied_filter' => __( 'filter applied', 'tainacan'),

View File

@ -8,6 +8,7 @@ module.exports = {
theme_search: './src/views/theme-search/js/theme-main.js',
item_submission: './src/views/item-submission/js/item-submission-main.js',
roles: './src/views/roles/js/roles-main.js',
reports: './src/views/reports/js/reports-main.js',
block_terms_list: './src/views/gutenberg-blocks/tainacan-terms/terms-list/index.js',