[hub][vue] Saved Products Page, remote item card
- also undo unfavouriting items
diff --git a/erpnext/public/js/hub/components/ItemCard.vue b/erpnext/public/js/hub/components/ItemCard.vue
index 4d72344..6e8b7ea 100644
--- a/erpnext/public/js/hub/components/ItemCard.vue
+++ b/erpnext/public/js/hub/components/ItemCard.vue
@@ -1,24 +1,24 @@
<template>
- <div class="col-md-3 col-sm-4 col-xs-6 hub-card-container">
- <div class="hub-card is_local"
+ <div v-if="item.seen" class="col-md-3 col-sm-4 col-xs-6 hub-card-container">
+ <div class="hub-card"
@click="on_click(item_id)"
>
<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-subtitle ellipsis text-muted" v-html='subtitle'></div>
</div>
<i v-if="allow_clear"
class="octicon octicon-x text-extra-muted"
- @click="$emit('remove-item', item_id)"
+ @click.stop="$emit('remove-item', item_id)"
>
</i>
</div>
<div class="hub-card-body">
<img class="hub-card-image" :src="item.image"/>
<div class="hub-card-overlay">
- <div class="hub-card-overlay-body">
- <div class="hub-card-overlay-button" style="right: 15px; bottom: 15px;">
+ <div v-if="is_local" class="hub-card-overlay-body">
+ <div class="hub-card-overlay-button">
<button class="btn btn-default zoom-view">
<i class="octicon octicon-pencil text-muted"></i>
</button>
@@ -34,13 +34,28 @@
export default {
name: 'item-card',
- props: ['item', 'item_id_fieldname', 'on_click', 'allow_clear'],
+ props: ['item', 'item_id_fieldname', 'is_local', 'on_click', 'allow_clear'],
computed: {
title() {
- return this.item.item_name
+ const item_name = this.item.item_name || this.item.name;
+ return strip_html(item_name);
},
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() {
return this.item[this.item_id_fieldname];
@@ -58,6 +73,7 @@
border: 1px solid @border-color;
border-radius: 4px;
overflow: hidden;
+ cursor: pointer;
&:hover .hub-card-overlay {
display: block;
diff --git a/erpnext/public/js/hub/components/ItemCardsContainer.vue b/erpnext/public/js/hub/components/ItemCardsContainer.vue
index 0e75795..d558175 100644
--- a/erpnext/public/js/hub/components/ItemCardsContainer.vue
+++ b/erpnext/public/js/hub/components/ItemCardsContainer.vue
@@ -12,6 +12,7 @@
:key="item[item_id_fieldname]"
:item="item"
:item_id_fieldname="item_id_fieldname"
+ :is_local="is_local"
:on_click="on_click"
:allow_clear="editable"
@remove-item="$emit('remove-item', item[item_id_fieldname])"
@@ -29,6 +30,7 @@
props: {
items: Array,
item_id_fieldname: String,
+ is_local: Boolean,
on_click: Function,
editable: Boolean,
diff --git a/erpnext/public/js/hub/components/PublishPage.vue b/erpnext/public/js/hub/components/PublishPage.vue
index 2068813..5178553 100644
--- a/erpnext/public/js/hub/components/PublishPage.vue
+++ b/erpnext/public/js/hub/components/PublishPage.vue
@@ -23,6 +23,7 @@
<item-cards-container
:items="selected_items"
:item_id_fieldname="item_id_fieldname"
+ :is_local="true"
:editable="true"
@remove-item="remove_item_from_selection"
@@ -44,6 +45,7 @@
<item-cards-container
:items="valid_items"
:item_id_fieldname="item_id_fieldname"
+ :is_local="true"
:on_click="show_publishing_dialog_for_item"
>
</item-cards-container>
diff --git a/erpnext/public/js/hub/components/SavedProductsPage.vue b/erpnext/public/js/hub/components/SavedProductsPage.vue
new file mode 100644
index 0000000..d10f550
--- /dev/null
+++ b/erpnext/public/js/hub/components/SavedProductsPage.vue
@@ -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>
diff --git a/erpnext/public/js/hub/components/item_card.js b/erpnext/public/js/hub/components/item_card.js
index e2546e6..a2c8553 100644
--- a/erpnext/public/js/hub/components/item_card.js
+++ b/erpnext/public/js/hub/components/item_card.js
@@ -2,6 +2,7 @@
if (item.recent_message) {
return get_item_message_card_html(item);
}
+
const item_name = item.item_name || item.name;
const title = strip_html(item_name);
const img_url = item.image;
@@ -14,7 +15,7 @@
if (rating > 0) {
subtitle.push(rating + `<i class='fa fa-fw fa-star-o'></i>`)
}
-
+
subtitle.push(company_name);
let dot_spacer = '<span aria-hidden="true"> · </span>';
@@ -51,61 +52,6 @@
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) {
const item_name = item.item_name || item.name;
const title = strip_html(item_name);
@@ -138,6 +84,5 @@
}
export {
- get_item_card_html,
- get_local_item_card_html
+ get_item_card_html
}
diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js
index 42b2dda..803babb 100644
--- a/erpnext/public/js/hub/marketplace.js
+++ b/erpnext/public/js/hub/marketplace.js
@@ -80,8 +80,8 @@
$nav_group.empty();
const user_specific_items_html = this.registered
- ? `<li class="hub-sidebar-item" data-route="marketplace/favourites">
- ${__('Favorites')}
+ ? `<li class="hub-sidebar-item" data-route="marketplace/saved-products">
+ ${__('Saved Products')}
</li>
<li class="hub-sidebar-item text-muted" data-route="marketplace/profile">
${__('Your Profile')}
@@ -196,8 +196,8 @@
}
// registered seller routes
- if (route[1] === 'favourites' && !this.subpages.favourites) {
- this.subpages.favourites = new erpnext.hub.Favourites(this.$body);
+ if (route[1] === 'saved-products' && !this.subpages['saved-products']) {
+ this.subpages['saved-products'] = new erpnext.hub.SavedProducts(this.$body);
}
if (route[1] === 'profile' && !this.subpages.profile) {
@@ -221,7 +221,7 @@
}
// 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])) {
frappe.set_route('marketplace', 'home');
return;
diff --git a/erpnext/public/js/hub/pages/favourites.js b/erpnext/public/js/hub/pages/favourites.js
index ae08a68..547f815 100644
--- a/erpnext/public/js/hub/pages/favourites.js
+++ b/erpnext/public/js/hub/pages/favourites.js
@@ -1,82 +1,20 @@
-import SubPage from './subpage';
-import { get_item_card_container_html } from '../components/items_container';
+import SavedProductsPage from '../components/SavedProductsPage.vue';
+import Vue from 'vue/dist/vue.js';
-erpnext.hub.Favourites = class Favourites extends SubPage {
- make_wrapper() {
- super.make_wrapper();
- this.bind_events();
+erpnext.hub.SavedProducts = class {
+ constructor(parent) {
+ this.$wrapper = $(`<div id="vue-area-saved">`).appendTo($(parent));
+
+ new Vue({
+ render: h => h(SavedProductsPage)
+ }).$mount('#vue-area-saved');
}
- bind_events() {
- this.$wrapper.on('click', '.hub-card', (e) => {
- 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);
- }
- });
+ show() {
+ $('[data-page-name="saved-products"]').show();
}
- refresh() {
- this.get_favourites()
- .then(items => {
- this.render(items);
- });
+ hide() {
+ $('[data-page-name="saved-products"]').hide();
}
-
- 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) { }
}
diff --git a/erpnext/public/less/hub.less b/erpnext/public/less/hub.less
index bcf57f4..720a9d8 100644
--- a/erpnext/public/less/hub.less
+++ b/erpnext/public/less/hub.less
@@ -44,18 +44,6 @@
&:hover .hub-card-overlay {
display: block;
}
-
- .octicon-x {
- display: none;
- margin-left: 10px;
- font-size: 20px;
- }
- }
-
- .hub-card.closable {
- .octicon-x {
- display: block;
- }
}
.hub-card.is-local {