Creates first version of dates intersection filter. #887.

This commit is contained in:
mateuswetah 2024-06-03 11:50:50 -03:00
parent 2f127030ed
commit 685b61b0b1
7 changed files with 410 additions and 1 deletions

View File

@ -252,13 +252,15 @@ import { formHooks } from "../../js/mixins";
import FormFilterNumeric from '../filter-types/numeric/FormNumeric.vue';
import FormFilterNumericInterval from '../filter-types/numeric-interval/FormNumericInterval.vue';
import FormFilterNumericListInterval from '../filter-types/numeric-list-interval/FormNumericListInterval.vue';
import FormFilterDatesIntersection from '../filter-types/dates-intersection/FormDatesIntersection.vue';
export default {
name: 'FilterEditionForm',
components: {
'tainacan-filter-form-numeric': FormFilterNumeric,
'tainacan-filter-form-numeric-interval': FormFilterNumericInterval,
'tainacan-filter-form-numeric-list-interval': FormFilterNumericListInterval
'tainacan-filter-form-numeric-list-interval': FormFilterNumericListInterval,
'tainacan-filter-form-dates-intersection': FormFilterDatesIntersection
},
mixins: [ formHooks ],
props: {

View File

@ -0,0 +1,98 @@
<template>
<div>
<b-field
:addons="false"
:type="metadataType"
:message="metadataMessage">
<label class="label is-inline">
{{ $i18n.getHelperTitle('tainacan-filter-dates-intersection', 'secondary_filter_metadatum_id') }}<span :class="metadataType">&nbsp;*&nbsp;</span>
<help-button
:title="$i18n.getHelperTitle('tainacan-filter-dates-intersection', 'secondary_filter_metadatum_id')"
:message="$i18n.getHelperMessage('tainacan-filter-dates-intersection', 'secondary_filter_metadatum_id')" />
</label>
<b-select
v-model="secondDateMetadatumId"
name="dates_intersect[secondary_filter_metadatum_id]"
:placeholder="$i18n.get('instruction_select_second_date_to_compare' )"
:loading="loading"
expanded
@change="onUpdateSecondDateMetadatumId()"
@focus="clear()">
<option :value="''">
{{ $i18n.get('instruction_select_second_date_to_compare' ) }}
</option>
<option
v-for="option in metadata.filter(aMetadatum => aMetadatum.id != filter.metadatumId )"
:key="option.id"
:value="option.id">
{{ option.name }}
</option>
</b-select>
</b-field>
</div>
</template>
<script>
import { tainacanApi } from '../../../js/axios';
export default {
props: {
filter: Object,
modelValue: Object,
errors: Object
},
emits: [
'update:model-value',
],
data() {
return {
metadata: [],
loading: true,
metadataType: '',
metadataMessage: '',
secondDateMetadatumId: [Number, String]
}
},
watch: {
errors(){
if ( this.errors && this.errors.secondary_filter_metadatum_id !== '' )
this.setErrorsAttributes( 'is-danger', this.errors.secondary_filter_metadatum_id );
else
this.setErrorsAttributes( '', '' );
}
},
created() {
this.secondDateMetadatumId = this.modelValue && this.modelValue.secondary_filter_metadatum_id ? this.modelValue.secondary_filter_metadatum_id : '';
this.loading = true;
this.fetchMetadata();
console.log(this.filter)
},
methods: {
async fetchMetadata() {
let endpoint = this.filter.collectionId && this.filter.collectionId !== 'default' ? ( '/collections/' + this.filter.collectionId + '/metadata' ) : '/metadata';
return await tainacanApi.get(endpoint)
.then(res => {
this.loading = false;
this.metadata = res.data ? res.data : [];
})
.catch(error => {
this.loading = false;
this.$console.log(error);
});
},
onUpdateSecondDateMetadatumId() {
const selectedMetadatum = this.metadata.find( aMetadatum => aMetadatum.id == this.secondDateMetadatumId );
const selectedMetadatumName = selectedMetadatum ? selectedMetadatum.name : '';
this.$emit('update:model-value', { secondary_filter_metadatum_id: this.secondDateMetadatumId, secondary_filter_metadatum_name: selectedMetadatumName});
},
setErrorsAttributes( type, message ) {
this.metadataType = type;
this.metadataMessage = message;
},
clear(){
this.metadataType = '';
this.metadataMessage = '';
},
}
}
</script>

View File

@ -0,0 +1,187 @@
<template>
<div>
<b-datepicker
v-model="dateInit"
:aria-labelledby="'filter-label-id-' + filter.id"
:placeholder="$i18n.get('label_selectbox_init')"
editable
:trap-focus="false"
:date-formatter="(date) => dateFormatter(date)"
:date-parser="(date) => dateParser(date)"
icon="calendar-today"
:years-range="[-200, 100]"
:day-names="[
$i18n.get('datepicker_short_sunday'),
$i18n.get('datepicker_short_monday'),
$i18n.get('datepicker_short_tuesday'),
$i18n.get('datepicker_short_wednesday'),
$i18n.get('datepicker_short_thursday'),
$i18n.get('datepicker_short_friday'),
$i18n.get('datepicker_short_saturday'),
]"
@focus="isTouched = true"
@update:model-value="($event) => { resetPage(); validadeValues($event) }" />
<p
style="font-size: 0.75em; margin-bottom: 0.125em;"
class="has-text-centered is-marginless">
{{ $i18n.get('label_until') }}
</p>
<b-datepicker
v-model="dateEnd"
:aria-labelledby="'filter-label-id-' + filter.id"
:placeholder="$i18n.get('label_selectbox_init')"
editable
:trap-focus="false"
:date-formatter="(date) => dateFormatter(date)"
:date-parser="(date) => dateParser(date)"
icon="calendar-today"
:years-range="[-200, 50]"
:day-names="[
$i18n.get('datepicker_short_sunday'),
$i18n.get('datepicker_short_monday'),
$i18n.get('datepicker_short_tuesday'),
$i18n.get('datepicker_short_wednesday'),
$i18n.get('datepicker_short_thursday'),
$i18n.get('datepicker_short_friday'),
$i18n.get('datepicker_short_saturday'),
]"
@update:model-value="validadeValues()"
@focus="isTouched = true" />
</div>
</template>
<script>
import { dateInter } from "../../../js/mixins";
import { filterTypeMixin } from '../../../js/filter-types-mixin';
import moment from 'moment';
export default {
mixins: [
dateInter,
filterTypeMixin
],
emits: [
'input',
],
data(){
return {
dateInit: undefined,
dateEnd: undefined,
isTouched: false
}
},
watch: {
isTouched( val ){
if ( val && this.dateInit === null)
this.dateInit = new Date();
if ( val && this.dateEnd === null)
this.dateEnd = new Date();
},
'query': {
handler() {
this.updateSelectedValues();
},
deep: true
}
},
mounted() {
this.updateSelectedValues();
},
methods: {
// only validate if the first value is higher than first
validadeValues: _.debounce( function (){
if (this.dateInit === undefined)
this.dateInit = new Date();
if (this.dateEnd === undefined)
this.dateEnd = new Date();
if (this.dateInit > this.dateEnd) {
this.showErrorMessage();
return
}
this.emit();
}, 800),
showErrorMessage(){
if ( !this.isTouched ) return false;
this.$buefy.toast.open({
duration: 3000,
message: this.$i18n.get('info_error_first_value_greater'),
position: 'is-bottom',
type: 'is-danger'
})
},
dateFormatter(dateObject){
return moment(dateObject, moment.ISO_8601).format(this.dateFormat);
},
dateParser(dateString){
return moment(dateString, this.dateFormat).toDate();
},
updateSelectedValues(){
if ( !this.query || !this.query.metaquery || !Array.isArray( this.query.metaquery ) )
return false;
let index = this.query.metaquery.findIndex(newMetadatum => newMetadatum.key == this.metadatumId);
if (index >= 0) {
let metadata = this.query.metaquery[ index ];
if (metadata.value && metadata.value.length > 0) {
const dateValueInit = new Date(metadata.value[0].replace(/-/g, '/'));
this.dateInit = moment(dateValueInit, moment.ISO_8601).toDate();
const dateValueEnd = new Date(metadata.value[1].replace(/-/g, '/'));
this.dateEnd = moment(dateValueEnd, moment.ISO_8601).toDate();
}
} else {
this.dateInit = null;
this.dateEnd = null;
}
},
// emit the operation for listeners
emit() {
let values = [];
if (this.dateInit === null && this.dateEnd === null) {
values = [];
} else {
let dateInit = this.dateInit.getUTCFullYear() + '-' +
('00' + (this.dateInit.getUTCMonth() + 1)).slice(-2) + '-' +
('00' + this.dateInit.getUTCDate()).slice(-2);
let dateEnd = this.dateEnd.getUTCFullYear() + '-' +
('00' + (this.dateEnd.getUTCMonth() + 1)).slice(-2) + '-' +
('00' + this.dateEnd.getUTCDate()).slice(-2);
values = [ dateInit, dateEnd ];
}
this.$emit('input', {
filter: 'intersection',
type: 'DATE',
compare: '>=',
metadatum_id: this.metadatumId,
collection_id: this.collectionId,
value: values
});
this.$emit('input', {
filter: 'intersection',
type: 'DATE',
compare: '<=',
metadatum_id: this.filterTypeOptions.secondary_filter_metadatum_id,
collection_id: this.collectionId,
value: values
});
}
}
}
</script>
<style scoped>
.field {
margin-bottom: 0.125em !important;
}
.dropdown-trigger input {
font-size: 0.75em;
}
</style>

View File

@ -0,0 +1,119 @@
<?php
namespace Tainacan\Filter_Types;
defined( 'ABSPATH' ) or die( 'No script kiddies please!' );
/**
* Class TainacanFilterType
*/
class Dates_Intersection extends Filter_Type {
function __construct(){
$this->set_name( __('Dates Intersection', 'tainacan') );
$this->set_supported_types(['date']);
$this->set_component('tainacan-filter-dates-intersection');
$this->set_form_component('tainacan-filter-form-dates-intersection');
$this->set_default_options([
'secondary_filter_metadatum_id' => '',
'secondary_filter_metadatum_name' => '',
]);
$this->set_use_max_options(false);
$this->set_preview_template('
<div>
<div class="datepicker control is-small">
<div class="dropdown is-bottom-left is-mobile-modal">
<div role="button" class="dropdown-trigger">
<div class="control has-icons-left is-small is-clearfix">
<input type="text" autocomplete="off" placeholder=" '. __('Select a date', 'tainacan') .'" class="input is-small">
<span class="icon is-left is-small"><i class="mdi mdi-calendar-today"></i></span>
</div>
</div>
</div>
</div>
<p class="is-size-7 has-text-centered is-marginless">until</p>
<div class="datepicker control is-small">
<div class="dropdown is-bottom-left is-mobile-modal">
<div role="button" class="dropdown-trigger">
<div class="control has-icons-left is-small is-clearfix">
<input type="text" autocomplete="off" placeholder=" '. __('Select a date', 'tainacan') .'" class="input is-small">
<span class="icon is-left is-small"><i class="mdi mdi-calendar-today"></i></span>
</div>
</div>
</div>
</div>
</div>
');
}
/**
* @inheritdoc
*/
public function get_form_labels(){
return [
'secondary_filter_metadatum_id' => [
'title' => __( 'Second date metadatum', 'tainacan' ),
'description' => __( 'The other metadatum to which this filter will compare values to find if there is an intersection of dates.', 'tainacan' ),
],
'secondary_filter_metadatum_name' => [
'title' => __( 'Second date metadatum', 'tainacan' ),
'description' => __( 'Label of the other metadatum to which this filter will compare values to find if there is an intersection of dates.', 'tainacan' ),
]
];
}
/**
* @param \Tainacan\Entities\Filter $filter
* @return array|bool true if is validate or array if has error
*/
public function validate_options(\Tainacan\Entities\Filter $filter) {
if ( !in_array($filter->get_status(), apply_filters('tainacan-status-require-validation', ['publish','future','private'])) )
return true;
if ( empty($this->get_option('secondary_filter_metadatum_id')) ) {
return [
'secondary_filter_metadatum_id' => __('The secondary date metadatum is required.','tainacan')
];
}
// Validate if the second date metadatum is a date metadatum
return true;
}
}
class Dates_Intersection_Interval_Helper {
use \Tainacan\Traits\Singleton_Instance;
protected function init() {
add_filter( 'tainacan-api-items-tainacan-filter-dates-intersection-filter-arguments', [$this, 'format_filter_arguments']);
}
function format_filter_arguments( $filter_arguments ) {
if (
!isset($filter_arguments['compare']) ||
!isset($filter_arguments['label'])
) {
return $filter_arguments;
}
if (
is_array($filter_arguments['label']) &&
count($filter_arguments['label']) === 2
) {
$filter_arguments['label'] = $filter_arguments['label'][0] . ' - ' . $filter_arguments['label'][1];
}
if (
isset( $filter_arguments['filter'] ) &&
isset( $filter_arguments['filter']['filter_type_options'] ) &&
isset( $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'] ) &&
!empty( $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'] )
) {
$filter_arguments['filter']['name'] = $filter_arguments['filter']['name'] . ' - ' . $filter_arguments['filter']['filter_type_options']['secondary_filter_metadatum_name'];
}
return $filter_arguments;
}
}
Dates_Intersection_Interval_Helper::get_instance();

View File

@ -38,6 +38,7 @@ class Filter_Type_Helper {
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Selectbox');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Autocomplete');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Date_Interval');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Dates_Intersection');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\Numeric_Interval');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\TaxonomyTaginput');
$this->Tainacan_Filters->register_filter_type('Tainacan\Filter_Types\TaxonomyCheckbox');

View File

@ -100,6 +100,7 @@
TainacanFilterTaxonomyCheckbox: defineAsyncComponent(() => import('./taxonomy/TainacanFilterCheckbox.vue')),
TainacanFilterTaxonomyTaginput: defineAsyncComponent(() => import('./taxonomy/TainacanFilterTaginput.vue')),
TainacanFilterDateInterval: defineAsyncComponent(() => import('./date-interval/TainacanFilterDateInterval.vue')),
TainacanFilterDatesIntersection: defineAsyncComponent(() => import('./dates-intersection/TainacanFilterDatesIntersection.vue')),
TainacanFilterNumericInterval: defineAsyncComponent(() => import('./numeric-interval/TainacanFilterNumericInterval.vue')),
TainacanFilterNumericListInterval: defineAsyncComponent(() => import('./numeric-list-interval/TainacanFilterNumericListInterval.vue'))
},

View File

@ -788,6 +788,7 @@ return apply_filters( 'tainacan-i18n', [
'instruction_click_to_add_a_point' => __( 'Drag to reposition or click to insert a marker', 'tainacan' ),
'instruction_select_geocoordinate_metadatum' => __( 'Select a geocoordinate metadatum', 'tainacan' ),
'instruction_multiple_terms_insertion' => __( 'Type or paste here a list of names using a separator to create multiple terms at once.', 'tainacan' ),
'instruction_select_second_date_to_compare' => __( 'Select the second date metadatum', 'tainacan' ),
// Info. Other feedback to user.
'info_items_tab_all' => __( 'Every item, except by those sent to trash.', 'tainacan' ),