[hub] Register components, init Item Page
- commonly used components are pre-registered
- add DetailHeaderItem component
- begin ItemPage
diff --git a/erpnext/public/js/hub/PageContainer.vue b/erpnext/public/js/hub/PageContainer.vue
index ca4c63a..794d4cb 100644
--- a/erpnext/public/js/hub/PageContainer.vue
+++ b/erpnext/public/js/hub/PageContainer.vue
@@ -3,25 +3,37 @@
<component :is="current_page"></component>
</div>
</template>
+
<script>
+
import Home from './pages/Home.vue';
-import SavedProducts from './pages/SavedProducts.vue';
-import Publish from './pages/Publish.vue';
-import Category from './pages/Category.vue';
import Search from './pages/Search.vue';
+import Category from './pages/Category.vue';
+import SavedProducts from './pages/SavedProducts.vue';
import PublishedProducts from './pages/PublishedProducts.vue';
+import Item from './pages/Item.vue';
+import Seller from './pages/Seller.vue';
+import Publish from './pages/Publish.vue';
import Buying from './pages/Buying.vue';
import BuyingMessages from './pages/BuyingMessages.vue';
+import Profile from './pages/Profile.vue';
+import NotFound from './pages/NotFound.vue';
const route_map = {
'marketplace/home': Home,
- 'marketplace/saved-products': SavedProducts,
- 'marketplace/my-products': PublishedProducts,
- 'marketplace/publish': Publish,
- 'marketplace/category/:category': Category,
'marketplace/search/:keyword': Search,
+ 'marketplace/category/:category': Category,
+ 'marketplace/item/:item': Item,
+ 'marketplace/seller/:seller': Seller,
+ 'marketplace/not-found': NotFound,
+
+ // Registered seller routes
+ 'marketplace/profile': Profile,
+ 'marketplace/saved-products': SavedProducts,
+ 'marketplace/publish': Publish,
+ 'marketplace/my-products': PublishedProducts,
'marketplace/buying': Buying,
- 'marketplace/buying/:item': BuyingMessages
+ 'marketplace/buying/:item': BuyingMessages,
}
export default {
diff --git a/erpnext/public/js/hub/components/DetailHeaderItem.vue b/erpnext/public/js/hub/components/DetailHeaderItem.vue
new file mode 100644
index 0000000..5f346ff
--- /dev/null
+++ b/erpnext/public/js/hub/components/DetailHeaderItem.vue
@@ -0,0 +1,20 @@
+<template>
+ <p class="text-muted" v-html="header_item"></p>
+</template>
+
+<script>
+
+const spacer = '<span aria-hidden="true"> · </span>';
+
+export default {
+ name: 'header-item',
+ props: ['value'],
+ data() {
+ return {
+ header_item: Array.isArray(this.value)
+ ? this.value.join(spacer)
+ : this.value
+ }
+ },
+}
+</script>
diff --git a/erpnext/public/js/hub/components/DetailView.vue b/erpnext/public/js/hub/components/DetailView.vue
index b86468b..eca31d1 100644
--- a/erpnext/public/js/hub/components/DetailView.vue
+++ b/erpnext/public/js/hub/components/DetailView.vue
@@ -31,16 +31,31 @@
<img :src="image">
</div>
</div>
- <div class="col-md-6">
+ <div class="col-md-8">
<h2>{{ title }}</h2>
<div class="text-muted">
- <p v-for="subtitle in subtitles"
- :key="subtitle"
- v-html="subtitle"
- >
- </p>
+ <slot name="subtitle"></slot>
</div>
+ <button v-if="primary_action" class="btn btn-primary" @click="primary_action.action">
+ {{ primary_action.label }}
+ </button>
+ </div>
+
+ <div v-if="menu_items" class="col-md-1">
+ <div class="dropdown pull-right hub-item-dropdown">
+ <a class="dropdown-toggle btn btn-xs btn-default" data-toggle="dropdown">
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu dropdown-right" role="menu">
+ <li v-for="menu_item in menu_items"
+ v-if="menu_item.condition"
+ :key="menu_item.label"
+ >
+ <a @click="menu_item.action">{{ menu_item.label }}</a>
+ </li>
+ </ul>
+ </div>
</div>
</div>
<div v-for="section in sections" class="row hub-item-description margin-bottom"
@@ -61,7 +76,7 @@
export default {
name: 'detail-view',
- props: ['title', 'subtitles', 'image', 'sections', 'show_skeleton'],
+ props: ['title', 'subtitles', 'image', 'sections', 'show_skeleton', 'menu_items', 'primary_action'],
data() {
return {
back_to_home_text: __('Back to Home')
diff --git a/erpnext/public/js/hub/components/ReviewTimelineItem.vue b/erpnext/public/js/hub/components/ReviewTimelineItem.vue
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/public/js/hub/components/ReviewTimelineItem.vue
diff --git a/erpnext/public/js/hub/marketplace.js b/erpnext/public/js/hub/marketplace.js
index 10a55aa..a442d4c 100644
--- a/erpnext/public/js/hub/marketplace.js
+++ b/erpnext/public/js/hub/marketplace.js
@@ -2,20 +2,10 @@
import './vue-plugins';
// pages
-import './pages/item';
import './pages/messages';
import './pages/buying_messages';
import PageContainer from './PageContainer.vue';
-import Home from './pages/Home.vue';
-import SavedProducts from './pages/SavedProducts.vue';
-import Publish from './pages/Publish.vue';
-import Category from './pages/Category.vue';
-import Search from './pages/Search.vue';
-import PublishedProducts from './pages/PublishedProducts.vue';
-import Profile from './pages/Profile.vue';
-import Seller from './pages/Seller.vue';
-import NotFound from './pages/NotFound.vue';
// components
import { ProfileDialog } from './components/profile_dialog';
@@ -199,7 +189,7 @@
}
if (route[1] === 'item' && route[2] && !this.subpages.item) {
- this.subpages.item = new erpnext.hub.Item(this.$body);
+ this.subpages.item = new erpnext.hub.ItemPage(this.$body);
}
if (route[1] === 'seller' && !this.subpages['seller']) {
@@ -282,167 +272,3 @@
});
}
}
-
-erpnext.hub.HomePage = class {
- constructor(parent) {
- this.$wrapper = $(`<div id="vue-area-home">`).appendTo($(parent));
-
- new Vue({
- render: h => h(Home)
- }).$mount('#vue-area-home');
- }
-
- show() {
- $('[data-page-name="home"]').show();
- }
-
- hide() {
- $('[data-page-name="home"]').hide();
- }
-}
-
-erpnext.hub.SavedProductsPage = class {
- constructor(parent) {
- this.$wrapper = $(`<div id="vue-area-saved">`).appendTo($(parent));
-
- new Vue({
- render: h => h(SavedProducts)
- }).$mount('#vue-area-saved');
- }
-
- show() {
- $('[data-page-name="saved-products"]').show();
- }
-
- hide() {
- $('[data-page-name="saved-products"]').hide();
- }
-}
-
-erpnext.hub.PublishPage = class {
- constructor(parent) {
- this.$wrapper = $(`<div id="vue-area">`).appendTo($(parent));
-
- new Vue({
- render: h => h(Publish)
- }).$mount('#vue-area');
- }
-
- show() {
- $('[data-page-name="publish"]').show();
- }
-
- hide() {
- $('[data-page-name="publish"]').hide();
- }
-
-}
-
-erpnext.hub.CategoryPage = class {
- constructor(parent) {
- this.$wrapper = $(`<div id="vue-area-category">`).appendTo($(parent));
-
- new Vue({
- render: h => h(Category)
- }).$mount('#vue-area-category');
- }
-
- show() {
- $('[data-page-name="category"]').show();
- }
-
- hide() {
- $('[data-page-name="category"]').hide();
- }
-}
-
-erpnext.hub.PublishedProductsPage = class {
- constructor(parent) {
- this.$wrapper = $(`<div id="vue-area-published-products">`).appendTo($(parent));
-
- new Vue({
- render: h => h(PublishedProducts)
- }).$mount('#vue-area-published-products');
- }
-
- show() {
- $('[data-page-name="published-products"]').show();
- }
-
- hide() {
- $('[data-page-name="published-products"]').hide();
- }
-}
-
-erpnext.hub.SearchPage = class {
- constructor(parent) {
- this.$wrapper = $(`<div id="vue-area-search">`).appendTo($(parent));
-
- new Vue({
- render: h => h(Search)
- }).$mount('#vue-area-search');
- }
-
- show() {
- $('[data-page-name="search"]').show();
- }
-
- hide() {
- $('[data-page-name="search"]').hide();
- }
-}
-
-erpnext.hub.ProfilePage = class {
- constructor(parent) {
- this.$wrapper = $(`<div id="vue-area-profile">`).appendTo($(parent));
-
- new Vue({
- render: h => h(Profile)
- }).$mount('#vue-area-profile');
- }
-
- show() {
- $('[data-page-name="profile"]').show();
- }
-
- hide() {
- $('[data-page-name="profile"]').hide();
- }
-}
-
-erpnext.hub.SellerPage = class {
- constructor(parent) {
- this.$wrapper = $(`<div id="vue-area-seller">`).appendTo($(parent));
-
- new Vue({
- render: h => h(Seller)
- }).$mount('#vue-area-seller');
- }
-
- show() {
- $('[data-page-name="seller"]').show();
- }
-
- hide() {
- $('[data-page-name="seller"]').hide();
- }
-}
-
-erpnext.hub.NotFoundPage = class {
- constructor(parent) {
- this.$wrapper = $(`<div id="vue-area-not-found">`).appendTo($(parent));
-
- new Vue({
- render: h => h(NotFound)
- }).$mount('#vue-area-not-found');
- }
-
- show() {
- $('[data-page-name="not-found"]').show();
- }
-
- hide() {
- $('[data-page-name="not-found"]').hide();
- }
-}
-
diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue
index 2a521f4..5a23870 100644
--- a/erpnext/public/js/hub/pages/Category.vue
+++ b/erpnext/public/js/hub/pages/Category.vue
@@ -17,13 +17,8 @@
</template>
<script>
-import ItemCardsContainer from '../components/ItemCardsContainer.vue';
-
export default {
name: 'saved-products-page',
- components: {
- ItemCardsContainer
- },
data() {
return {
page_name: frappe.get_route()[1],
diff --git a/erpnext/public/js/hub/pages/Home.vue b/erpnext/public/js/hub/pages/Home.vue
index 1d1973c..7019a62 100644
--- a/erpnext/public/js/hub/pages/Home.vue
+++ b/erpnext/public/js/hub/pages/Home.vue
@@ -27,17 +27,8 @@
</template>
<script>
-import SearchInput from '../components/SearchInput.vue';
-import SectionHeader from '../components/SectionHeader.vue';
-import ItemCardsContainer from '../components/ItemCardsContainer.vue';
-
export default {
name: 'home-page',
- components: {
- SectionHeader,
- SearchInput,
- ItemCardsContainer
- },
data() {
return {
page_name: frappe.get_route()[1],
diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue
new file mode 100644
index 0000000..fffd370
--- /dev/null
+++ b/erpnext/public/js/hub/pages/Item.vue
@@ -0,0 +1,150 @@
+<template>
+ <div
+ class="marketplace-page"
+ :data-page-name="page_name"
+ v-if="init || item"
+ >
+
+ <detail-view
+ :title="title"
+ :subtitles="subtitles"
+ :image="image"
+ :sections="sections"
+ :menu_items="menu_items"
+ :show_skeleton="init"
+ >
+ <detail-header-item slot="subtitle"
+ :value="item_subtitle"
+ ></detail-header-item>
+ <detail-header-item slot="subtitle"
+ :value="item_views_and_ratings"
+ ></detail-header-item>
+ </detail-view>
+ </div>
+</template>
+
+<script>
+import { get_rating_html } from '../components/reviews';
+
+export default {
+ name: 'item-page',
+ data() {
+ return {
+ page_name: frappe.get_route()[1],
+ hub_item_code: frappe.get_route()[2],
+
+ init: true,
+
+ item: null,
+ title: null,
+ subtitles: [],
+ image: null,
+ sections: [],
+
+ menu_items: [
+ {
+ label: __('Report this Product'),
+ condition: !this.is_own_item,
+ action: this.report_item
+ },
+ {
+ label: __('Edit Details'),
+ condition: this.is_own_item,
+ action: this.report_item
+ },
+ {
+ label: __('Unpublish Product'),
+ condition: this.is_own_item,
+ action: this.report_item
+ }
+ ]
+ };
+ },
+ computed: {
+ is_own_item() {
+ let is_own_item = false;
+ if(this.item) {
+ if(this.item.hub_seller === hub.setting.company_email) {
+ is_own_item = true;
+ }
+ }
+ return is_own_item;
+ },
+
+ item_subtitle() {
+ if(!this.item) {
+ return '';
+ }
+
+ const dot_spacer = '<span aria-hidden="true"> · </span>';
+ 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;
+ },
+
+ item_views_and_ratings() {
+ if(!this.item) {
+ return '';
+ }
+
+ let stats = __('No views yet');
+ if(this.item.view_count) {
+ const views_message = __(`${this.item.view_count} Views`);
+
+ const rating_html = get_rating_html(this.item.average_rating);
+ const rating_count = this.item.no_of_ratings > 0 ? `${this.item.no_of_ratings} reviews` : __('No reviews yet');
+
+ stats = [views_message, rating_html, rating_count];
+ }
+
+ return stats;
+ }
+ },
+ created() {
+ this.get_profile();
+ },
+ methods: {
+ get_profile() {
+ hub.call('get_item_details',{ hub_item_code: this.hub_item_code })
+ .then(item => {
+ this.init = false;
+ this.item = item;
+
+ this.build_data();
+ });
+ },
+
+ build_data() {
+ this.title = this.item.item_name || this.item.name;
+
+ this.image = this.item.image;
+
+ this.sections = [
+ {
+ title: __('Product Description'),
+ content: this.item.description
+ ? __(this.item.description)
+ : __('No description')
+ },
+ {
+ title: __('Seller Information'),
+ content: ''
+ }
+ ];
+ },
+
+ report_item(){
+ //
+ }
+ }
+}
+</script>
+
+<style scoped></style>
diff --git a/erpnext/public/js/hub/pages/NotFound.vue b/erpnext/public/js/hub/pages/NotFound.vue
index 7a76437..246d31b 100644
--- a/erpnext/public/js/hub/pages/NotFound.vue
+++ b/erpnext/public/js/hub/pages/NotFound.vue
@@ -14,13 +14,8 @@
</template>
<script>
-import EmptyState from '../components/EmptyState.vue';
-
export default {
name: 'not-found-page',
- components: {
- EmptyState
- },
data() {
return {
page_name: 'not-found',
diff --git a/erpnext/public/js/hub/pages/Profile.vue b/erpnext/public/js/hub/pages/Profile.vue
index 1767796..9bbfe83 100644
--- a/erpnext/public/js/hub/pages/Profile.vue
+++ b/erpnext/public/js/hub/pages/Profile.vue
@@ -17,13 +17,8 @@
</template>
<script>
-import DetailView from '../components/DetailView.vue';
-
export default {
name: 'profile-page',
- components: {
- DetailView
- },
data() {
return {
page_name: frappe.get_route()[1],
diff --git a/erpnext/public/js/hub/pages/Publish.vue b/erpnext/public/js/hub/pages/Publish.vue
index b801b92..788d7ba 100644
--- a/erpnext/public/js/hub/pages/Publish.vue
+++ b/erpnext/public/js/hub/pages/Publish.vue
@@ -54,13 +54,14 @@
</template>
<script>
-import SearchInput from '../components/SearchInput.vue';
-import ItemCardsContainer from '../components/ItemCardsContainer.vue';
import NotificationMessage from '../components/NotificationMessage.vue';
import { ItemPublishDialog } from '../components/item_publish_dialog';
export default {
name: 'publish-page',
+ components: {
+ NotificationMessage
+ },
data() {
return {
page_name: frappe.get_route()[1],
@@ -87,11 +88,6 @@
: ''
};
},
- components: {
- SearchInput,
- ItemCardsContainer,
- NotificationMessage
- },
computed: {
no_selected_items() {
return this.selected_items.length === 0;
diff --git a/erpnext/public/js/hub/pages/PublishedProducts.vue b/erpnext/public/js/hub/pages/PublishedProducts.vue
index db9057d..80fe8c0 100644
--- a/erpnext/public/js/hub/pages/PublishedProducts.vue
+++ b/erpnext/public/js/hub/pages/PublishedProducts.vue
@@ -17,13 +17,8 @@
</template>
<script>
-import ItemCardsContainer from '../components/ItemCardsContainer.vue';
-
export default {
name: 'saved-products-page',
- components: {
- ItemCardsContainer
- },
data() {
return {
page_name: frappe.get_route()[1],
diff --git a/erpnext/public/js/hub/pages/SavedProducts.vue b/erpnext/public/js/hub/pages/SavedProducts.vue
index f113a20..dd8b026 100644
--- a/erpnext/public/js/hub/pages/SavedProducts.vue
+++ b/erpnext/public/js/hub/pages/SavedProducts.vue
@@ -19,8 +19,6 @@
</template>
<script>
-import ItemCardsContainer from '../components/ItemCardsContainer.vue';
-
export default {
name: 'saved-products-page',
data() {
@@ -34,9 +32,6 @@
empty_state_message: __(`You haven't favourited any items yet.`)
};
},
- components: {
- ItemCardsContainer
- },
created() {
this.get_items();
},
diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue
index a7fbffc..fc4aff8 100644
--- a/erpnext/public/js/hub/pages/Search.vue
+++ b/erpnext/public/js/hub/pages/Search.vue
@@ -24,15 +24,8 @@
</template>
<script>
-import SearchInput from '../components/SearchInput.vue';
-import ItemCardsContainer from '../components/ItemCardsContainer.vue';
-
export default {
name: 'saved-products-page',
- components: {
- SearchInput,
- ItemCardsContainer
- },
data() {
return {
page_name: frappe.get_route()[1],
diff --git a/erpnext/public/js/hub/pages/Seller.vue b/erpnext/public/js/hub/pages/Seller.vue
index 5a35812..eb9cab6 100644
--- a/erpnext/public/js/hub/pages/Seller.vue
+++ b/erpnext/public/js/hub/pages/Seller.vue
@@ -25,15 +25,8 @@
</template>
<script>
-import DetailView from '../components/DetailView.vue';
-import ItemCardsContainer from '../components/ItemCardsContainer.vue';
-
export default {
name: 'seller-page',
- components: {
- DetailView,
- ItemCardsContainer
- },
data() {
return {
page_name: frappe.get_route()[1],
diff --git a/erpnext/public/js/hub/pages/messages.js b/erpnext/public/js/hub/pages/messages.js
index 6222f53..5f67502 100644
--- a/erpnext/public/js/hub/pages/messages.js
+++ b/erpnext/public/js/hub/pages/messages.js
@@ -2,7 +2,7 @@
// import { get_item_card_container_html } from '../components/items_container';
import { get_buying_item_message_card_html } from '../components/item_card';
import { get_selling_item_message_card_html } from '../components/item_card';
-import { get_empty_state } from '../components/empty_state';
+// import { get_empty_state } from '../components/empty_state';
erpnext.hub.Buying = class Buying extends SubPage {
refresh() {
@@ -27,8 +27,8 @@
}
render_empty_state() {
- const empty_state = get_empty_state(__('You haven\'t interacted with any seller yet.'));
- this.$wrapper.html(empty_state);
+ // const empty_state = get_empty_state(__('You haven\'t interacted with any seller yet.'));
+ // this.$wrapper.html(empty_state);
}
get_items_for_messages() {
diff --git a/erpnext/public/js/hub/vue-plugins.js b/erpnext/public/js/hub/vue-plugins.js
index 5555945..97bc66d 100644
--- a/erpnext/public/js/hub/vue-plugins.js
+++ b/erpnext/public/js/hub/vue-plugins.js
@@ -1,7 +1,23 @@
import Vue from 'vue/dist/vue.js';
+
+// Global components
+import ItemCardsContainer from './components/ItemCardsContainer.vue';
+import SectionHeader from './components/SectionHeader.vue';
+import SearchInput from './components/SearchInput.vue';
+import DetailView from './components/DetailView.vue';
+import DetailHeaderItem from './components/DetailHeaderItem.vue';
+import EmptyState from './components/EmptyState.vue';
+
Vue.prototype.__ = window.__;
Vue.prototype.frappe = window.frappe;
+Vue.component('item-cards-container', ItemCardsContainer);
+Vue.component('section-header', SectionHeader);
+Vue.component('search-input', SearchInput);
+Vue.component('detail-view', DetailView);
+Vue.component('detail-header-item', DetailHeaderItem);
+Vue.component('empty-state', EmptyState);
+
Vue.directive('route', {
bind(el, binding) {
const route = binding.value;