mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-02 11:49:10 +00:00
[hub][vue] Saved Products Page, remote item card
- also undo unfavouriting items
This commit is contained in:
@@ -1,24 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="col-md-3 col-sm-4 col-xs-6 hub-card-container">
|
<div v-if="item.seen" class="col-md-3 col-sm-4 col-xs-6 hub-card-container">
|
||||||
<div class="hub-card is_local"
|
<div class="hub-card"
|
||||||
@click="on_click(item_id)"
|
@click="on_click(item_id)"
|
||||||
>
|
>
|
||||||
<div class="hub-card-header flex justify-between">
|
<div class="hub-card-header flex justify-between">
|
||||||
<div>
|
<div class="ellipsis" :style="{ width: '85%' }">
|
||||||
<div class="hub-card-title ellipsis bold">{{ title }}</div>
|
<div class="hub-card-title ellipsis bold">{{ title }}</div>
|
||||||
<div class="hub-card-subtitle ellipsis text-muted" v-html='subtitle'></div>
|
<div class="hub-card-subtitle ellipsis text-muted" v-html='subtitle'></div>
|
||||||
</div>
|
</div>
|
||||||
<i v-if="allow_clear"
|
<i v-if="allow_clear"
|
||||||
class="octicon octicon-x text-extra-muted"
|
class="octicon octicon-x text-extra-muted"
|
||||||
@click="$emit('remove-item', item_id)"
|
@click.stop="$emit('remove-item', item_id)"
|
||||||
>
|
>
|
||||||
</i>
|
</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="hub-card-body">
|
<div class="hub-card-body">
|
||||||
<img class="hub-card-image" :src="item.image"/>
|
<img class="hub-card-image" :src="item.image"/>
|
||||||
<div class="hub-card-overlay">
|
<div class="hub-card-overlay">
|
||||||
<div class="hub-card-overlay-body">
|
<div v-if="is_local" class="hub-card-overlay-body">
|
||||||
<div class="hub-card-overlay-button" style="right: 15px; bottom: 15px;">
|
<div class="hub-card-overlay-button">
|
||||||
<button class="btn btn-default zoom-view">
|
<button class="btn btn-default zoom-view">
|
||||||
<i class="octicon octicon-pencil text-muted"></i>
|
<i class="octicon octicon-pencil text-muted"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -34,13 +34,28 @@
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'item-card',
|
name: 'item-card',
|
||||||
props: ['item', 'item_id_fieldname', 'on_click', 'allow_clear'],
|
props: ['item', 'item_id_fieldname', 'is_local', 'on_click', 'allow_clear'],
|
||||||
computed: {
|
computed: {
|
||||||
title() {
|
title() {
|
||||||
return this.item.item_name
|
const item_name = this.item.item_name || this.item.name;
|
||||||
|
return strip_html(item_name);
|
||||||
},
|
},
|
||||||
subtitle() {
|
subtitle() {
|
||||||
return comment_when(this.item.creation);
|
const dot_spacer = '<span aria-hidden="true"> · </span>';
|
||||||
|
if(this.is_local){
|
||||||
|
return comment_when(this.item.creation);
|
||||||
|
} else {
|
||||||
|
let subtitle_items = [comment_when(this.item.creation)];
|
||||||
|
const rating = this.item.average_rating;
|
||||||
|
|
||||||
|
if (rating > 0) {
|
||||||
|
subtitle_items.push(rating + `<i class='fa fa-fw fa-star-o'></i>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
subtitle_items.push(this.item.company);
|
||||||
|
|
||||||
|
return subtitle_items.join(dot_spacer);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
item_id() {
|
item_id() {
|
||||||
return this.item[this.item_id_fieldname];
|
return this.item[this.item_id_fieldname];
|
||||||
@@ -58,6 +73,7 @@ export default {
|
|||||||
border: 1px solid @border-color;
|
border: 1px solid @border-color;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover .hub-card-overlay {
|
&:hover .hub-card-overlay {
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
:key="item[item_id_fieldname]"
|
:key="item[item_id_fieldname]"
|
||||||
:item="item"
|
:item="item"
|
||||||
:item_id_fieldname="item_id_fieldname"
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:is_local="is_local"
|
||||||
:on_click="on_click"
|
:on_click="on_click"
|
||||||
:allow_clear="editable"
|
:allow_clear="editable"
|
||||||
@remove-item="$emit('remove-item', item[item_id_fieldname])"
|
@remove-item="$emit('remove-item', item[item_id_fieldname])"
|
||||||
@@ -29,6 +30,7 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
items: Array,
|
items: Array,
|
||||||
item_id_fieldname: String,
|
item_id_fieldname: String,
|
||||||
|
is_local: Boolean,
|
||||||
on_click: Function,
|
on_click: Function,
|
||||||
editable: Boolean,
|
editable: Boolean,
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
<item-cards-container
|
<item-cards-container
|
||||||
:items="selected_items"
|
:items="selected_items"
|
||||||
:item_id_fieldname="item_id_fieldname"
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:is_local="true"
|
||||||
:editable="true"
|
:editable="true"
|
||||||
@remove-item="remove_item_from_selection"
|
@remove-item="remove_item_from_selection"
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
<item-cards-container
|
<item-cards-container
|
||||||
:items="valid_items"
|
:items="valid_items"
|
||||||
:item_id_fieldname="item_id_fieldname"
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:is_local="true"
|
||||||
:on_click="show_publishing_dialog_for_item"
|
:on_click="show_publishing_dialog_for_item"
|
||||||
>
|
>
|
||||||
</item-cards-container>
|
</item-cards-container>
|
||||||
|
|||||||
120
erpnext/public/js/hub/components/SavedProductsPage.vue
Normal file
120
erpnext/public/js/hub/components/SavedProductsPage.vue
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="marketplace-page"
|
||||||
|
:data-page-name="page_name"
|
||||||
|
>
|
||||||
|
<h5>{{ page_title }}</h5>
|
||||||
|
|
||||||
|
<item-cards-container
|
||||||
|
:items="items"
|
||||||
|
:item_id_fieldname="item_id_fieldname"
|
||||||
|
:on_click="go_to_item_details_page"
|
||||||
|
:editable="true"
|
||||||
|
@remove-item="on_item_remove"
|
||||||
|
:empty_state_message="empty_state_message"
|
||||||
|
>
|
||||||
|
</item-cards-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ItemCardsContainer from './ItemCardsContainer.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'saved-products-page',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page_name: frappe.get_route()[1],
|
||||||
|
items: [],
|
||||||
|
all_items: [],
|
||||||
|
item_id_fieldname: 'hub_item_code',
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
page_title: __('Saved Products'),
|
||||||
|
empty_state_message: __(`You haven't favourited any items yet.`)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ItemCardsContainer
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.get_items();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
get_items() {
|
||||||
|
hub.call(
|
||||||
|
'get_favourite_items_of_seller',
|
||||||
|
{
|
||||||
|
hub_seller: hub.settings.company_email
|
||||||
|
},
|
||||||
|
'action:item_favourite'
|
||||||
|
)
|
||||||
|
.then((items) => {
|
||||||
|
this.items = items.map(item => {
|
||||||
|
item.seen = true;
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
go_to_item_details_page(hub_item_code) {
|
||||||
|
frappe.set_route(`marketplace/item/${hub_item_code}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
on_item_remove(hub_item_code) {
|
||||||
|
const grace_period = 5000;
|
||||||
|
let reverted = false;
|
||||||
|
let alert;
|
||||||
|
|
||||||
|
const undo_remove = () => {
|
||||||
|
this.toggle_item(hub_item_code);;
|
||||||
|
reverted = true;
|
||||||
|
alert.hide();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert = frappe.show_alert(__(`<span>${hub_item_code} removed.
|
||||||
|
<a href="#" class="undo-remove" data-action="undo-remove"><b>Undo</b></a></span>`),
|
||||||
|
grace_period/1000,
|
||||||
|
{
|
||||||
|
'undo-remove': undo_remove.bind(this)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.toggle_item(hub_item_code, false);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if(!reverted) {
|
||||||
|
this.remove_item_from_saved_products(hub_item_code);
|
||||||
|
}
|
||||||
|
}, grace_period);
|
||||||
|
},
|
||||||
|
|
||||||
|
remove_item_from_saved_products(hub_item_code) {
|
||||||
|
erpnext.hub.trigger('action:item_favourite');
|
||||||
|
hub.call('remove_item_from_seller_favourites', {
|
||||||
|
hub_item_code,
|
||||||
|
hub_seller: hub.settings.company_email
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.get_items();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// By default show
|
||||||
|
toggle_item(hub_item_code, show=true) {
|
||||||
|
this.items = this.items.map(item => {
|
||||||
|
if(item.hub_item_code === hub_item_code) {
|
||||||
|
item.seen = show;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -2,6 +2,7 @@ function get_item_card_html(item) {
|
|||||||
if (item.recent_message) {
|
if (item.recent_message) {
|
||||||
return get_item_message_card_html(item);
|
return get_item_message_card_html(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
const item_name = item.item_name || item.name;
|
const item_name = item.item_name || item.name;
|
||||||
const title = strip_html(item_name);
|
const title = strip_html(item_name);
|
||||||
const img_url = item.image;
|
const img_url = item.image;
|
||||||
@@ -51,61 +52,6 @@ function get_item_card_html(item) {
|
|||||||
return item_html;
|
return item_html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_local_item_card_html(item) {
|
|
||||||
const item_name = item.item_name || item.name;
|
|
||||||
const title = strip_html(item_name);
|
|
||||||
const img_url = item.image;
|
|
||||||
const company_name = item.company;
|
|
||||||
|
|
||||||
const is_active = item.publish_in_hub;
|
|
||||||
const id = item.hub_item_code || item.item_code;
|
|
||||||
|
|
||||||
// Subtitle
|
|
||||||
let subtitle = [comment_when(item.creation)];
|
|
||||||
const rating = item.average_rating;
|
|
||||||
|
|
||||||
if (rating > 0) {
|
|
||||||
subtitle.push(rating + `<i class='fa fa-fw fa-star-o'></i>`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (company_name) {
|
|
||||||
subtitle.push(company_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let dot_spacer = '<span aria-hidden="true"> · </span>';
|
|
||||||
subtitle = subtitle.join(dot_spacer);
|
|
||||||
|
|
||||||
const edit_item_button = `<div class="hub-card-overlay-button" style="right: 15px; bottom: 15px;" data-route="Form/Item/${item.item_name}">
|
|
||||||
<button class="btn btn-default zoom-view">
|
|
||||||
<i class="octicon octicon-pencil text-muted"></i>
|
|
||||||
</button>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
const item_html = `
|
|
||||||
<div class="col-md-3 col-sm-4 col-xs-6 hub-card-container">
|
|
||||||
<div class="hub-card is-local ${is_active ? 'active' : ''}" data-id="${id}">
|
|
||||||
<div class="hub-card-header flex">
|
|
||||||
<div class="ellipsis">
|
|
||||||
<div class="hub-card-title ellipsis bold">${title}</div>
|
|
||||||
<div class="hub-card-subtitle ellipsis text-muted">${subtitle}</div>
|
|
||||||
</div>
|
|
||||||
<i class="octicon octicon-check text-success"></i>
|
|
||||||
</div>
|
|
||||||
<div class="hub-card-body">
|
|
||||||
<img class="hub-card-image" src="${img_url}" />
|
|
||||||
<div class="hub-card-overlay">
|
|
||||||
<div class="hub-card-overlay-body">
|
|
||||||
${edit_item_button}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return item_html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_item_message_card_html(item) {
|
function get_item_message_card_html(item) {
|
||||||
const item_name = item.item_name || item.name;
|
const item_name = item.item_name || item.name;
|
||||||
const title = strip_html(item_name);
|
const title = strip_html(item_name);
|
||||||
@@ -138,6 +84,5 @@ function get_item_message_card_html(item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
get_item_card_html,
|
get_item_card_html
|
||||||
get_local_item_card_html
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ erpnext.hub.Marketplace = class Marketplace {
|
|||||||
$nav_group.empty();
|
$nav_group.empty();
|
||||||
|
|
||||||
const user_specific_items_html = this.registered
|
const user_specific_items_html = this.registered
|
||||||
? `<li class="hub-sidebar-item" data-route="marketplace/favourites">
|
? `<li class="hub-sidebar-item" data-route="marketplace/saved-products">
|
||||||
${__('Favorites')}
|
${__('Saved Products')}
|
||||||
</li>
|
</li>
|
||||||
<li class="hub-sidebar-item text-muted" data-route="marketplace/profile">
|
<li class="hub-sidebar-item text-muted" data-route="marketplace/profile">
|
||||||
${__('Your Profile')}
|
${__('Your Profile')}
|
||||||
@@ -196,8 +196,8 @@ erpnext.hub.Marketplace = class Marketplace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// registered seller routes
|
// registered seller routes
|
||||||
if (route[1] === 'favourites' && !this.subpages.favourites) {
|
if (route[1] === 'saved-products' && !this.subpages['saved-products']) {
|
||||||
this.subpages.favourites = new erpnext.hub.Favourites(this.$body);
|
this.subpages['saved-products'] = new erpnext.hub.SavedProducts(this.$body);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route[1] === 'profile' && !this.subpages.profile) {
|
if (route[1] === 'profile' && !this.subpages.profile) {
|
||||||
@@ -221,7 +221,7 @@ erpnext.hub.Marketplace = class Marketplace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// dont allow unregistered users to access registered routes
|
// dont allow unregistered users to access registered routes
|
||||||
const registered_routes = ['favourites', 'profile', 'publish', 'my-products', 'messages'];
|
const registered_routes = ['saved-products', 'profile', 'publish', 'my-products', 'messages'];
|
||||||
if (!hub.settings.registered && registered_routes.includes(route[1])) {
|
if (!hub.settings.registered && registered_routes.includes(route[1])) {
|
||||||
frappe.set_route('marketplace', 'home');
|
frappe.set_route('marketplace', 'home');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,82 +1,20 @@
|
|||||||
import SubPage from './subpage';
|
import SavedProductsPage from '../components/SavedProductsPage.vue';
|
||||||
import { get_item_card_container_html } from '../components/items_container';
|
import Vue from 'vue/dist/vue.js';
|
||||||
|
|
||||||
erpnext.hub.Favourites = class Favourites extends SubPage {
|
erpnext.hub.SavedProducts = class {
|
||||||
make_wrapper() {
|
constructor(parent) {
|
||||||
super.make_wrapper();
|
this.$wrapper = $(`<div id="vue-area-saved">`).appendTo($(parent));
|
||||||
this.bind_events();
|
|
||||||
|
new Vue({
|
||||||
|
render: h => h(SavedProductsPage)
|
||||||
|
}).$mount('#vue-area-saved');
|
||||||
}
|
}
|
||||||
|
|
||||||
bind_events() {
|
show() {
|
||||||
this.$wrapper.on('click', '.hub-card', (e) => {
|
$('[data-page-name="saved-products"]').show();
|
||||||
const $target = $(e.target);
|
|
||||||
if($target.hasClass('octicon-x')) {
|
|
||||||
e.stopPropagation();
|
|
||||||
const hub_item_code = $target.attr('data-hub-item-code');
|
|
||||||
this.on_item_remove(hub_item_code);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
hide() {
|
||||||
this.get_favourites()
|
$('[data-page-name="saved-products"]').hide();
|
||||||
.then(items => {
|
|
||||||
this.render(items);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get_favourites() {
|
|
||||||
return hub.call('get_favourite_items_of_seller', {
|
|
||||||
hub_seller: hub.settings.company_email
|
|
||||||
}, 'action:item_favourite');
|
|
||||||
}
|
|
||||||
|
|
||||||
render(items) {
|
|
||||||
this.$wrapper.find('.hub-items-container').empty();
|
|
||||||
const html = get_item_card_container_html(items, __('Favourites'));
|
|
||||||
this.$wrapper.html(html);
|
|
||||||
this.$wrapper.find('.hub-card').addClass('closable');
|
|
||||||
|
|
||||||
if (!items.length) {
|
|
||||||
this.render_empty_state();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render_empty_state() {
|
|
||||||
this.$wrapper.find('.hub-items-container').append(`
|
|
||||||
<div class="col-md-12">${__("You don't have any favourites yet.")}</div>
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
|
|
||||||
on_item_remove(hub_item_code, $hub_card = '') {
|
|
||||||
const $message = $(__(`<span>${hub_item_code} removed.
|
|
||||||
<a href="#" data-action="undo-remove"><b>Undo</b></a></span>`));
|
|
||||||
|
|
||||||
frappe.show_alert($message);
|
|
||||||
|
|
||||||
$hub_card = this.$wrapper.find(`.hub-card[data-hub-item-code="${hub_item_code}"]`);
|
|
||||||
|
|
||||||
$hub_card.hide();
|
|
||||||
|
|
||||||
const grace_period = 5000;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.remove_item(hub_item_code, $hub_card);
|
|
||||||
}, grace_period);
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_item(hub_item_code, $hub_card) {
|
|
||||||
hub.call('remove_item_from_seller_favourites', {
|
|
||||||
hub_item_code,
|
|
||||||
hub_seller: hub.settings.company_email
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
$hub_card.remove();
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.log(e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
undo_remove(hub_item_code) { }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,18 +44,6 @@ body[data-route^="marketplace/"] {
|
|||||||
&:hover .hub-card-overlay {
|
&:hover .hub-card-overlay {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.octicon-x {
|
|
||||||
display: none;
|
|
||||||
margin-left: 10px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hub-card.closable {
|
|
||||||
.octicon-x {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hub-card.is-local {
|
.hub-card.is-local {
|
||||||
|
|||||||
Reference in New Issue
Block a user