| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- <template>
- <div class="filter-modal" v-show="show" @click="closeFilter">
- <div class="filter-panel" ref="panel">
- <div class="filter-title" v-if="title">{{ title }}</div>
- <div class="filter" v-for="(f, idx) in filters" :key="idx">
- <date-picker
- v-if="f.type == 'date'"
- :label="f.name"
- lang="fr"
- target="day"
- :modelValue="defaultValues[idx]"
- @update:modelValue="updateDateFilter($event, idx)"
- />
- <date-picker
- v-if="f.type == 'hour'"
- :label="f.name"
- lang="fr"
- target="hour"
- :modelValue="defaultValues[idx]"
- @update:modelValue="updateDateFilter($event, idx)"
- />
- <select-chip-input
- v-if="f.type == 'category'"
- :label="f.name"
- :modelValue="defaultValues[idx]"
- @update:modelValue="updateCategoryFilter($event, idx)"
- :autocompleteList="autocompleteList[idx]"
- :placeholder="f.placeholder"
- secondaryPlaceholder="+1"
- />
- </div>
- </div>
- </div>
- </template>
- <script lang="ts">
- import { defineComponent, PropType } from "vue";
- import SelectChipInput from "@/components/SelectChipInput.vue";
- import DatePicker from "@/components/date-picker.vue";
- import dayjs, { ConfigType } from "dayjs";
- export type FilterParam = {
- name: string;
- key: string;
- type: "hour" | "date" | "category";
- direction?: "bigger" | "smaller";
- default?: string;
- placeholder?: string;
- };
- export default defineComponent({
- components: { DatePicker, SelectChipInput },
- emits: ["filterChange"],
- props: {
- title: { type: String, default: "Apply filter" },
- data: { type: Array as PropType<Array<Record<string, unknown>>>, default: () => [] },
- filters: { type: Array as PropType<Array<FilterParam>>, default: () => [] },
- },
- data() {
- return {
- defaultValues: [] as Array<unknown>,
- filterFunctions: [] as Array<(o: Record<string, unknown>) => boolean>,
- show: false,
- };
- },
- computed: {
- autocompleteList(): Array<Array<unknown>> {
- return this.filters.map((f) => {
- return f.type == "category" ? Array.from(new Set(this.data.flatMap((o) => o[f.key]))) : [];
- });
- },
- },
- watch: {
- data() {
- this.emitFilteredValue();
- },
- filters() {
- this.reset();
- },
- },
- methods: {
- open() {
- this.show = true;
- },
- closeFilter(e: MouseEvent) {
- if (!(this.$refs.panel as HTMLElement).contains(e.target as Node)) {
- this.show = false;
- }
- },
- reset() {
- this.filterFunctions = this.filters.map(() => () => true);
- this.defaultValues = this.filters.map(this.getDefault);
- },
- getDefault(f: FilterParam) {
- if (["date", "hour"].includes(f.type)) {
- if (f.default == undefined) return "";
- const d = dayjs(f.default);
- return d.isValid() ? d.toISOString() : "";
- }
- if (f.type == "category") {
- if (f.default == undefined) return [];
- return JSON.parse(f.default) ?? [];
- }
- },
- emitFilteredValue() {
- this.$emit(
- "filterChange",
- this.data.filter((o) => !this.filterFunctions.some((f) => !f(o)))
- );
- },
- updateDateFilter(value: Date, idx: number) {
- const f = this.filters[idx];
- const d = dayjs(value);
- const compareFunction = f.direction == "smaller" ? d.isBefore : d.isAfter;
- this.filterFunctions[idx] = d.isValid()
- ? (o) => compareFunction(dayjs(o[f.key] as ConfigType))
- : () => true;
- this.defaultValues[idx] = value;
- this.emitFilteredValue();
- },
- updateCategoryFilter(value: Array<unknown>, idx: number) {
- const f = this.filters[idx];
- this.filterFunctions[idx] =
- value.length > 0
- ? (o) => !value.some((k) => !(o[f.key] as Array<unknown>).includes(k))
- : () => true;
- this.defaultValues[idx] = value;
- this.emitFilteredValue();
- },
- },
- mounted() {
- this.reset();
- },
- });
- </script>
- <style scoped>
- .filter-modal {
- position: fixed;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- background-color: rgba(0, 0, 0, 0.2);
- z-index: 1300;
- }
- .filter-panel {
- position: relative;
- top: 0;
- left: 0;
- bottom: 0;
- overflow-x: auto;
- background-color: #fff;
- width: 350px;
- height: 100vh;
- }
- .filter-title {
- padding: 16px;
- font-size: 2rem;
- }
- .filter {
- padding: 8px 16px;
- }
- </style>
|