Bug 38201: Add a ResourceList component
This patch adds a ResourceList component to handle the abstraction of the current List components Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu> Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org> Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
parent
2dfa887f24
commit
c787e8821b
|
|
@ -57,6 +57,11 @@ export default {
|
|||
if (prop.type === "boolean") {
|
||||
acc[key] = prop.value;
|
||||
}
|
||||
|
||||
if (key === "disabled") {
|
||||
const currentValue = acc[key];
|
||||
acc[key] = !!currentValue;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
const attr = show ? this.attr.showElement : this.attr;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { inject } from "vue";
|
||||
import { build_url } from "../composables/datatables";
|
||||
|
||||
export default {
|
||||
setup(props) {
|
||||
|
|
@ -7,6 +8,9 @@ export default {
|
|||
const { setConfirmationDialog, setMessage, setError, setWarning } =
|
||||
inject("mainStore");
|
||||
|
||||
const AVStore = inject("AVStore");
|
||||
const { get_lib_from_av, map_av_dt_filter } = AVStore;
|
||||
|
||||
const format_date = $date;
|
||||
const patron_to_html = $patron_to_html;
|
||||
|
||||
|
|
@ -18,6 +22,11 @@ export default {
|
|||
setWarning,
|
||||
format_date,
|
||||
patron_to_html,
|
||||
logged_in_user,
|
||||
escape_str,
|
||||
get_lib_from_av,
|
||||
map_av_dt_filter,
|
||||
build_url,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -166,6 +175,26 @@ export default {
|
|||
additionalFieldsChanged(additionalFieldValues, resource) {
|
||||
resource.extended_attributes = additionalFieldValues;
|
||||
},
|
||||
getFilters(query, filterData) {
|
||||
const filters = filterData ? filterData : this.tableFilters;
|
||||
const filterOptions = filters.reduce((acc, filter) => {
|
||||
acc[filter.name] = filter.value;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
Object.keys(query).forEach(key => {
|
||||
if (
|
||||
filterOptions.hasOwnProperty(key) &&
|
||||
query[key] !== filterOptions[key]
|
||||
) {
|
||||
filterOptions[key] = query[key];
|
||||
}
|
||||
if (!filterOptions.hasOwnProperty(key)) {
|
||||
filterOptions[key] = query[key];
|
||||
}
|
||||
});
|
||||
return filterOptions;
|
||||
},
|
||||
},
|
||||
name: "BaseResource",
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,21 @@
|
|||
<template>
|
||||
<ResourceList
|
||||
v-if="action === 'list'"
|
||||
v-bind="{
|
||||
api_client,
|
||||
i18n,
|
||||
tableOptions,
|
||||
resource_name,
|
||||
goToResourceShow,
|
||||
goToResourceEdit,
|
||||
doResourceDelete,
|
||||
goToResourceAdd,
|
||||
tableFilters,
|
||||
getFilters,
|
||||
filterTable,
|
||||
tableUrl,
|
||||
}"
|
||||
/>
|
||||
<ResourceShow
|
||||
v-if="action === 'show'"
|
||||
v-bind="{
|
||||
|
|
@ -32,9 +49,10 @@ import { storeToRefs } from "pinia";
|
|||
import { APIClient } from "../../fetch/api-client.js";
|
||||
import ResourceShow from "../ResourceShow.vue";
|
||||
import ResourceFormAdd from "../ResourceFormAdd.vue";
|
||||
import ResourceList from "../ResourceList.vue";
|
||||
|
||||
export default {
|
||||
components: { ResourceShow, ResourceFormAdd },
|
||||
components: { ResourceShow, ResourceFormAdd, ResourceList },
|
||||
extends: BaseResource,
|
||||
props: {
|
||||
action: String,
|
||||
|
|
@ -51,6 +69,9 @@ export default {
|
|||
av_agreement_relationships,
|
||||
} = storeToRefs(AVStore);
|
||||
|
||||
const vendorStore = inject("vendorStore");
|
||||
const { vendors } = storeToRefs(vendorStore);
|
||||
|
||||
return {
|
||||
...BaseResource.setup({
|
||||
resource_name: "agreement",
|
||||
|
|
@ -64,6 +85,8 @@ export default {
|
|||
resource_table_url: APIClient.erm._baseURL + "agreements",
|
||||
i18n: {
|
||||
display_name: __("Agreement"),
|
||||
display_name_lc: __("agreement"),
|
||||
display_name_plural: __("agreements"),
|
||||
},
|
||||
av_agreement_statuses,
|
||||
av_agreement_closure_reasons,
|
||||
|
|
@ -72,10 +95,15 @@ export default {
|
|||
av_agreement_license_statuses,
|
||||
av_agreement_license_location,
|
||||
av_agreement_relationships,
|
||||
agreement_table_settings,
|
||||
vendors,
|
||||
}),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
const tableFilters = this.getTableFilters();
|
||||
const defaults = this.getFilters(this.$route.query, tableFilters);
|
||||
|
||||
return {
|
||||
resource_attrs: [
|
||||
{
|
||||
|
|
@ -631,6 +659,55 @@ export default {
|
|||
documents: [],
|
||||
extended_attributes: [],
|
||||
},
|
||||
tableOptions: {
|
||||
columns: this.getTableColumns(),
|
||||
options: {
|
||||
embed: "user_roles,vendor,extended_attributes,+strings",
|
||||
},
|
||||
url: () => this.tableUrl(defaults),
|
||||
table_settings: this.agreement_table_settings,
|
||||
add_filters: true,
|
||||
filters_options: {
|
||||
1: () =>
|
||||
this.vendors.map(e => {
|
||||
e["_id"] = e["id"];
|
||||
e["_str"] = e["name"];
|
||||
return e;
|
||||
}),
|
||||
3: () => this.map_av_dt_filter("av_agreement_statuses"),
|
||||
4: () =>
|
||||
this.map_av_dt_filter("av_agreement_closure_reasons"),
|
||||
5: [
|
||||
{ _id: 0, _str: this.$__("No") },
|
||||
{ _id: 1, _str: this.$__("Yes") },
|
||||
],
|
||||
6: () =>
|
||||
this.map_av_dt_filter(
|
||||
"av_agreement_renewal_priorities"
|
||||
),
|
||||
},
|
||||
actions: {
|
||||
0: ["show"],
|
||||
"-1": this.embedded
|
||||
? [
|
||||
{
|
||||
select: {
|
||||
text: this.$__("Select"),
|
||||
icon: "fa fa-check",
|
||||
},
|
||||
},
|
||||
]
|
||||
: ["edit", "delete"],
|
||||
},
|
||||
default_filters: {
|
||||
"user_roles.user_id": function () {
|
||||
return defaults.by_mine
|
||||
? logged_in_user.borrowernumber
|
||||
: "";
|
||||
},
|
||||
},
|
||||
},
|
||||
tableFilters,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -776,6 +853,162 @@ export default {
|
|||
);
|
||||
}
|
||||
},
|
||||
getTableColumns() {
|
||||
let get_lib_from_av = this.get_lib_from_av;
|
||||
let escape_str = this.escape_str;
|
||||
|
||||
return [
|
||||
{
|
||||
title: __("Name"),
|
||||
data: "me.name:me.agreement_id",
|
||||
searchable: true,
|
||||
orderable: true,
|
||||
render: function (data, type, row, meta) {
|
||||
return (
|
||||
'<a role="button" class="show">' +
|
||||
escape_str(`${row.name} (#${row.agreement_id})`) +
|
||||
"</a>"
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: __("Vendor"),
|
||||
data: "vendor_id",
|
||||
searchable: true,
|
||||
orderable: true,
|
||||
render: function (data, type, row, meta) {
|
||||
return row.vendor_id != undefined
|
||||
? '<a href="/cgi-bin/koha/acqui/supplier.pl?booksellerid=' +
|
||||
row.vendor_id +
|
||||
'">' +
|
||||
escape_str(row.vendor.name) +
|
||||
"</a>"
|
||||
: "";
|
||||
},
|
||||
},
|
||||
{
|
||||
title: __("Description"),
|
||||
data: "description",
|
||||
searchable: true,
|
||||
orderable: true,
|
||||
},
|
||||
{
|
||||
title: __("Status"),
|
||||
data: "status",
|
||||
searchable: true,
|
||||
orderable: true,
|
||||
render: function (data, type, row, meta) {
|
||||
return escape_str(
|
||||
get_lib_from_av("av_agreement_statuses", row.status)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: __("Closure reason"),
|
||||
data: "closure_reason",
|
||||
searchable: true,
|
||||
orderable: true,
|
||||
render: function (data, type, row, meta) {
|
||||
return escape_str(
|
||||
get_lib_from_av(
|
||||
"av_agreement_closure_reasons",
|
||||
row.closure_reason
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: __("Is perpetual"),
|
||||
data: "is_perpetual",
|
||||
searchable: true,
|
||||
orderable: true,
|
||||
render: function (data, type, row, meta) {
|
||||
return escape_str(
|
||||
row.is_perpetual ? __("Yes") : __("No")
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: __("Renewal priority"),
|
||||
data: "renewal_priority",
|
||||
searchable: true,
|
||||
orderable: true,
|
||||
render: function (data, type, row, meta) {
|
||||
return escape_str(
|
||||
get_lib_from_av(
|
||||
"av_agreement_renewal_priorities",
|
||||
row.renewal_priority
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
getTableFilters() {
|
||||
return [
|
||||
{
|
||||
name: "by_expired",
|
||||
type: "checkbox",
|
||||
label: __("Filter by expired"),
|
||||
value: false,
|
||||
onChange: function (filters) {
|
||||
if (filters.by_expired) {
|
||||
filters.max_expiration_date = new Date()
|
||||
.toISOString()
|
||||
.substring(0, 10);
|
||||
} else {
|
||||
filters.max_expiration_date = "";
|
||||
}
|
||||
return filters;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "max_expiration_date",
|
||||
type: "component",
|
||||
label: __("on"),
|
||||
componentPath: "./FlatPickrWrapper.vue",
|
||||
props: {
|
||||
id: {
|
||||
type: "string",
|
||||
value: "max_expiration_date_filter",
|
||||
},
|
||||
disabled: {
|
||||
type: "resourceProperty",
|
||||
resourceProperty: "by_expired",
|
||||
},
|
||||
},
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
name: "by_mine",
|
||||
type: "checkbox",
|
||||
label: __("Show mine only"),
|
||||
value: false,
|
||||
},
|
||||
];
|
||||
},
|
||||
tableUrl(filters) {
|
||||
let url = this.getResourceTableUrl();
|
||||
if (filters?.by_expired)
|
||||
url += "?max_expiration_date=" + filters.max_expiration_date;
|
||||
return url;
|
||||
},
|
||||
async filterTable(filters, table, embedded = false) {
|
||||
if (!embedded) {
|
||||
if (filters.by_expired && !filters.max_expiration_date) {
|
||||
filters.max_expiration_date = new Date()
|
||||
.toISOString()
|
||||
.substring(0, 10);
|
||||
}
|
||||
if (!filters.by_expired) {
|
||||
filters.max_expiration_date = "";
|
||||
}
|
||||
let { href } = this.$router.resolve({ name: "AgreementsList" });
|
||||
let new_route = this.build_url(href, filters);
|
||||
this.$router.push(new_route);
|
||||
}
|
||||
table.redraw(this.tableUrl(filters));
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
newResource() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div v-if="attr.type == 'text'">
|
||||
<template v-if="attr.type == 'text'">
|
||||
<label
|
||||
:for="`${attr.name}${index}`"
|
||||
:class="{ required: attr.required }"
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
:required="attr.required ? true : false"
|
||||
/>
|
||||
<span v-if="attr.required" class="required">{{ $__("Required") }}</span>
|
||||
</div>
|
||||
<div v-else-if="attr.type == 'textarea'">
|
||||
</template>
|
||||
<template v-else-if="attr.type == 'textarea'">
|
||||
<label
|
||||
:for="`${attr.name}${index}`"
|
||||
:class="{ required: attr.required }"
|
||||
|
|
@ -28,8 +28,22 @@
|
|||
:required="attr.required ? true : false"
|
||||
/>
|
||||
<span v-if="attr.required" class="required">{{ $__("Required") }}</span>
|
||||
</div>
|
||||
<div v-else-if="attr.type == 'boolean'">
|
||||
</template>
|
||||
<template v-else-if="attr.type == 'checkbox'">
|
||||
<label
|
||||
:for="`${attr.name}${index}`"
|
||||
:class="{ required: attr.required }"
|
||||
>{{ attr.label }}:</label
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:id="`${attr.name}${index}`"
|
||||
v-model="resource[attr.name]"
|
||||
@change="attr.onChange && attr.onChange(resource)"
|
||||
/>
|
||||
<span v-if="attr.required" class="required">{{ $__("Required") }}</span>
|
||||
</template>
|
||||
<template v-else-if="attr.type == 'boolean'">
|
||||
<label :for="`${attr.name}${index}`">{{ attr.label }}:</label>
|
||||
<label class="radio" :for="`${attr.name}${index}` + '_yes'"
|
||||
>{{ $__("Yes") }}:
|
||||
|
|
@ -51,8 +65,8 @@
|
|||
v-model="resource[attr.name]"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div v-else-if="attr.type == 'select'">
|
||||
</template>
|
||||
<template v-else-if="attr.type == 'select'">
|
||||
<label
|
||||
:for="`${attr.name}${index}`"
|
||||
:class="{ required: attr.required }"
|
||||
|
|
@ -78,8 +92,8 @@
|
|||
</template>
|
||||
</v-select>
|
||||
<span v-if="attr.required" class="required">{{ $__("Required") }}</span>
|
||||
</div>
|
||||
<div v-else-if="attr.type == 'component'">
|
||||
</template>
|
||||
<template v-else-if="attr.type == 'component'">
|
||||
<label
|
||||
v-if="attr.label"
|
||||
:for="attr.name"
|
||||
|
|
@ -100,7 +114,7 @@
|
|||
v-on="getEventHandlers()"
|
||||
></component>
|
||||
<span v-if="attr.required" class="required">{{ $__("Required") }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="attr.type == 'relationship' && attr.componentPath">
|
||||
<component
|
||||
:is="requiredComponent"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,168 @@
|
|||
<template>
|
||||
<div v-if="!initialized">{{ $__("Loading") }}</div>
|
||||
<div v-else id="resource_list">
|
||||
<Toolbar v-if="!embedded">
|
||||
<ToolbarButton
|
||||
action="add"
|
||||
@go-to-add-resource="goToResourceAdd"
|
||||
:title="$__('New %s').format(i18n.display_name_lc)"
|
||||
/>
|
||||
</Toolbar>
|
||||
<fieldset
|
||||
v-if="tableFilters?.length > 0 && resource_count > 0"
|
||||
class="filters"
|
||||
>
|
||||
<template
|
||||
v-for="(filter, index) in tableFilters"
|
||||
v-bind:key="index"
|
||||
>
|
||||
<FormElement
|
||||
:resource="filters"
|
||||
:attr="filter"
|
||||
:index="index"
|
||||
/>
|
||||
</template>
|
||||
<input
|
||||
@click="filterTable(filters, table, embedded)"
|
||||
id="filterTable"
|
||||
type="button"
|
||||
:value="$__('Filter')"
|
||||
/>
|
||||
</fieldset>
|
||||
<div v-if="resource_count > 0" class="page-section">
|
||||
<KohaTable
|
||||
ref="table"
|
||||
v-bind="tableOptions"
|
||||
:searchable_additional_fields="searchable_additional_fields"
|
||||
:searchable_av_options="searchable_av_options"
|
||||
@show="goToResourceShow"
|
||||
@edit="goToResourceEdit"
|
||||
@delete="doResourceDelete"
|
||||
></KohaTable>
|
||||
</div>
|
||||
<div v-else class="alert alert-info">
|
||||
{{
|
||||
$__("There are no %s defined").format(i18n.display_name_plural)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Toolbar from "./Toolbar.vue";
|
||||
import ToolbarButton from "./ToolbarButton.vue";
|
||||
import { ref } from "vue";
|
||||
import { APIClient } from "../fetch/api-client.js";
|
||||
import KohaTable from "./KohaTable.vue";
|
||||
import FormElement from "./FormElement.vue";
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const table = ref();
|
||||
|
||||
return {
|
||||
table,
|
||||
license_table_settings,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
embedded: { type: Boolean, default: false },
|
||||
api_client: Object,
|
||||
i18n: Object,
|
||||
tableOptions: Object,
|
||||
resource_name: String,
|
||||
goToResourceShow: Function,
|
||||
goToResourceEdit: Function,
|
||||
doResourceDelete: Function,
|
||||
goToResourceAdd: Function,
|
||||
tableFilters: Array,
|
||||
getFilters: Function,
|
||||
filterTable: Function,
|
||||
tableUrl: Function,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
resource_count: 0,
|
||||
initialized: false,
|
||||
searchable_additional_fields: [],
|
||||
searchable_av_options: [],
|
||||
filters: this.getFilters(this.$route.query),
|
||||
router: this.$router,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.embedded) {
|
||||
this.getResourceCount().then(() => (this.initialized = true));
|
||||
} else {
|
||||
this.getResourceCount().then(() =>
|
||||
this.getSearchableAdditionalFields().then(() =>
|
||||
this.getSearchableAVOptions().then(
|
||||
() => (this.initialized = true)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getResourceCount() {
|
||||
await this.api_client.count().then(
|
||||
count => {
|
||||
this.resource_count = count;
|
||||
},
|
||||
error => {}
|
||||
);
|
||||
},
|
||||
async getSearchableAdditionalFields() {
|
||||
const client = APIClient.additional_fields;
|
||||
await client.additional_fields.getAll(this.resource_name).then(
|
||||
searchable_additional_fields => {
|
||||
this.searchable_additional_fields =
|
||||
searchable_additional_fields.filter(
|
||||
field => field.searchable
|
||||
);
|
||||
},
|
||||
error => {}
|
||||
);
|
||||
},
|
||||
async getSearchableAVOptions() {
|
||||
const client_av = APIClient.authorised_values;
|
||||
let av_cat_array = this.searchable_additional_fields
|
||||
.filter(field => field.authorised_value_category_name)
|
||||
.map(field => field.authorised_value_category_name);
|
||||
|
||||
await client_av.values
|
||||
.getCategoriesWithValues([
|
||||
...new Set(av_cat_array.map(av_cat => '"' + av_cat + '"')),
|
||||
]) // unique
|
||||
.then(av_categories => {
|
||||
av_cat_array.forEach(av_cat => {
|
||||
let av_match = av_categories.find(
|
||||
element => element.category_name == av_cat
|
||||
);
|
||||
this.searchable_av_options[av_cat] =
|
||||
av_match.authorised_values.map(av => ({
|
||||
value: av.value,
|
||||
label: av.description,
|
||||
}));
|
||||
});
|
||||
});
|
||||
},
|
||||
redrawTable() {
|
||||
if (this.filterTable) {
|
||||
this.filterTable();
|
||||
} else {
|
||||
this.table.redraw(this.tableUrl(this.filters));
|
||||
}
|
||||
},
|
||||
},
|
||||
components: { Toolbar, ToolbarButton, KohaTable, FormElement },
|
||||
name: "ResourcesList",
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filters > input[type="checkbox"],
|
||||
.filters > input[type="button"] {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -56,7 +56,7 @@ export const routes = [
|
|||
{
|
||||
path: "",
|
||||
name: "AgreementsList",
|
||||
component: markRaw(AgreementsList),
|
||||
component: markRaw(ResourceWrapper),
|
||||
},
|
||||
{
|
||||
path: ":agreement_id",
|
||||
|
|
|
|||
Loading…
Reference in New Issue