Эх сурвалжийг харах

Работа над диалогом выбора даты

Book Pauk 2 жил өмнө
parent
commit
8982b3eaf0

+ 1 - 7
client/components/Search/AuthorList/AuthorList.vue

@@ -360,13 +360,7 @@ class AuthorList extends BaseList {
 
     async refresh() {
         //параметры запроса
-        let newQuery = _.cloneDeep(this.search);
-        newQuery = newQuery.setDefaults(newQuery);
-        delete newQuery.setDefaults;
-        newQuery.offset = (newQuery.page - 1)*newQuery.limit;
-        if (!this.showDeleted)
-            newQuery.del = 0;
-
+        const newQuery = this.getQuery();
         if (_.isEqual(newQuery, this.prevQuery))
             return;
         this.prevQuery = newQuery;

+ 51 - 2
client/components/Search/BaseList.js

@@ -1,3 +1,4 @@
+import moment from 'moment';
 import _ from 'lodash';
 
 import authorBooksStorage from './authorBooksStorage';
@@ -230,7 +231,7 @@ export default class BaseList {
 
     async expandSeries(seriesItem) {
         this.$emit('listEvent', {action: 'ignoreScroll'});
-        
+
         const expandedSeries = _.cloneDeep(this.expandedSeries);
         const key = seriesItem.key;
 
@@ -406,7 +407,8 @@ export default class BaseList {
             //date
             let dateFound = !s.date;
             if (!dateFound) {
-                let [from = '0000-00-00', to = '9999-99-99'] = s.date.split(',');
+                const date = this.queryDate(s.date.split(','));
+                let [from = '0000-00-00', to = '9999-99-99'] = date;
                 dateFound = (book.date >= from && book.date <= to);
             }
 
@@ -467,4 +469,51 @@ export default class BaseList {
             return (dserno ? dserno : (dtitle ? dtitle : dext));
         });
     }
+
+    queryDate(date) {
+        if (!(utils.isDigit(date[0]) && utils.isDigit(date[1]))) {//!manual
+            /*
+            {label: 'сегодня', value: 'today'},
+            {label: 'за 3 дня', value: '3days'},
+            {label: 'за неделю', value: 'week'},
+            {label: 'за 2 недели', value: '2weeks'},
+            {label: 'за месяц', value: 'month'},
+            {label: 'за 3 месяца', value: '3months'},
+            {label: 'указать даты', value: 'manual'},
+            */
+            const sqlFormat = 'YYYY-MM-DD';
+            switch (date) {
+                case 'today': date = utils.dateFormat(moment(), sqlFormat); break;
+                case '3days': date = utils.dateFormat(moment().subtract(3, 'days'), sqlFormat); break;
+                case 'week': date = utils.dateFormat(moment().subtract(1, 'weeks'), sqlFormat); break;
+                case '2weeks': date = utils.dateFormat(moment().subtract(2, 'weeks'), sqlFormat); break;
+                case 'month': date = utils.dateFormat(moment().subtract(1, 'months'), sqlFormat); break;
+                case '3months': date = utils.dateFormat(moment().subtract(3, 'months'), sqlFormat); break;
+                default:
+                    date = '';
+            }
+        }
+
+        return date;
+    }
+
+    getQuery() {
+        let newQuery = _.cloneDeep(this.search);
+        newQuery = newQuery.setDefaults(newQuery);
+        delete newQuery.setDefaults;
+
+        //дата
+        if (newQuery.date) {
+            newQuery.date = this.queryDate(newQuery.date);
+        }
+
+        //offset
+        newQuery.offset = (newQuery.page - 1)*newQuery.limit;
+
+        //del
+        if (!this.showDeleted)
+            newQuery.del = 0;
+
+        return newQuery;
+    }
 }

+ 1 - 2
client/components/Search/BookView/BookView.vue

@@ -204,8 +204,7 @@ class BookView {
         if (!this.book.date)
             return '';
 
-        const date = utils.parseDate(this.book.date);
-        return utils.formatDate(date, 'noDate');
+        return utils.sqlDateFormat(this.book.date);
     }
 
     selectAuthor() {

+ 90 - 8
client/components/Search/Search.vue

@@ -120,14 +120,35 @@
                     </q-input>
 
                     <div class="q-mx-xs" />
-                    <q-input
-                        v-model="search.date" :maxlength="inputMaxLength" :debounce="inputDebounce"
-                        class="q-mt-xs" :bg-color="inputBgColor()" input-style="cursor: pointer" style="width: 200px;" label="Дата поступления" stack-label outlined dense clearable
+                    <q-select 
+                        v-model="searchDate"
+                        class="q-mt-xs"
+                        :options="searchDateOptions"
+                        dropdown-icon="la la-angle-down la-sm"
+                        :bg-color="inputBgColor()"
+                        style="width: 200px;"
+                        label="Дата поступления" stack-label
+                        outlined dense emit-value map-options clearable
                     >
-                        <q-tooltip v-if="search.date && showTooltips" :delay="500" anchor="bottom middle" content-style="font-size: 80%" max-width="400px">
-                            {{ search.date }}
-                        </q-tooltip>
-                    </q-input>
+                        <template #selected-item="scope">
+                            <div v-if="scope.opt.value == 'manual'">
+                                <div v-html="formatSearchDate" />
+                            </div>
+                            <div v-else>
+                                {{ scope.opt.label }}
+                            </div>
+                        </template>
+
+                        <template #option="scope">
+                            <q-item v-bind="scope.itemProps" @click="dateSelectItemClick(scope.opt.value)">
+                                <q-item-section>
+                                    <q-item-label>
+                                        {{ scope.opt.label }}
+                                    </q-item-label>
+                                </q-item-section>
+                            </q-item>
+                        </template>
+                    </q-select>
 
                     <div class="q-mx-xs" />
                     <q-input
@@ -210,6 +231,7 @@
         <SelectGenreDialog v-model="selectGenreDialogVisible" v-model:genre="search.genre" :genre-tree="genreTree" />
         <SelectLangDialog v-model="selectLangDialogVisible" v-model:lang="search.lang" :lang-list="langList" :lang-default="langDefault" />        
         <SelectLibRateDialog v-model="selectLibRateDialogVisible" v-model:librate="search.librate" />
+        <SelectDateDialog v-model="selectDateDialogVisible" v-model:date="search.date" />
     </div>
 </template>
 
@@ -225,6 +247,7 @@ import PageScroller from './PageScroller/PageScroller.vue';
 import SelectGenreDialog from './SelectGenreDialog/SelectGenreDialog.vue';
 import SelectLangDialog from './SelectLangDialog/SelectLangDialog.vue';
 import SelectLibRateDialog from './SelectLibRateDialog/SelectLibRateDialog.vue';
+import SelectDateDialog from './SelectDateDialog/SelectDateDialog.vue';
 
 import authorBooksStorage from './authorBooksStorage';
 import DivBtn from '../share/DivBtn.vue';
@@ -250,6 +273,7 @@ const componentOptions = {
         SelectGenreDialog,
         SelectLangDialog,
         SelectLibRateDialog,
+        SelectDateDialog,
         Dialog,
         DivBtn
     },
@@ -271,6 +295,7 @@ const componentOptions = {
 
                 this.makeTitle();
                 this.updateRouteQueryFromSearch();
+                this.updateSearchDate(true);
             },
             deep: true,
         },
@@ -334,7 +359,10 @@ const componentOptions = {
             if (this.getListRoute() != newValue) {
                 this.updateRouteQueryFromSearch();
             }
-        }
+        },
+        searchDate() {
+            this.updateSearchDate(false);
+        },
     },
 };
 class Search {
@@ -354,6 +382,7 @@ class Search {
     selectGenreDialogVisible = false;
     selectLangDialogVisible = false;
     selectLibRateDialogVisible = false;
+    selectDateDialogVisible = false;
 
     pageCount = 1;    
 
@@ -378,6 +407,8 @@ class Search {
         },
     };
 
+    searchDate = '';
+
     //settings
     showCounts = true;
     showRates = true;
@@ -414,6 +445,16 @@ class Search {
         {label: '1000', value: 1000},
     ];
 
+    searchDateOptions = [
+        {label: 'сегодня', value: 'today'},
+        {label: 'за 3 дня', value: '3days'},
+        {label: 'за неделю', value: 'week'},
+        {label: 'за 2 недели', value: '2weeks'},
+        {label: 'за месяц', value: 'month'},
+        {label: 'за 3 месяца', value: '3months'},
+        {label: 'выбрать даты', value: 'manual'},
+    ];
+
     created() {
         this.commit = this.$store.commit;
         this.api = this.$root.api;
@@ -928,6 +969,47 @@ class Search {
             this.genreTreeUpdating = false;
         }
     }
+
+    isManualDate(date) {
+        return date && utils.isDigit(date[0]) && utils.isDigit(date[1]);
+    }
+
+    updateSearchDate(toLocal) {
+        if (toLocal) {
+            let local = this.search.date || '';
+            if (this.isManualDate(local))
+                local = 'manual';
+
+            this.searchDate = local;
+        } else {
+            if (this.searchDate != 'manual')
+                this.search.date = this.searchDate || '';
+        }
+    }
+
+    get formatSearchDate() {
+        const result = [];
+        const date = this.search.date;
+        if (this.isManualDate(date)) {
+            const [from, to] = date.split(',')
+            if (from)
+                result.push(`<div style="display: inline-block; width: 15px; text-align: right;">от</div> ${utils.sqlDateFormat(from)}`);
+            if (to)
+                result.push(`<div style="display: inline-block; width: 15px; text-align: right;">до</div> ${utils.sqlDateFormat(to)}`);
+        }
+
+        return result.join('<br>');
+    }
+
+    dateSelectItemClick(itemValue) {
+        if (itemValue == 'manual') {
+            if (!this.isManualDate(this.search.date)) {
+                this.search.date = '';
+                this.searchDate = '';
+            }
+            this.selectDateDialogVisible = true
+        }
+    }
 }
 
 export default vueComponent(Search);

+ 132 - 0
client/components/Search/SelectDateDialog/SelectDateDialog.vue

@@ -0,0 +1,132 @@
+<template>
+    <Dialog ref="dialog" v-model="dialogVisible">
+        <template #header>
+            <div class="row items-center">
+                <div style="font-size: 130%">
+                    Выбрать даты
+                </div>
+            </div>
+        </template>
+
+        <div ref="box" class="column q-mt-xs overflow-auto no-wrap" style="width: 240px; padding: 0px 10px 10px 10px;">
+            <div class="row items-center">
+                <span class="q-mr-sm">От:</span>
+                <q-btn icon="la la-calendar" color="secondary" :label="labelFrom" dense no-caps style="width: 150px;">
+                    <q-popup-proxy cover transition-show="scale" transition-hide="scale">
+                        <q-date v-model="from" mask="YYYY-MM-DD">
+                            <div class="row items-center justify-end q-gutter-sm">
+                                <q-btn v-close-popup label="Отмена" color="primary" flat />
+                                <q-btn v-close-popup label="OK" color="primary" flat @click="save" />
+                            </div>
+                        </q-date>
+                    </q-popup-proxy>
+                </q-btn>
+                <q-icon name="la la-times-circle" class="q-ml-sm text-grey-6 clickable2" size="28px" @click="from = ''; save();" />
+            </div>
+
+            <div class="q-my-sm" />
+            <div class="row items-center">
+                <span class="q-mr-sm">До:</span>
+                <q-btn icon="la la-calendar" color="secondary" :label="labelTo" dense no-caps style="width: 150px;">
+                    <q-popup-proxy cover transition-show="scale" transition-hide="scale">
+                        <q-date v-model="to" mask="YYYY-MM-DD">
+                            <div class="row items-center justify-end q-gutter-sm">
+                                <q-btn v-close-popup label="Отмена" color="primary" flat />
+                                <q-btn v-close-popup label="OK" color="primary" flat @click="save" />
+                            </div>
+                        </q-date>
+                    </q-popup-proxy>
+                </q-btn>
+                <q-icon name="la la-times-circle" class="q-ml-sm text-grey-6 clickable2" size="28px" @click="to = ''; save();" />
+            </div>
+        </div>
+
+        <template #footer>
+            <q-btn class="q-px-md q-ml-sm" color="primary" dense no-caps @click="okClick">
+                OK
+            </q-btn>
+        </template>
+    </Dialog>
+</template>
+
+<script>
+//-----------------------------------------------------------------------------
+import vueComponent from '../../vueComponent.js';
+
+import Dialog from '../../share/Dialog.vue';
+import * as utils from '../../../share/utils';
+
+const componentOptions = {
+    components: {
+        Dialog
+    },
+    watch: {
+        modelValue(newValue) {
+            this.dialogVisible = newValue;
+        },
+        dialogVisible(newValue) {
+            this.$emit('update:modelValue', newValue);
+        },
+        date() {
+            this.updateFromTo();
+        },
+    }
+};
+class SelectDateDialog {
+    _options = componentOptions;
+    _props = {
+        modelValue: Boolean,
+        date: String,
+    };
+
+    dialogVisible = false;
+
+    from = '';
+    to = '';
+
+    created() {
+    }
+
+    mounted() {
+        this.updateFromTo();
+    }
+
+    updateFromTo() {
+        this.from = this.splitDate.from;
+        this.to = this.splitDate.to;
+    }
+
+    get splitDate() {
+        const [from = '', to = ''] = (this.date || '').split(',');
+        return {from, to};
+    }
+
+    get labelFrom() {
+        return (this.splitDate.from ? utils.sqlDateFormat(this.splitDate.from) : 'Не указано');
+    }
+
+    get labelTo() {
+        return (this.splitDate.to ? utils.sqlDateFormat(this.splitDate.to) : 'Не указано');
+    }
+
+    save() {
+        let d = this.from;
+        if (this.to)
+            d += `,${this.to}`;
+        this.$emit('update:date', d);
+    }
+
+    okClick() {
+        this.dialogVisible = false;
+    }
+}
+
+export default vueComponent(SelectDateDialog);
+//-----------------------------------------------------------------------------
+</script>
+
+<style scoped>
+.clickable2 {
+    cursor: pointer;
+}
+</style>

+ 1 - 1
client/components/Search/SelectLangDialog/SelectLangDialog.vue

@@ -3,7 +3,7 @@
         <template #header>
             <div class="row items-center">
                 <div style="font-size: 130%">
-                    Выбрать язык
+                    Выбрать языки
                 </div>
             </div>
         </template>

+ 1 - 7
client/components/Search/SeriesList/SeriesList.vue

@@ -224,13 +224,7 @@ class SeriesList extends BaseList {
 
     async refresh() {
         //параметры запроса
-        let newQuery = _.cloneDeep(this.search);
-        newQuery = newQuery.setDefaults(newQuery);
-        delete newQuery.setDefaults;
-        newQuery.offset = (newQuery.page - 1)*newQuery.limit;
-        if (!this.showDeleted)
-            newQuery.del = 0;
-
+        const newQuery = this.getQuery();
         if (_.isEqual(newQuery, this.prevQuery))
             return;
         this.prevQuery = newQuery;

+ 1 - 7
client/components/Search/TitleList/TitleList.vue

@@ -85,13 +85,7 @@ class TitleList extends BaseList {
 
     async refresh() {
         //параметры запроса
-        let newQuery = _.cloneDeep(this.search);
-        newQuery = newQuery.setDefaults(newQuery);
-        delete newQuery.setDefaults;
-        newQuery.offset = (newQuery.page - 1)*newQuery.limit;
-        if (!this.showDeleted)
-            newQuery.del = 0;
-
+        const newQuery = this.getQuery();
         if (_.isEqual(newQuery, this.prevQuery))
             return;
         this.prevQuery = newQuery;

+ 8 - 5
client/quasar.js

@@ -21,14 +21,15 @@ import {QIcon} from 'quasar/src/components/icon';
 //import {QTabPanels, QTabPanel} from 'quasar/src/components/tab-panels';
 //import {QSeparator} from 'quasar/src/components/separator';
 //import {QList} from 'quasar/src/components/item';
-//import {QItem, QItemSection, QItemLabel} from 'quasar/src/components/item';
+import {QItem, QItemSection, QItemLabel} from 'quasar/src/components/item';
 import {QTooltip} from 'quasar/src/components/tooltip';
 //import {QSpinner} from 'quasar/src/components/spinner';
 //import {QTable, QTh, QTr, QTd} from 'quasar/src/components/table';
 import {QCheckbox} from 'quasar/src/components/checkbox';
 import {QSelect} from 'quasar/src/components/select';
 //import {QColor} from 'quasar/src/components/color';
-//import {QPopupProxy} from 'quasar/src/components/popup-proxy';
+import {QPopupProxy} from 'quasar/src/components/popup-proxy';
+import {QDate} from 'quasar/src/components/date';
 import {QDialog} from 'quasar/src/components/dialog';
 //import {QChip} from 'quasar/src/components/chip';
 import {QTree} from 'quasar/src/components/tree';
@@ -55,14 +56,15 @@ const components = {
     //QTabPanels, QTabPanel,
     //QSeparator,
     //QList,
-    //QItem, QItemSection, QItemLabel,
+    QItem, QItemSection, QItemLabel,
     QTooltip,
     //QSpinner,
     //QTable, QTh, QTr, QTd,
     QCheckbox,
     QSelect,
     //QColor,
-    //QPopupProxy,
+    QPopupProxy,
+    QDate,
     QDialog,
     //QChip,
     QTree,
@@ -91,12 +93,13 @@ const plugins = {
 //import '@quasar/extras/fontawesome-v5/fontawesome-v5.css';
 //import fontawesomeV5 from 'quasar/icon-set/fontawesome-v5.js'
 
+import lang from 'quasar/lang/ru';
 import '@quasar/extras/line-awesome/line-awesome.css';
 import lineAwesome from 'quasar/icon-set/line-awesome.js'
 
 export default {
     quasar: Quasar,
-    options: { config, components, directives, plugins }, 
+    options: { config, components, directives, plugins, lang }, 
     init: () => {
         Quasar.iconSet.set(lineAwesome);
 }

+ 15 - 1
client/share/utils.js

@@ -1,3 +1,4 @@
+import moment from 'moment';
 import {Buffer} from 'safe-buffer';
 //import _ from 'lodash';
 
@@ -98,7 +99,7 @@ export function makeValidFilename(filename, repl = '_') {
     else
         throw new Error('Invalid filename');
 }
-
+/*
 export function formatDate(d, format = 'normal') {
     switch (format) {
         case 'normal':
@@ -124,4 +125,17 @@ export function parseDate(sqlDate) {
     result.setYear(parseInt(d[0], 10));
         
     return result;
+}
+*/
+
+export function isDigit(c) {
+    return !isNaN(parseInt(c, 10));
+}
+
+export function dateFormat(date, format = 'DD.MM.YYYY') {
+    return moment(date).format(format);
+}
+
+export function sqlDateFormat(date, format = 'DD.MM.YYYY') {
+    return moment(date, 'YYYY-MM-DD').format(format);
 }