Bugzilla – Attachment 112060 Details for
Bug 15522
New interface for revamped circulation rules
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 15522: (Vue alternative) Add vue scripts
Bug-15522-Vue-alternative-Add-vue-scripts.patch (text/plain), 33.60 KB, created by
Agustín Moyano
on 2020-10-20 17:18:00 UTC
(
hide
)
Description:
Bug 15522: (Vue alternative) Add vue scripts
Filename:
MIME Type:
Creator:
Agustín Moyano
Created:
2020-10-20 17:18:00 UTC
Size:
33.60 KB
patch
obsolete
>From 0aeeafba2826b3902b4bfc6c50c498204419aa27 Mon Sep 17 00:00:00 2001 >From: Agustin Moyano <agustinmoyano@theke.io> >Date: Tue, 20 Oct 2020 13:32:03 -0300 >Subject: [PATCH] Bug 15522: (Vue alternative) Add vue scripts > >This patch adds vue scripts for circulation rules > >To test: >1. Apply all patches >2. restart_all >3. yarn install >4. yarn run build >5. go to circulation rules in staff interface > >You'll notice the working area is divided in 3 parts.. > >On the left you've got a list area. That list area has a combo where you can choose >between libraries, patron categories and item types. What you choose >there are the rules you'll be watching and editing. > >The middle area is where rules are shown. That area is divided by circulation rules, fines >rules and hold rules, and each of that sections are divided by rules >specific for libraries, libraries and categories, libraries and >item types or libraries, categories and item types. To edit a rule click >on the pencil icon in the section title. If you're editing default >values (Values for all libraries, categories or item types) the >corresponding edit control (input, select, etc) is shown. If you are >editing for a particular library, category or item type, above the >editing control you get a checkbox to use default value or to overwrite >that rule. > >In the area to the right you've got a label with the currently selected >library, category or item type. If when selecting items in the list area >you do ctrl+click (cmd+click on mac) you can select multiple elements >from the list. All selected elements will appear in the right area. >Notice that only one of them is still selected. You can rapidly change >from one selected item to the other by clicking on those labels >--- > .../js/src/admin/components/datepicker.vue | 33 ++ > .../admin/policy/components/policyMain.vue | 351 ++++++++++++++++++ > .../admin/policy/components/policySidebar.vue | 102 +++++ > .../admin/policy/components/policyTags.vue | 68 ++++ > .../prog/js/src/admin/policy/main.d.ts | 22 ++ > .../prog/js/src/admin/policy/main.ts | 73 ++++ > 6 files changed, 649 insertions(+) > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/admin/components/datepicker.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policyMain.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policySidebar.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policyTags.vue > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/main.d.ts > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/main.ts > >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/admin/components/datepicker.vue b/koha-tmpl/intranet-tmpl/prog/js/src/admin/components/datepicker.vue >new file mode 100644 >index 0000000000..60fdf9605e >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/src/admin/components/datepicker.vue >@@ -0,0 +1,33 @@ >+<template> >+ <input type="text" :class="{[random]: true}" :value="date" style="width: calc(100% - 16px); display: inline-block"/> >+</template> >+<script lang="ts"> >+import {defineComponent} from 'vue' >+export default defineComponent({ >+ props: [ >+ 'date' >+ ], >+ name: 'DatePicker', >+ computed: { >+ random() { >+ return 'rnd_'+Math.round(Math.random() * 1000) >+ } >+ }, >+ mounted() { >+ const self = this; >+ this.$nextTick(()=>{ >+ $('.'+self.random).datepicker({ >+ onClose: (dateText, inst) => { >+ validate_date(dateText, inst) >+ } >+ }).on("change", function () { >+ if (!is_valid_date($(this).val())) { >+ $(this).val("") >+ } >+ self.$emit('update:date', $(this).val()) >+ self.$emit('change'); >+ }); >+ }) >+ } >+}) >+</script> >\ No newline at end of file >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policyMain.vue b/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policyMain.vue >new file mode 100644 >index 0000000000..901dbdbbaa >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policyMain.vue >@@ -0,0 +1,351 @@ >+<template> >+ <div> >+ <div class="group" v-for="group in groups" :key="group.key"> >+ <div class="row"> >+ <h2 class="col-md-9">{{group.title}}</h2> >+ <div class="col-md-3"> >+ <button @click="group.expand = !group.expand">{{group.expand ? collapseText : expandText}}</button> >+ </div> >+ </div> >+ <div class="text-muted"> >+ {{group.description}} >+ </div> >+ <form v-if="group.expand" class="form-horizontal"> >+ <template v-for="panel in group.panels" :key="panel.key"> >+ <div class="panel panel-default" v-if="has_kinds(group.key, panel.key)"> >+ <div class="panel-heading"> >+ <div class="row"> >+ <div class="col-md-11">{{self[panel.key+'_panel_heading']}}</div> >+ <div class="col-md-1"> >+ <span class="fa fa-pencil" @click="panel.edit = !panel.edit"></span> >+ </div> >+ </div> >+ </div> >+ <div class="panel-body"> >+ <div v-for="(rule, key) in effective_rules[group.key][panel.key]" :key="key" class="form-group"> >+ <label for="{{key}}" class="col-md-3 control-label">{{rule.kind.description}}</label> >+ <div class="col-md-9" v-if="!panel.edit"> >+ <div class="form-control-static">{{get_rule_value(rule)}}</div> >+ <div class="text-muted" v-if="rule.rule._use_default && !rule.is_default">Using default value</div> >+ </div> >+ <div class="col-md-9" v-if="panel.edit"> >+ <template v-if="!rule.is_default"> >+ <div> >+ <input type="checkbox" :id="key+'_default'" v-model="rule.rule._use_default" />Use default value ({{get_default_value(rule)}}) >+ </div> >+ <div> >+ <input v-if="!rule.kind.choices && rule.kind.type != 'date'" :readonly="rule.rule._use_default" :disabled="rule.rule._use_default" type="text" :id="key" v-model="rule.rule.rule_value" @change="edit(rule.rule)" class="form-control" :class="{datepicker: rule.kind.type == 'date'}"/> >+ <date-picker v-if="!rule.kind.choices && rule.kind.type == 'date'" :readonly="rule.rule._use_default" :disabled="rule.rule._use_default" :id="key" v-model:date="rule.rule.rule_value" @change="edit(rule.rule)" class="form-control"/> >+ <select v-if="rule.kind.choices" :readonly="rule.rule._use_default" :disabled="rule.rule._use_default" :id="key" v-model="rule.rule.rule_value" @change="edit(rule.rule)" class="form-control"> >+ <option v-for="choice in rule.kind.choices" :key="choice[0]" :value="choice[0]">{{choice[1]}}</option> >+ </select> >+ </div> >+ </template> >+ <template v-if="rule.is_default"> >+ <input v-if="!rule.kind.choices && rule.kind.type != 'date'" type="text" :id="key" v-model="rule.default.rule_value" @change="edit(rule.default)" class="form-control" :class="{datepicker: rule.kind.type == 'date'}"/> >+ <date-picker v-if="!rule.kind.choices && rule.kind.type == 'date'" :id="key" v-model:date="rule.default.rule_value" @change="edit(rule.default)" class="form-control"/> >+ <select v-if="rule.kind.choices" :id="key" v-model="rule.default.rule_value" @change="edit(rule.default)" class="form-control"> >+ <option v-for="choice in rule.kind.choices" :key="choice[0]" :value="choice[0]">{{choice[1]}}</option> >+ </select> >+ </template> >+ >+ </div> >+ </div> >+ </div> >+ </div> >+ </template> >+ </form> >+ </div> >+ </div> >+</template> >+<script lang="ts"> >+ import { defineComponent, readonly, reactive } from 'vue' >+ import DatePicker from '../../components/datepicker.vue' >+ >+ export default defineComponent({ >+ name: 'PolicyMain', >+ components: { >+ DatePicker >+ }, >+ props: [ >+ 'current', >+ 'libraries', >+ 'categories', >+ 'itemtypes', >+ 'doSave', >+ 'doClear' >+ ], >+ data() { >+ return { >+ self: this, >+ expandText: _('Expand'), >+ collapseText: _('Collapse'), >+ kinds: {}, >+ origRules: {}, >+ rules: {}, >+ groups: [ >+ { >+ key: 'circulation', >+ title: _("Circulation rules"), >+ description: _("A brief and tiny text that explains how awesome and great cirulation rules are"), >+ expanded: false, >+ panels: [ >+ { >+ scope: ['branchcode'], >+ key: 'lib' >+ }, >+ { >+ scope: ['branchcode', 'categorycode'], >+ key: 'cat' >+ }, >+ { >+ scope: ['branchcode', 'itemtype'], >+ key: 'itype' >+ }, >+ { >+ scope: ['branchcode', 'categorycode', 'itemtype'], >+ key: 'all' >+ } >+ ] >+ }, >+ { >+ key: 'fines', >+ title: _("Fines rules"), >+ description: _("A long and tedious text that explains how deadly serious fines rules are"), >+ expanded: false, >+ panels: [ >+ { >+ scope: ['branchcode'], >+ key: 'lib' >+ }, >+ { >+ scope: ['branchcode', 'categorycode'], >+ key: 'cat' >+ }, >+ { >+ scope: ['branchcode', 'itemtype'], >+ key: 'itype' >+ }, >+ { >+ scope: ['branchcode', 'categorycode', 'itemtype'], >+ key: 'all' >+ } >+ ] >+ }, >+ { >+ key: 'holds', >+ title: _("Holds rules"), >+ description: _("A light text that explains why you cannot live without holds rules"), >+ expanded: false, >+ panels: [ >+ { >+ scope: ['branchcode'], >+ key: 'lib' >+ }, >+ { >+ scope: ['branchcode', 'categorycode'], >+ key: 'cat' >+ }, >+ { >+ scope: ['branchcode', 'itemtype'], >+ key: 'itype' >+ }, >+ { >+ scope: ['branchcode', 'categorycode', 'itemtype'], >+ key: 'all' >+ } >+ ] >+ }, >+ ], >+ edited: [] >+ } >+ }, >+ async created() { >+ this.kinds = await fetch( "/api/v1/circulation-rules/kinds" ).then( result => result.json() ) >+ this.rules = await fetch( "/api/v1/circulation-rules" ).then( result => result.json() ) >+ this.origRules = readonly(JSON.parse(JSON.stringify(this.rules))); >+ }, >+ computed: { >+ lib_panel_heading() { >+ return 'For '+this.get_current('lib') >+ }, >+ cat_panel_heading() { >+ return 'For '+this.get_current('lib')+' and '+this.get_current('cat') >+ }, >+ itype_panel_heading() { >+ return 'For '+this.get_current('lib')+' and '+this.get_current('itype') >+ }, >+ all_panel_heading() { >+ return 'For '+this.get_current('lib')+', '+this.get_current('cat')+' and '+this.get_current('itype') >+ }, >+ effective_rules() { >+ const groups = {} >+ const missing_rules = []; >+ this.groups.forEach(group => { >+ groups[group.key] = {}, >+ group.panels.forEach(panel => { >+ groups[group.key][panel.key] = {} >+ Object.keys(this.kinds).filter(key => { >+ const kind = this.kinds[key] >+ if(!kind.description) return; >+ if(kind.group == group.key && !this.simetric_diff(kind.scope, panel.scope).length) { >+ groups[group.key][panel.key][key] = {kind} >+ const rules = this.rules >+ .filter(rule => rule.rule_name == key) >+ rules.forEach(rule => { >+ if(rule.branchcode == null) rule.branchcode = ''; >+ if(rule.categorycode == null) rule.categorycode = ''; >+ if(rule.itemtype == null) rule.itemtype = ''; >+ }) >+ groups[group.key][panel.key][key].default = rules.find(rule => rule.branchcode == '' && rule.categorycode == '' && rule.itemtype == '') >+ if(!groups[group.key][panel.key][key].default) { >+ missing_rules.push({ >+ rule_value: '', >+ rule_name: key, >+ branchcode: '', >+ categorycode: '', >+ itemtype: '' >+ }) >+ } >+ if(panel.key == 'lib' && this.current.lib == '') { >+ groups[group.key][panel.key][key].is_default = true >+ } else if(panel.key == 'cat' && this.current.lib == '' && this.current.cat == '') { >+ groups[group.key][panel.key][key].is_default = true >+ } else if(panel.key == 'itype' && this.current.lib == '' && this.current.itype == '') { >+ groups[group.key][panel.key][key].is_default = true >+ } else if(panel.key == 'all' && this.current.lib == '' && this.current.cat == '' && this.current.itype == '') { >+ groups[group.key][panel.key][key].is_default = true >+ } else { >+ groups[group.key][panel.key][key].is_default = false >+ } >+ >+ groups[group.key][panel.key][key].rule = rules.find(rule => rule.branchcode == this.current.lib && rule.categorycode == this.current.cat && rule.itemtype == this.current.itype) >+ if(!groups[group.key][panel.key][key].rule) { >+ missing_rules.push({ >+ rule_name: key, >+ rule_value: null, >+ branchcode: this.current.lib, >+ categorycode: this.current.cat, >+ itemtype: this.current.itype, >+ _use_default: true >+ }); >+ } >+ } >+ }) >+ }) >+ }) >+ if(missing_rules.length) { >+ this.$nextTick(()=>{ >+ this.rules = this.rules.concat(missing_rules) >+ }) >+ return null >+ } >+ return groups >+ } >+ }, >+ methods: { >+ simetric_diff(left, right) { >+ const rightSet = new Set(right) >+ const leftSet = new Set(left) >+ return left.filter(item => !rightSet.has(item)).concat(right.filter(item => !leftSet.has(item))) >+ }, >+ has_kinds(group, panel) { >+ return this.effective_rules && this.effective_rules[group] && this.effective_rules[group][panel] && Object.keys(this.effective_rules[group][panel]).length >+ }, >+ get_default_value(rule) { >+ if(!rule.default) { >+ return '' >+ } >+ if(rule.kind.choices && rule.kind.choices) { >+ const choice = rule.kind.choices.find(choice => choice[0]==rule.default.rule_value) >+ if(choice) return choice[1] >+ } >+ return rule.default.rule_value >+ }, >+ get_rule_value(rule) { >+ if(rule.rule._use_default) return this.get_default_value(rule); >+ if(rule.kind.choices && rule.kind.choices) { >+ const choice = rule.kind.choices.find(choice => choice[0]==rule.rule.rule_value) >+ if(choice) return choice[1] >+ } >+ return rule.rule.rule_value >+ }, >+ get_current(type) { >+ if(type == 'lib') { >+ let curr_name = this.libraries.find(lib => lib.code == this.current.lib).name >+ return this.current[type]==''?curr_name.toLowerCase():curr_name >+ } >+ if(type == 'cat') { >+ let curr_name = this.categories.find(cat => cat.code == this.current.cat).name >+ return this.current[type]==''?curr_name.toLowerCase():_('%s category').format(curr_name) >+ } >+ if(type == 'itype') { >+ let curr_name = this.itemtypes.find(itype => itype.code == this.current.itype).name >+ return this.current[type]==''?curr_name.toLowerCase():_('%s item type').format(curr_name) >+ } >+ }, >+ edit(rule) { >+ if(!this.edited.includes(rule)) this.edited.push(rule) >+ }, >+ clear() { >+ this.rules = reactive(JSON.parse(JSON.stringify(this.origRules))) >+ this.edited = [] >+ }, >+ save() { >+ const payload = this.edited >+ .filter(rule => { >+ return rule.id || !rule._use_default >+ }) >+ .map(origRule => { >+ const rule = { >+ rule_name: origRule.rule_name, >+ rule_value: origRule.id && origRule._use_default?null:origRule.rule_value, >+ branchcode: origRule.branchcode == ''?null:origRule.branchcode, >+ categorycode: origRule.categorycode == ''?null:origRule.categorycode, >+ itemtype: origRule.itemtype == ''?null:origRule.itemtype >+ }; >+ if(origRule.id) rule.id = origRule.id >+ >+ return rule >+ }) >+ fetch( "/api/v1/circulation-rules", { >+ method: "POST", >+ credentials: "include", >+ body: JSON.stringify(payload), >+ } ).then( response => { >+ switch( response.status ) { >+ case 200: >+ this.edited = [] >+ this.origRules = readonly(JSON.parse(JSON.stringify(this.rules))) >+ humanMsg.displayMsg( '<h3>'+_("Rules saved.")+'</h3>', { className: "humanSuccess" } ) >+ break >+ case 401: >+ humanMsg.displayAlert( '<h3>'+_("Rules not saved")+'</h3><p>'+_("Please reload the page to log in again")+'</p>', { className: "humanError" } ) >+ break >+ default: >+ humanMsg.displayAlert( '<h3>'+_("Rules not saved")+'</h3><p>'+_("Internal error")+'</p>', { className: "humanError" } ) >+ break >+ } >+ } ) >+ } >+ }, >+ watch: { >+ doSave(val, oldVal) { >+ if(val) { >+ this.save() >+ } >+ }, >+ doClear(val, oldVal) { >+ if(val) { >+ this.clear() >+ } >+ }, >+ edited: { >+ handler(val, oldVal) { >+ this.$emit('edited', val) >+ }, >+ deep: true >+ } >+ } >+ }) >+</script> >\ No newline at end of file >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policySidebar.vue b/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policySidebar.vue >new file mode 100644 >index 0000000000..51cf2798a2 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policySidebar.vue >@@ -0,0 +1,102 @@ >+<template> >+ <div> >+ <form> >+ <div class="form-group"> >+ <label for="choose" class="control-label">Choose list</label> >+ <select v-model="choose" id="choose" class="form-control"> >+ <option value="lib">Library</option> >+ <option value="cat">Patron category</option> >+ <option value="itype">Item type</option> >+ </select> >+ </div> >+ <div class="form-group"> >+ <label for="filter" class="control-label">Filter list</label> >+ <input type="text" id="filter" v-model="filter" class="form-control"/> >+ </div> >+ </form> >+ <div class="list-group"> >+ <a class="list-group-item" v-for="item in list" :key="item.code" :class="{active: is_selected(item.code)}" @click.exact="do_select(item.code)" @click.ctrl="add_select(item.code)" @click.meta="add_select(item.code)"> >+ {{item.name}} >+ </a> >+ </div> >+ </div> >+</template> >+<script lang="ts"> >+/// <reference path="../main.d.ts" /> >+ import { defineComponent } from 'vue' >+ export default defineComponent({ >+ name: 'PolicySidebar', >+ props: [ >+ 'current', >+ 'select', >+ 'libraries', >+ 'categories', >+ 'itemtypes' >+ ], >+ data() { >+ return { >+ choose: 'lib', >+ filter: null, >+ libList: [], >+ catList: [], >+ itypeList: [], >+ localCurrent: { >+ lib: '', >+ cat: '', >+ itype: '' >+ }, >+ localSelected: { >+ lib: [''], >+ cat: [''], >+ itype: [''] >+ } >+ } >+ }, >+ created() { >+ this.$emit('update:current', this.localCurrent) >+ this.$emit('update:select', this.localSelected) >+ this.libList = this.libraries >+ this.catList = this.categories >+ this.itypeList = this.itemtypes >+ }, >+ methods: { >+ do_select(code) { >+ this.localCurrent[this.choose] = code >+ this.localSelected[this.choose] = [code] >+ this.$emit('update:current', this.localCurrent) >+ this.$emit('update:select', this.localSelected) >+ }, >+ add_select(code) { >+ this.localSelected[this.choose].push(code) >+ this.$emit('update:select', this.localSelected) >+ }, >+ is_selected(code) { >+ return this.localSelected[this.choose].includes(code) >+ } >+ }, >+ computed: { >+ list() { >+ if(this.choose == 'lib') return this.libList.filter(item => item.show) >+ if(this.choose == 'cat') return this.catList.filter(item => item.show) >+ return this.itypeList.filter(item => item.show) >+ } >+ }, >+ watch: { >+ choose() { >+ this.filter = null; >+ }, >+ filter() { >+ if(this.filter == null) { >+ this.libList.forEach(item => item.show = true) >+ this.catList.forEach(item => item.show = true) >+ this.itypeList.forEach(item => item.show = true) >+ } else { >+ let reg = new RegExp(this.filter, 'i') >+ this.libList.forEach(item => item.show = reg.test(item.name)) >+ this.catList.forEach(item => item.show = reg.test(item.name)) >+ this.itypeList.forEach(item => item.show = reg.test(item.name)) >+ } >+ } >+ } >+ }) >+</script> >\ No newline at end of file >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policyTags.vue b/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policyTags.vue >new file mode 100644 >index 0000000000..1ddb64aacb >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/components/policyTags.vue >@@ -0,0 +1,68 @@ >+<template> >+ <div> >+ <div class="panel panel-default"> >+ <div class="panel-heading"> >+ <h3 class="panel-title">Libraries</h3> >+ </div> >+ <div class="panel-body"> >+ <div class="container-fluid"> >+ <div class="row"> >+ <span class="label col-md-6" v-for="lib in select.lib" :key="lib" :class="{'label-primary': is_current('lib', lib), 'label-default': !is_current('lib', lib)}" @click="set_current('lib', lib)">{{get_name('lib', lib)}}</span> >+ </div> >+ </div> >+ </div> >+ </div> >+ <div class="panel panel-default"> >+ <div class="panel-heading"> >+ <h3 class="panel-title">Patron categories</h3> >+ </div> >+ <div class="panel-body"> >+ <div class="container-fluid"> >+ <div class="row"> >+ <span class="label col-md-6" v-for="cat in select.cat" :key="cat" :class="{'label-primary': is_current('cat', cat), 'label-default': !is_current('cat', cat)}" @click="set_current('cat', cat)">{{get_name('cat', cat)}}</span> >+ </div> >+ </div> >+ </div> >+ </div> >+ <div class="panel panel-default"> >+ <div class="panel-heading"> >+ <h3 class="panel-title">Item types</h3> >+ </div> >+ <div class="panel-body"> >+ <div class="container-fluid"> >+ <div class="row"> >+ <span class="label col-md-6" v-for="itype in select.itype" :key="itype" :class="{'label-primary': is_current('itype', itype), 'label-default': !is_current('itype', itype)}" @click="set_current('itype', itype)">{{get_name('itype', itype)}}</span> >+ </div> >+ </div> >+ </div> >+ </div> >+ </div> >+</template> >+<script lang="ts"> >+/// <reference path="../main.d.ts" /> >+ import { defineComponent } from 'vue' >+ export default defineComponent({ >+ name: 'PolicyTags', >+ props: [ >+ 'current', >+ 'select', >+ 'libraries', >+ 'categories', >+ 'itemtypes' >+ ], >+ methods: { >+ is_current(type, code) { >+ return this.current[type] == code; >+ }, >+ set_current(type, code) { >+ this.current[type] = code >+ this.$emit('update:current', this.current) >+ }, >+ get_name(type, code) { >+ if(type == 'lib') return this.libraries.find(item => item.code == code).name >+ if(type == 'cat') return this.categories.find(item => item.code == code).name >+ if(type == 'itype') return this.itemtypes.find(item => item.code == code).name >+ } >+ } >+ }) >+</script> >\ No newline at end of file >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/main.d.ts b/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/main.d.ts >new file mode 100644 >index 0000000000..3ad6aec7e3 >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/main.d.ts >@@ -0,0 +1,22 @@ >+declare function _ (text: string, ...params: any[]): string >+declare function $ (text: string, ...params: any[]): any >+declare function validate_date (date: string, obj: object): void >+declare function is_valid_date (date: string): boolean >+ >+type KOHA = { >+ BRANCHES: object, >+ ITEM_TYPES: object, >+ PATRON_CATEGORIES: object >+} >+ >+declare interface String { >+ format(...string):string >+} >+ >+type HUMANMSG = { >+ displayMsg(string, any): void, >+ displayAlert(string, any): void >+} >+ >+declare var Koha: KOHA >+ declare var humanMsg:HUMANMSG >\ No newline at end of file >diff --git a/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/main.ts b/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/main.ts >new file mode 100644 >index 0000000000..7d0b07ff7e >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/src/admin/policy/main.ts >@@ -0,0 +1,73 @@ >+/// <reference path="main.d.ts" /> >+ >+import {createApp} from "vue/dist/vue.esm-bundler"; >+// @ts-ignore >+import PolicySidebar from "./components/policySidebar.vue"; >+// @ts-ignore >+import PolicyMain from "./components/policyMain.vue"; >+// @ts-ignore >+import PolicyTags from "./components/policyTags.vue"; >+ >+ >+createApp({ >+ template: ` >+ <div class="container-fluid"> >+ <div class="row"> >+ <h1 class="col-md-10">{{title}}</h1> >+ <div class="col-md-2"> >+ <button type="button" class="btn btn-primary" :disabled="!edited.length" @click="doSave = true">Save</button> >+ <button type="button" class="btn btn-default" :disabled="!edited.length" @click="doClear = true">Clear</button> >+ </div> >+ </div> >+ <hr/> >+ <div class="row"> >+ <policy-sidebar class="col-md-3" v-model:current="current" v-model:select="selected" :libraries="libraries" :categories="categories" :itemtypes="itemtypes"></policy-sidebar> >+ <policy-main class="col-md-7" :current="current" :libraries="libraries" :categories="categories" :itemtypes="itemtypes" :doSave="doSave" :doClear="doClear" @edited="setEdited"></policy-main> >+ <policy-tags class="col-md-2" v-model:current="current" :select="selected" :libraries="libraries" :categories="categories" :itemtypes="itemtypes"></policy-tags> >+ </div> >+ </div> >+ `, >+ data() { >+ return { >+ title: _('Circulation, fines, and holds rules'), >+ current: {}, >+ selected: {}, >+ doSave: false, >+ doClear: false, >+ edited: [], >+ saved: false >+ } >+ }, >+ computed: { >+ libraries() { >+ const libs = Object.keys(Koha.BRANCHES).map(code => { >+ return {code, name: Koha.BRANCHES[code].branchname, show: true} >+ }) >+ return [{code: '', name: _('All libraries'), show: true}].concat(libs) >+ }, >+ categories() { >+ const cats = Object.keys(Koha.PATRON_CATEGORIES).map(code => { >+ return {code, name: Koha.PATRON_CATEGORIES[code].description, show: true} >+ }) >+ return [{code: '', name: _('All categories'), show: true}].concat(cats) >+ }, >+ itemtypes() { >+ const itypes = Object.keys(Koha.ITEM_TYPES).map(code => { >+ return {code, name: Koha.ITEM_TYPES[code].description, show: true} >+ }) >+ return [{code: '', name: _('All item types'), show: true}].concat(itypes) >+ } >+ }, >+ methods: { >+ setEdited(edited) { >+ this.edited = edited >+ this.doSave = false >+ this.doClear = false >+ } >+ }, >+ components: { >+ PolicySidebar, >+ PolicyMain, >+ PolicyTags >+ } >+}).mount("#vue-base"); >\ No newline at end of file >-- >2.25.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 15522
:
68231
|
68232
|
68233
|
68234
|
68235
|
71048
|
71049
|
71050
|
71051
|
71052
|
72239
|
72242
|
91977
|
91978
|
91979
|
91980
|
91981
|
91982
|
91983
|
91984
|
94201
|
94202
|
94203
|
94204
|
94205
|
94206
|
94207
|
94208
|
99148
|
99149
|
99150
|
99151
|
99152
|
99153
|
99154
|
99155
|
99156
|
99157
|
99199
|
99200
|
99201
|
99202
|
99203
|
99204
|
99205
|
99206
|
99207
|
99208
|
99209
|
99210
|
99211
|
99213
|
99214
|
99215
|
101918
|
101919
|
101920
|
101921
|
101922
|
101923
|
101924
|
101925
|
101926
|
101927
|
101934
|
101935
|
101936
|
101937
|
101938
|
101939
|
101940
|
101941
|
101942
|
101943
|
101944
|
101945
|
101946
|
101947
|
106913
|
106914
|
106915
|
106916
|
106917
|
106918
|
106919
|
106920
|
106921
|
106922
|
106923
|
106924
|
106925
|
106926
|
112056
|
112057
|
112058
|
112059
| 112060 |
112061