diff --git a/imports/plugins/core/orders/client/components/orderActions.js b/imports/plugins/core/orders/client/components/orderActions.js deleted file mode 100644 index 07cd88b24a6..00000000000 --- a/imports/plugins/core/orders/client/components/orderActions.js +++ /dev/null @@ -1,77 +0,0 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; -import Radium from "radium"; -import { TabList, TabItem } from "/imports/plugins/core/ui/client/components"; - -const styles = { - list: { - display: "flex", - height: 100 - }, - item: { - display: "flex", - flex: "1 1 auto", - width: "33%", - height: "100%", - justifyContent: "center", - alignItems: "center" - }, - title: { - fontSize: 24, - display: "block" - }, - stat: { - display: "flex", - flexDirection: "column", - height: "100%", - justifyContent: "center", - alignItems: "center" - } -}; - -class OrderActions extends Component { - static propTypes = { - filters: PropTypes.arrayOf(PropTypes.object), - onActionClick: PropTypes.func, - selectedIndex: PropTypes.number - } - - handleActionClick = (event, value) => { - if (typeof this.props.onActionClick === "function") { - this.props.onActionClick(value); - } - } - - renderAction() { - if (Array.isArray(this.props.filters)) { - return this.props.filters.map((filter, index) => { - return ( - -
- {filter.count} - {filter.label} -
-
- ); - }); - } - - return null; - } - - render() { - return ( - - {this.renderAction()} - - ); - } -} - -export default Radium(OrderActions); diff --git a/imports/plugins/core/orders/client/components/orderList.js b/imports/plugins/core/orders/client/components/orderList.js new file mode 100644 index 00000000000..59eba416e26 --- /dev/null +++ b/imports/plugins/core/orders/client/components/orderList.js @@ -0,0 +1,86 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { Icon, Translation } from "@reactioncommerce/reaction-ui"; +import OrderTable from "./orderTable"; + +class OrdersList extends Component { + static propTypes = { + displayMedia: PropTypes.func, + handleClick: PropTypes.func, + handleSelect: PropTypes.func, + multipleSelect: PropTypes.bool, + orders: PropTypes.array, + selectAllOrders: PropTypes.func, + selectedItems: PropTypes.array + } + + state = { + detailClassName: "", + listClassName: "order-icon-toggle", + openList: true + } + + handleListToggle = () => { + this.setState({ + detailClassName: "", + listClassName: "order-icon-toggle", + openList: true + }); + } + + handleDetailToggle = () => { + this.setState({ + detailClassName: "order-icon-toggle", + listClassName: "", + openList: false + }); + } + + render() { + if (this.props.orders.length) { + return ( +
+
+ + + +
+ +
+ +
+
+ ); + } + + return ( +
+
+ + +
+
+ ); + } +} + +export default OrdersList; diff --git a/imports/plugins/core/orders/client/components/orderTable.js b/imports/plugins/core/orders/client/components/orderTable.js new file mode 100644 index 00000000000..9e6299562f4 --- /dev/null +++ b/imports/plugins/core/orders/client/components/orderTable.js @@ -0,0 +1,384 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { Orders } from "/lib/collections"; +import { SortableTable, Loading, Checkbox } from "@reactioncommerce/reaction-ui"; +import OrderTableColumn from "./orderTableColumn"; + +import classnames from "classnames/dedupe"; +import Avatar from "react-avatar"; +import moment from "moment"; +import { formatPriceString } from "/client/api"; +import { Badge, ClickToCopy, Icon, Translation, Button } from "@reactioncommerce/reaction-ui"; +import ProductImage from "./productImage"; + +const classNames = { + colClassNames: { + "Name": "order-table-column-name", + "Email": "order-table-column-email hidden-xs hidden-sm", + "Date": "order-table-column-date hidden-xs hidden-sm", + "ID": "order-table-column-id hidden-xs", + "Total": "order-table-column-total hidden-xs", + "Shipping": "order-table-column-shipping hidden-xs hidden-sm", + "Status": "order-table-column-status", + "": "order-table-column-control" + }, + headerClassNames: { + "Name": "order-table-header-name", + "Email": "order-table-header-email hidden-xs hidden-sm", + "Date": "order-table-header-date hidden-xs hidden-sm", + "ID": "order-table-header-id hidden-xs", + "Total": "order-table-header-total hidden-xs", + "Shipping": "order-table-header-shipping hidden-xs hidden-sm", + "Status": "order-table-header-status", + "": "order-table-header-control" + } +}; + +class OrderTable extends Component { + static propTypes = { + displayMedia: PropTypes.func, + handleClick: PropTypes.func, + handleSelect: PropTypes.func, + isOpen: PropTypes.bool, + multipleSelect: PropTypes.bool, + orders: PropTypes.array, + selectAllOrders: PropTypes.func, + selectedItems: PropTypes.array + } + + /** + * Fullfilment Badge + * @param {Object} order object containing info for order and coreOrderWorkflow + * @return {string} A string containing the type of Badge + */ + fulfillmentBadgeStatus(order) { + const orderStatus = order.workflow.status; + + if (orderStatus === "new") { + return "info"; + } else if (orderStatus === "coreOrderWorkflow/processing") { + return "success"; + } else if (orderStatus === "coreOrderWorkflow/canceled") { + return "danger"; + } else if (orderStatus === "coreOrderWorkflow/completed") { + return "primary"; + } + + return "default"; + } + + /** + * Shipping Badge + * TODO: any logic here, we don't have shipping status changes at the moment + * @return {string} A string containing the type of Badge + */ + shippingBadgeStatus() { + return "basic"; + } + + renderOrderButton(order) { + const startWorkflow = order.workflow.status === "new"; + const classes = classnames({ + "rui": true, + "btn": true, + "btn-success": startWorkflow + }); + + return ( + + ); + } + + renderOrderInfo(order) { + const { displayMedia } = this.props; + + return ( +
+
+ + Date: + {moment(order.createdAt).fromNow()} | {moment(order.createdAt).format("MM/D/YYYY")} + + + + Order ID: + + + + + Total: {formatPriceString(order.billing[0].invoice.total)} + +
+ +
+ {order.items.map((item, i) => { + return ( +
+
+ +
+
+ ); + })} +
+
+ ); + } + + renderShipmentInfo(order) { + const emailAddress = order.email || ; + return ( +
+
+ + {order.shipping[0].address.fullName} | {emailAddress} +
+
+ + +
+
+ ); + } + + /** + * Render Sortable Table for the list view for orders + * @param {Object} orders object containing info for order + * @return {Component} SortableTable list of orders + */ + + renderOrderCard(order) { + return ( +
+
this.props.handleClick(order, false)}> + {this.renderShipmentInfo(order)} + {this.renderOrderInfo(order)} +
+
this.props.handleClick(order)}> + {this.renderOrderButton(order)} +
+
+ ); + } + + renderBulkOrderActionsBar = () => { + const { orders, multipleSelect, selectedItems, selectAllOrders } = this.props; + + if (selectedItems.length > 0) { + return ( +
+ selectAllOrders(orders, (selectedItems.length === orders.length || multipleSelect))} + /> + +
+ ); + } + } + + render() { + let getTrProps = undefined; + let getTheadProps = undefined; + let getTrGroupProps = undefined; + let getTableProps = undefined; + + const customColumnMetadata = []; + + if (this.props.isOpen) { + // Render order list column/row data + const filteredFields = { + "Name": "shipping[0].address.fullName", + "Email": "email", + "Date": "createdAt", + "ID": "_id", + "Total": "billing[0].invoice.total", + "Shipping": "shipping[0].workflow.status", + "Status": "workflow.status", + "": "" + }; + + const columnNames = Object.keys(filteredFields); + + getTheadProps = () => { + return { + className: "order-table-thead" + }; + }; + + getTrGroupProps = () => { + return { + className: "order-table-tr-group" + }; + }; + + getTableProps = () => { + return { + className: "order-table-list" + }; + }; + + // https://react-table.js.org/#/story/cell-renderers-custom-components + columnNames.forEach((columnName) => { + let colHeader = undefined; + let resizable = true; + let sortable = true; + + // Add custom styles for the column name `name` + if (columnName === "Name") { + colHeader = () => ( +
+ this.props.selectAllOrders(this.props.orders, this.props.multipleSelect)} + /> + {columnName} +
+ ); + } + + if (columnName === "") { + resizable = false; + sortable = false; + } + + const columnMeta = { + accessor: filteredFields[columnName], + Header: colHeader ? colHeader : columnName, + headerClassName: classNames.headerClassNames[columnName], + className: classNames.colClassNames[columnName], + resizable: resizable, + sortable: sortable, + Cell: row => ( + + ) + }; + customColumnMetadata.push(columnMeta); + }); + } else { + // Render order detail column/row dat + + const columnMeta = { + Cell: row => (
{this.renderOrderCard(row.original)}
) + }; + + customColumnMetadata.push(columnMeta); + + getTheadProps = () => { + return { + className: "hidden" + }; + }; + + getTrGroupProps = () => { + return { + className: "order-table-details-tr-group" + }; + }; + + getTableProps = () => { + return { + className: "order-table-detail" + }; + }; + + getTrProps = () => { + return { + className: "order-table-detail-tr" + }; + }; + } + + return ( +
+ {this.props.isOpen && this.renderBulkOrderActionsBar()} + 0 ? + "table-header-hidden" : + "table-header-visible"}` + } + publication="CustomPaginatedOrders" + collection={Orders} + matchingResultsCount="order-count" + columnMetadata={customColumnMetadata} + externalLoadingComponent={Loading} + filterType="none" + selectedRows={this.props.selectedItems} + getTheadProps={getTheadProps} + getTrProps={getTrProps} + getTrGroupProps={getTrGroupProps} + getPaginationProps={() => { + return { + className: this.props.isOpen && this.props.selectedItems.length > 0 ? + "order-table-pagination-hidden" : + "order-table-pagination-visible" + }; + }} + getTableProps={getTableProps} + showPaginationTop={true} + /> +
+ ); + } +} + +export default OrderTable; diff --git a/imports/plugins/core/orders/client/components/orderTableColumn.js b/imports/plugins/core/orders/client/components/orderTableColumn.js new file mode 100644 index 00000000000..2f0f7235753 --- /dev/null +++ b/imports/plugins/core/orders/client/components/orderTableColumn.js @@ -0,0 +1,117 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import classnames from "classnames/dedupe"; +import moment from "moment"; +import { formatPriceString } from "/client/api"; +import Avatar from "react-avatar"; +import { Badge, ClickToCopy, Icon, RolloverCheckbox } from "@reactioncommerce/reaction-ui"; + +class OrderTableColumn extends Component { + static propTypes = { + fulfillmentBadgeStatus: PropTypes.func, + handleClick: PropTypes.func, + handleSelect: PropTypes.func, + row: PropTypes.object, + selectedItems: PropTypes.array, + shippingBadgeStatus: PropTypes.func + } + + render() { + const { row, selectedItems, handleClick, handleSelect, fulfillmentBadgeStatus, shippingBadgeStatus } = this.props; + const columnAccessor = row.column.id; + + if (columnAccessor === "shipping[0].address.fullName") { + return ( +
+ + + + {row.value} +
+ ); + } + if (columnAccessor === "email") { + return ( +
{row.value}
+ ); + } + if (columnAccessor === "createdAt") { + const createdDate = moment(row.value).format("MM/D/YYYY"); + return ( +
{createdDate}
+ ); + } + if (columnAccessor === "_id") { + const id = row.original._id; + const truncatedId = id.substring(0, 4); + return ( +
+ +
+ ); + } + if (columnAccessor === "billing[0].invoice.total") { + return ( +
+ {formatPriceString(row.original.billing[0].invoice.total)} +
+ ); + } + if (columnAccessor === "shipping[0].workflow.status") { + return ( + + ); + } + if (columnAccessor === "workflow.status") { + return ( + + ); + } + if (columnAccessor === "") { + const startWorkflow = row.original.workflow.status === "new"; + const classes = classnames({ + "rui": true, + "btn": true, + "btn-success": startWorkflow + }); + + return ( + + ); + } + return ( + {row.value} + ); + } +} + +export default OrderTableColumn; diff --git a/imports/plugins/core/orders/client/components/ordersList.js b/imports/plugins/core/orders/client/components/ordersList.js deleted file mode 100644 index 15892eb0fbd..00000000000 --- a/imports/plugins/core/orders/client/components/ordersList.js +++ /dev/null @@ -1,186 +0,0 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; -import classnames from "classnames/dedupe"; -import Avatar from "react-avatar"; -import moment from "moment"; -import { formatPriceString } from "/client/api"; -import { Badge, ClickToCopy, Icon, Translation } from "@reactioncommerce/reaction-ui"; -import ProductImage from "./productImage"; - -class OrdersList extends Component { - static propTypes = { - displayMedia: PropTypes.func, - handleClick: PropTypes.func, - orders: PropTypes.array - } - - /** - * Fullfilment Badge - * @param {Object} order object containing info for order and coreOrderWorkflow - * @return {string} A string containing the type of Badge - */ - fulfillmentBadgeStatus(order) { - const orderStatus = order.workflow.status; - - if (orderStatus === "new") { - return "info"; - } else if (orderStatus === "coreOrderWorkflow/processing") { - return "success"; - } else if (orderStatus === "coreOrderWorkflow/canceled") { - return "danger"; - } else if (orderStatus === "coreOrderWorkflow/completed") { - return "primary"; - } - - return "default"; - } - - /** - * Shipping Badge - * TODO: any logic here, we don't have shipping status changes at the moment - * @return {string} A string containing the type of Badge - */ - shippingBadgeStatus() { - return "basic"; - } - - renderOrderButton(order) { - const startWorkflow = order.workflow.status === "new"; - const classes = classnames({ - "rui": true, - "btn": true, - "btn-success": startWorkflow - }); - - return ( - - ); - } - - renderOrderInfo(order) { - const { displayMedia } = this.props; - - return ( -
-
- - Date: - {moment(order.createdAt).fromNow()} | {moment(order.createdAt).format("MM/D/YYYY")} - - - - Order ID: - - - - - Total: {formatPriceString(order.billing[0].invoice.total)} - -
- -
- {order.items.map((item, i) => { - return ( -
-
- -
-
- ); - })} -
-
- ); - } - - renderShipmentInfo(order) { - const emailAddress = order.email || ; - return ( -
-
- - {order.shipping[0].address.fullName} | {emailAddress} -
-
- - -
-
- ); - } - - - renderOrderCard(order) { - const { handleClick } = this.props; - - return ( -
-
handleClick(order, false)}> - {this.renderShipmentInfo(order)} - {this.renderOrderInfo(order)} -
-
handleClick(order)}> - {this.renderOrderButton(order)} -
-
- ); - } - - - render() { - const { orders } = this.props; - - if (orders.length) { - return ( -
- {orders.map((order, i) => { - return ( -
- {this.renderOrderCard(order)} -
- ); - })} -
- ); - } - - return ( -
-
- - -
-
- ); - } -} - -export default OrdersList; diff --git a/imports/plugins/core/orders/client/containers/ordersActionContainer.js b/imports/plugins/core/orders/client/containers/ordersActionContainer.js deleted file mode 100644 index b07bada34ef..00000000000 --- a/imports/plugins/core/orders/client/containers/ordersActionContainer.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from "react"; -import { composeWithTracker } from "@reactioncommerce/reaction-components"; -import { Meteor } from "meteor/meteor"; -import { Counts } from "meteor/tmeasday:publish-counts"; -import { Reaction, i18next } from "/client/api"; -import OrderActions from "../components/orderActions"; -import * as Constants from "../../lib/constants"; - -function handleActionClick(filter) { - Reaction.pushActionView({ - provides: "dashboard", - template: "orders", - data: { - filter - } - }); -} - -function composer(props, onData) { - const selectedFilterName = Reaction.getUserPreferences(Constants.PACKAGE_NAME, Constants.ORDER_LIST_FILTERS_PREFERENCE_NAME); - let selectedIndex; - - Meteor.subscribe("PaginatedOrders", selectedFilterName, (props.limit || 0)); - - const filters = Constants.orderFilters.map((filter, index) => { - if (filter.name === selectedFilterName) { - selectedIndex = index; - } - - filter.label = i18next.t(`order.filter.${filter.name}`, { defaultValue: filter.label }); - filter.i18nKeyLabel = `order.filter.${filter.name}`; - - if (filter.name === "new") { - filter.count = Counts.get("newOrder-count"); - } else if (filter.name === "processing") { - filter.count = Counts.get("processingOrder-count"); - } else if (filter.name === "completed") { - filter.count = Counts.get("completedOrder-count"); - } - - return filter; - }); - - onData(null, { - filters, - selectedIndex, - - onActionClick: props.onActionClick || handleActionClick - }); -} - -function OrdersActionContainer(props) { - return ( - - ); -} - -export default composeWithTracker(composer)(OrdersActionContainer); diff --git a/imports/plugins/core/orders/client/containers/ordersContainer.js b/imports/plugins/core/orders/client/containers/ordersContainer.js new file mode 100644 index 00000000000..e0c33ceeaa4 --- /dev/null +++ b/imports/plugins/core/orders/client/containers/ordersContainer.js @@ -0,0 +1,83 @@ +import React, { Component } from "react"; +import { Meteor } from "meteor/meteor"; +import { Tracker } from "meteor/tracker"; +import { Counts } from "meteor/tmeasday:publish-counts"; +import { Orders, Shops } from "/lib/collections"; +import OrdersListContainer from "./ordersListContainer"; + +class OrdersContainer extends Component { + constructor() { + super(); + this.state = { + orders: [], + count: 0, + limit: 10, + currency: {}, + ready: false + }; + + this.hasMoreOrders = this.hasMoreOrders.bind(this); + this.showMoreOrders = this.showMoreOrders.bind(this); + this.dep = new Tracker.Dependency; + } + + componentDidMount() { + Tracker.autorun(() => { + this.dep.depend(); + this.subscription = Meteor.subscribe("CustomPaginatedOrders"); + + if (this.subscription.ready()) { + const orders = Orders.find().fetch(); + this.setState({ + orders: orders, + count: Counts.get("order-count"), + ready: true + }); + } + + const shop = Shops.findOne({}); + + // Update currency information, this is passed to child components containing + // Numeric inputs + this.setState({ + currency: shop.currencies[shop.currency] + }); + }); + } + + componentWillUnmount() { + this.subscription.stop(); + } + + hasMoreOrders = () => { + return this.state.count > this.state.limit; + } + + showMoreOrders = (event) => { + event.preventDefault(); + let limit = this.state.limit; + limit += 10; + + this.setState({ + limit: limit + }, () => { + this.dep.changed(); + }); + } + + render() { + if (this.state.ready) { + return ( + + ); + } + return null; + } +} + +export default OrdersContainer; diff --git a/imports/plugins/core/orders/client/containers/ordersListContainer.js b/imports/plugins/core/orders/client/containers/ordersListContainer.js index 95d51102b94..1df7d76835d 100644 --- a/imports/plugins/core/orders/client/containers/ordersListContainer.js +++ b/imports/plugins/core/orders/client/containers/ordersListContainer.js @@ -4,7 +4,8 @@ import { composeWithTracker } from "@reactioncommerce/reaction-components"; import { Meteor } from "meteor/meteor"; import { Media } from "/lib/collections"; import { Reaction } from "/client/api"; -import OrdersList from "../components/ordersList.js"; +import { Loading } from "/imports/plugins/core/ui/client/components"; +import OrdersList from "../components/orderList.js"; import { PACKAGE_NAME, ORDER_LIST_FILTERS_PREFERENCE_NAME, @@ -14,6 +15,8 @@ import { class OrdersListContainer extends Component { static propTypes = { + handleShowMoreClick: PropTypes.func, + hasMoreOrders: PropTypes.func, invoice: PropTypes.object, orders: PropTypes.array, uniqueItems: PropTypes.array @@ -22,8 +25,69 @@ class OrdersListContainer extends Component { constructor(props) { super(props); + this.state = { + selectedItems: [], + orders: props.orders, + hasMoreOrders: props.hasMoreOrders(), + multipleSelect: false + }; + this.handleClick = this.handleClick.bind(this); this.handleDisplayMedia = this.handleDisplayMedia.bind(this); + this.handleSelect = this.handleSelect.bind(this); + this.selectAllOrders = this.selectAllOrders.bind(this); + } + + componentWillReceiveProps = (nextProps) => { + this.setState({ + orders: nextProps.orders, + hasMoreOrders: nextProps.hasMoreOrders() + }); + } + + handleSelect = (event, isInputChecked, name) => { + this.setState({ + multipleSelect: false + }); + const selectedItemsArray = this.state.selectedItems; + + if (!selectedItemsArray.includes(name)) { + selectedItemsArray.push(name); + this.setState({ + selectedItems: selectedItemsArray + }); + } else { + const updatedSelectedArray = selectedItemsArray.filter((id) => { + if (id !== name) { + return id; + } + }); + this.setState({ + selectedItems: updatedSelectedArray + }); + } + } + + selectAllOrders = (orders, areAllSelected) => { + if (areAllSelected) { + // if all orders are selected, clear the selectedItems array + // and set multipleSelect to false + this.setState({ + selectedItems: [], + multipleSelect: false + }); + } else { + // if there are no selected orders, or if there are some orders that have been + // selected but not all of them, loop through the orders array and return a + // new array with order ids only, then set the selectedItems array with the orderIds + const orderIds = orders.map((order) => { + return order._id; + }); + this.setState({ + selectedItems: orderIds, + multipleSelect: true + }); + } } handleClick = (order, startWorkflow = false) => { @@ -77,13 +141,18 @@ class OrdersListContainer extends Component { } render() { - const { orders } = this.props; + const { handleShowMoreClick } = this.props; return ( ); } @@ -99,4 +168,4 @@ const composer = (props, onData) => { } }; -export default composeWithTracker(composer)(OrdersListContainer); +export default composeWithTracker(composer, Loading)(OrdersListContainer); diff --git a/imports/plugins/core/orders/client/index.js b/imports/plugins/core/orders/client/index.js index f222067f292..f52800d3bb7 100644 --- a/imports/plugins/core/orders/client/index.js +++ b/imports/plugins/core/orders/client/index.js @@ -1,6 +1,3 @@ -import { registerComponent } from "/imports/plugins/core/layout/lib/components"; - - import "./templates/list/items.html"; import "./templates/list/items.js"; import "./templates/list/ordersList.html"; @@ -27,11 +24,3 @@ import "./templates/workflow/workflow.js"; import "./templates/orders.html"; import "./templates/orders.js"; - -import OrdersActionContainer from "./containers/ordersActionContainer"; - -// Register PDP components and some others -registerComponent({ - name: "orders_ActionDashboard", - component: OrdersActionContainer -}); diff --git a/imports/plugins/core/orders/client/templates/orders.html b/imports/plugins/core/orders/client/templates/orders.html index e291cef0cb1..8149a8ead38 100644 --- a/imports/plugins/core/orders/client/templates/orders.html +++ b/imports/plugins/core/orders/client/templates/orders.html @@ -1,13 +1,7 @@ diff --git a/imports/plugins/core/orders/client/templates/orders.js b/imports/plugins/core/orders/client/templates/orders.js index 067c3e47c3a..a6b07fce627 100644 --- a/imports/plugins/core/orders/client/templates/orders.js +++ b/imports/plugins/core/orders/client/templates/orders.js @@ -1,207 +1,10 @@ -import _ from "lodash"; import { Template } from "meteor/templating"; -import { ReactiveDict } from "meteor/reactive-dict"; -import { Counts } from "meteor/tmeasday:publish-counts"; -import { Reaction } from "/client/api"; -import { Orders, Shops } from "/lib/collections"; -import OrdersActionContainer from "../containers/ordersActionContainer"; -import OrdersListContainer from "../containers/ordersListContainer"; -import { - PACKAGE_NAME, - ORDER_LIST_FILTERS_PREFERENCE_NAME, - ORDER_LIST_SELECTED_ORDER_PREFERENCE_NAME, - DEFAULT_FILTER_NAME, - orderFilters -} from "../../lib/constants"; +import OrdersContainer from "../containers/ordersContainer"; -const OrderHelper = { - makeQuery(filter) { - let query = {}; - - switch (filter) { - // New orders - case "new": - query = { - "workflow.status": "new" - }; - break; - - // Orders that have yet to be captured & shipped - case "processing": - query = { - "workflow.status": "coreOrderWorkflow/processing" - }; - break; - - // Orders that have been shipped, based on if the items have been shipped - case "shipped": - query = { - "items.workflow.status": "coreOrderItemWorkflow/shipped" - }; - break; - - // Orders that are complete, including all items with complete status - case "completed": - query = { - "workflow.status": { - $in: ["coreOrderWorkflow/completed", "coreOrderWorkflow/canceled"] - }, - "items.workflow.status": { - $in: ["coreOrderItemWorkflow/completed", "coreOrderItemWorkflow/canceled"] - } - }; - break; - - // Orders that have been captured, but not yet shipped - case "captured": - query = { - "billing.paymentMethod.status": "completed", - "shipping.shipped": false - }; - break; - - case "canceled": - query = { - "workflow.status": "coreOrderWorkflow/canceled" - }; - break; - - // Orders that have been refunded partially or fully - case "refunded": - query = { - "billing.paymentMethod.status": "captured", - "shipping.shipped": true - }; - break; - default: - } - - return query; - } -}; - -Template.orders.onCreated(function () { - this.state = new ReactiveDict(); - this.orderLimits = new ReactiveDict(); - this.state.setDefault({ - orders: [] - }); - this.orderLimits.setDefault({ - new: 10, - processing: 10, - completed: 10 - }); - this.state.set("count", 0); - - const filterName = this.data && this.data.filter && this.data.filter.name || "new"; - Reaction.setUserPreferences(PACKAGE_NAME, ORDER_LIST_FILTERS_PREFERENCE_NAME, filterName); - - this.autorun(() => { - const filter = Reaction.getUserPreferences(PACKAGE_NAME, ORDER_LIST_FILTERS_PREFERENCE_NAME, DEFAULT_FILTER_NAME); - const limit = this.orderLimits.get(filter); - const query = OrderHelper.makeQuery(filter); - this.subscription = this.subscribe("PaginatedOrders", filter, limit); - - if (this.subscription.ready()) { - const orders = Orders.find(query, { limit: limit }).fetch(); - this.state.set("orders", orders); - } - }); - - // Watch for updates to shop collection - this.autorun(() => { - const shop = Shops.findOne({}); - - // Update currency information, this is passed to child components containing - // Numeric inputs - this.state.set("currency", shop.currencies[shop.currency]); - }); -}); - -/** - * orders helpers - */ Template.orders.helpers({ - orderSubscription() { - return Template.instance().subscription.ready(); - }, - FilterComponent() { - const orderFilter = Reaction.getUserPreferences(PACKAGE_NAME, ORDER_LIST_FILTERS_PREFERENCE_NAME, DEFAULT_FILTER_NAME); - return { - component: OrdersActionContainer, - limit: Template.instance().orderLimits.get(orderFilter), - onActionClick(filter) { - Reaction.setUserPreferences(PACKAGE_NAME, ORDER_LIST_FILTERS_PREFERENCE_NAME, filter.name); - Reaction.setUserPreferences(PACKAGE_NAME, ORDER_LIST_SELECTED_ORDER_PREFERENCE_NAME, null); - } - }; - }, - OrdersListComponent() { - const orderFilter = Reaction.getUserPreferences(PACKAGE_NAME, ORDER_LIST_FILTERS_PREFERENCE_NAME, DEFAULT_FILTER_NAME); - return { - component: OrdersListContainer, - limit: Template.instance().orderLimits.get(orderFilter), - orders: Template.instance().state.get("orders") || false, - onActionClick(filter) { - Reaction.setUserPreferences(PACKAGE_NAME, ORDER_LIST_FILTERS_PREFERENCE_NAME, filter.name); - Reaction.setUserPreferences(PACKAGE_NAME, ORDER_LIST_SELECTED_ORDER_PREFERENCE_NAME, null); - } - }; - }, - itemProps(order) { + ordersComponent() { return { - order, - currencyFormat: Template.instance().state.get("currency") + component: OrdersContainer }; - }, - - orders() { - return Template.instance().state.get("orders") || false; - }, - - hasMoreOrders() { - const filter = Reaction.getUserPreferences(PACKAGE_NAME, ORDER_LIST_FILTERS_PREFERENCE_NAME, DEFAULT_FILTER_NAME); - const instance = Template.instance(); - - if (filter === "new") { - instance.state.set("count", Counts.get("newOrder-count")); - } else if (filter === "processing") { - instance.state.set("count", Counts.get("processingOrder-count")); - } else if (filter === "completed") { - instance.state.set("count", Counts.get("completedOrder-count")); - } - - return instance.state.get("count") > instance.orderLimits.get(filter); - }, - - currentFilterLabel() { - const foundFilter = _.find(orderFilters, (filter) => { - return filter.name === Reaction.Router.getQueryParam("filter"); - }); - - if (foundFilter) { - return foundFilter.label; - } - - return ""; - }, - activeClassname(orderId) { - if (Reaction.Router.getQueryParam("_id") === orderId) { - return "panel-info"; - } - return "panel-default"; - } -}); - -/** - * orders events - */ -Template.orders.events({ - "click .show-more-orders": function (event, instance) { - event.preventDefault(); - const filter = Reaction.getUserPreferences(PACKAGE_NAME, ORDER_LIST_FILTERS_PREFERENCE_NAME, DEFAULT_FILTER_NAME); - let limit = instance.orderLimits.get(filter); - limit += 10; - instance.orderLimits.set(filter, limit); } }); diff --git a/imports/plugins/core/ui/client/components/badge/__tests__/__snapshots__/badge.js.snap b/imports/plugins/core/ui/client/components/badge/__tests__/__snapshots__/badge.js.snap index 8f9afbdd18e..87d539bea63 100644 --- a/imports/plugins/core/ui/client/components/badge/__tests__/__snapshots__/badge.js.snap +++ b/imports/plugins/core/ui/client/components/badge/__tests__/__snapshots__/badge.js.snap @@ -2,7 +2,7 @@ exports[`Badge snapshot test 1`] = ` diff --git a/imports/plugins/core/ui/client/components/checkbox/checkbox.js b/imports/plugins/core/ui/client/components/checkbox/checkbox.js index 7fa067993f5..6b891aabfca 100644 --- a/imports/plugins/core/ui/client/components/checkbox/checkbox.js +++ b/imports/plugins/core/ui/client/components/checkbox/checkbox.js @@ -16,8 +16,10 @@ class Checkbox extends Component { @@ -31,10 +33,12 @@ Checkbox.defaultProps = { Checkbox.propTypes = { checked: PropTypes.bool, + className: PropTypes.string, i18nKeyLabel: PropTypes.string, label: PropTypes.string, name: PropTypes.string, - onChange: PropTypes.func + onChange: PropTypes.func, + onMouseOut: PropTypes.func }; registerComponent("Checkbox", Checkbox); diff --git a/imports/plugins/core/ui/client/components/checkbox/index.js b/imports/plugins/core/ui/client/components/checkbox/index.js index d7ffe3a0e02..ae685201fb6 100644 --- a/imports/plugins/core/ui/client/components/checkbox/index.js +++ b/imports/plugins/core/ui/client/components/checkbox/index.js @@ -1 +1,2 @@ export { default as Checkbox } from "./checkbox"; +export { default as RolloverCheckbox } from "./rolloverCheckbox"; diff --git a/imports/plugins/core/ui/client/components/checkbox/rolloverCheckbox.js b/imports/plugins/core/ui/client/components/checkbox/rolloverCheckbox.js new file mode 100644 index 00000000000..982e07b6320 --- /dev/null +++ b/imports/plugins/core/ui/client/components/checkbox/rolloverCheckbox.js @@ -0,0 +1,45 @@ +import React from "react"; +import PropTypes from "prop-types"; +import Checkbox from "./checkbox"; + +const RolloverCheckbox = ({ children, checked, checkboxClassName, onChange, name }) => { + if (checked) { + return ( +
+
+ +
+
+ ); + } + return ( +
+
+ { children } +
+
+ +
+
+ ); +}; + +RolloverCheckbox.propTypes = { + checkboxClassName: PropTypes.string, + checked: PropTypes.bool, + children: PropTypes.node, + name: PropTypes.string, + onChange: PropTypes.func +}; + +export default RolloverCheckbox; diff --git a/imports/plugins/core/ui/client/components/index.js b/imports/plugins/core/ui/client/components/index.js index 2a2c14b7ae7..ad9b84aaad0 100644 --- a/imports/plugins/core/ui/client/components/index.js +++ b/imports/plugins/core/ui/client/components/index.js @@ -19,7 +19,7 @@ export * from "./cards"; export { MediaGallery, MediaItem } from "./media"; export { default as FlatButton } from "./button/flatButton"; export { SortableTable, SortableTableLegacy } from "./table"; -export { Checkbox } from "./checkbox"; +export { Checkbox, RolloverCheckbox } from "./checkbox"; export { default as Loading } from "./loading/loading"; export * from "./forms"; export * from "./toolbar"; diff --git a/imports/plugins/core/ui/client/components/table/sortableTable.js b/imports/plugins/core/ui/client/components/table/sortableTable.js index 7d737c9de19..4d4c63deb07 100644 --- a/imports/plugins/core/ui/client/components/table/sortableTable.js +++ b/imports/plugins/core/ui/client/components/table/sortableTable.js @@ -136,7 +136,7 @@ class SortableTable extends Component { * @returns {Object} data filed (string), translated header (string), and minWidth (number / undefined) */ renderData() { - const { filteredFields } = this.props; + const { filteredFields, filterType } = this.props; const { filterInput } = this.state; let originalData = []; @@ -145,8 +145,12 @@ class SortableTable extends Component { originalData = this.getMeteorData().results; } - const filteredData = matchSorter(originalData, filterInput, { keys: filteredFields }); - return filteredData; + if (filterType === "both" || filterType === "table") { + const filteredData = matchSorter(originalData, filterInput, { keys: filteredFields }); + return filteredData; + } + + return originalData; } @@ -187,6 +191,24 @@ class SortableTable extends Component { return null; } + /** + * selectedRowsClassName() - if any rows are selected, give them a className of "selected-row" + * @param {object} rowInfo row data passed in from ReactTable + * @returns {String} className to apply to row that is selected, or empty string if no row is selected + */ + selectedRowsClassName(rowInfo) { + const { selectedRows } = this.props; + let className = ""; + + if (selectedRows && selectedRows.length) { + if (selectedRows.includes(rowInfo.row._id)) { + className = "selected-row"; + } + } + + return className; + } + renderPaginationBottom = () => { if (this.getMeteorData().matchingResults === 0) { return false; @@ -203,15 +225,16 @@ class SortableTable extends Component { return 0; } - render() { const { ...otherProps } = this.props; + const defaultClassName = "-striped -highlight"; + // All available props: https://github.com/tannerlinsley/react-table#props return (
{this.renderTableFilter()} { // eslint-disable-line no-unused-vars + if (otherProps.getTrProps) { + return otherProps.getTrProps(); + } + return { onClick: e => { // eslint-disable-line no-unused-vars this.handleClick(rowInfo); - } + }, + className: this.selectedRowsClassName(rowInfo) }; }} + getTableProps={otherProps.getTableProps} + getTrGroupProps={otherProps.getTrGroupProps} + getTheadProps={otherProps.getTheadProps} + getPaginationProps={otherProps.getPaginationProps} />
); @@ -270,6 +304,8 @@ SortableTable.propTypes = { publication: PropTypes.string, /** @type {object} query provides query for publication filtering */ query: PropTypes.object, + /** @type {array} selectedRows provides selected rows in the table */ + selectedRows: PropTypes.array, /** @type {function} transform transform of collection for grid results */ transform: PropTypes.func }; @@ -289,6 +325,14 @@ SortableTable.defaultProps = { pageText: "Page", ofText: "of", rowsText: "rows" + // noDataMessage: , + // previousText: , + // nextText: , + // loadingText: , + // noDataText: , + // pageText: , + // ofText: , + // rowsText: }; export default SortableTable; diff --git a/imports/plugins/core/ui/client/components/table/sortableTableComponents/pagination.js b/imports/plugins/core/ui/client/components/table/sortableTableComponents/pagination.js index a6b2b8e22e8..7eda6499b87 100644 --- a/imports/plugins/core/ui/client/components/table/sortableTableComponents/pagination.js +++ b/imports/plugins/core/ui/client/components/table/sortableTableComponents/pagination.js @@ -21,18 +21,20 @@ class SortableTablePagination extends Component { this.setState({ page: nextProps.page }); } - getSafePage(page) { + getSafePage(safePage) { + let page = safePage; if (isNaN(page)) { - page = this.props.page; // eslint-disable-line + page = this.props.page; } return Math.min(Math.max(page, 0), this.props.pages - 1); } changePage(page) { - page = this.getSafePage(page); // eslint-disable-line - this.setState({ page }); - if (this.props.page !== page) { - this.props.onPageChange(page); + let safePage = page; + safePage = this.getSafePage(page); + this.setState({ page: safePage }); + if (this.props.page !== safePage) { + this.props.onPageChange(safePage); } } @@ -74,11 +76,11 @@ class SortableTablePagination extends Component { type={this.state.page === "" ? "text" : "number"} onChange={e => { const val = e.target.value; - const page = val - 1; // eslint-disable-line + const currentPage = val - 1; if (val === "") { return this.setState({ page: val }); } - this.setState({ page: this.getSafePage(page) }); + this.setState({ page: this.getSafePage(currentPage) }); }} value={this.state.page === "" ? "" : this.state.page + 1} onBlur={this.applyPage} @@ -112,9 +114,8 @@ class SortableTablePagination extends Component {
{ // eslint-disable-line no-unused-vars - if (!canPrevious) { - return this.changePage(page - 1); - } + if (!canPrevious) return; + this.changePage(page - 1); }} disabled={!canPrevious} > @@ -125,9 +126,8 @@ class SortableTablePagination extends Component {
{ // eslint-disable-line no-unused-vars - if (!canNext) { - return this.changePage(page + 1); - } + if (!canNext) return; + this.changePage(page + 1); }} disabled={!canNext} > @@ -144,7 +144,7 @@ SortableTablePagination.propTypes = { PreviousComponent: PropTypes.func, canNext: PropTypes.bool, canPrevious: PropTypes.bool, - className: PropTypes.object, + className: PropTypes.string, nextText: PropTypes.string, ofText: PropTypes.string, onPageChange: PropTypes.func, diff --git a/imports/plugins/core/ui/client/components/table/sortableTableLegacy.js b/imports/plugins/core/ui/client/components/table/sortableTableLegacy.js index e810facc955..8795fdc8a84 100644 --- a/imports/plugins/core/ui/client/components/table/sortableTableLegacy.js +++ b/imports/plugins/core/ui/client/components/table/sortableTableLegacy.js @@ -3,17 +3,6 @@ import PropTypes from "prop-types"; import { TacoTable } from "react-taco-table"; class SortableTableLegacy extends Component { - constructor(props) { - super(props); - this.handleRowClick = this.handleRowClick.bind(this); - } - - handleRowClick = (event, value) => { - if (this.props.onRowClick) { - this.props.onRowClick(event, value); - } - } - render() { const { data, columns } = this.props; @@ -21,7 +10,6 @@ class SortableTableLegacy extends Component { ); @@ -30,8 +18,7 @@ class SortableTableLegacy extends Component { SortableTableLegacy.propTypes = { columns: PropTypes.array, - data: PropTypes.array, - onRowClick: PropTypes.func + data: PropTypes.array }; export default SortableTableLegacy; diff --git a/imports/plugins/core/ui/client/components/translation/translation.js b/imports/plugins/core/ui/client/components/translation/translation.js index 3023f4df07b..438703f5d4d 100644 --- a/imports/plugins/core/ui/client/components/translation/translation.js +++ b/imports/plugins/core/ui/client/components/translation/translation.js @@ -4,16 +4,17 @@ import PropTypes from "prop-types"; import { registerComponent } from "@reactioncommerce/reaction-components"; import { i18next } from "/client/api"; -const Translation = ({ i18nKey, defaultValue }) => { +const Translation = ({ i18nKey, defaultValue, className }) => { const key = i18nKey || camelCase(defaultValue); const translation = i18next.t(key, { defaultValue }); return ( - {translation} + {translation} ); }; Translation.propTypes = { + className: PropTypes.string, defaultValue: PropTypes.string, i18nKey: PropTypes.string }; diff --git a/imports/plugins/included/default-theme/client/styles/dashboard/orders.less b/imports/plugins/included/default-theme/client/styles/dashboard/orders.less index f88e3b44d55..55f3efe68cf 100644 --- a/imports/plugins/included/default-theme/client/styles/dashboard/orders.less +++ b/imports/plugins/included/default-theme/client/styles/dashboard/orders.less @@ -134,6 +134,19 @@ .order-items { list-style-type: none; + margin-bottom: 0px; + padding-top: 10px; + cursor: pointer; + + &.invoice-item { + padding-top: 8px; + height: 56px; + margin-bottom: 10px; + } + + &.selected { + background-color: #edfdf8; + } } .order-item { @@ -145,6 +158,172 @@ cursor: pointer; } +.invoice-popover { + width: 416px; + height: auto; + margin-left: -10px; + padding-top: 0px; + padding: 10px; + padding-bottom: 30px; + border-radius: 4px; + background-color: #ffffff; + box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.16); + border: solid 1px #7894a4; +} + +.invoice-popover-checkbox { + padding-left: 10px; + -webkit-flex: 1; + flex: 0; + border: 1px +} + +.invoice-popover-controls { + margin-top: 10px; + margin-bottom: 10px; + display: flex; + width: 100%; + height: 30px; + + .invoice-popover-selected { + flex: 7; + padding-top: 2px; + padding-left: 10px; + + i { + font-size: 18px; + } + + span { + font-size: 16px; + left: 5px; + position: relative; + top: 0px; + } + } + + .invoice-popover-close { + flex: 1; + } +} + +.invoice-order-line-media { + flex: 0 0 auto; + width: 40px; + + img { + width: 100%; + } +} + +.invoive-order-items { + margin-bottom: 10px; + height: 84px; + padding: 10px; + background-color: #f7f7f7; + + .invoice-order-item-tax { + display: flex; + width: 100%; + div { + flex: 1; + + &.tax-code { + text-align: right; + } + + &.tax-cost { + text-align: right; + } + } + } +} + +.invoice-actions { + display: flex; + div { + &.invoice-action-cancel { + flex: 2; + margin-right: 10px; + } + &.invoice-action-refund { + flex: 1; + button { + width: 100%; + } + } + } +} + +.invoice-refund-edited { + width: 100%; + padding-bottom: 10px; + + .refund-header { + display: flex; + border-bottom: 1px solid #bfbfbf; + padding: 10px; + + div { + flex: 1; + + &:first-child { + flex: 2; + } + + &:last-child { + text-align: right; + } + + span { + color: @rui-danger; + font-weight: 600; + } + } + } + + .refund-body { + margin-top: 10px; + .refund-item { + padding: 10px; + margin-bottom: 4px; + display: flex; + background: @rui-danger-bg; + + div { + flex: 1; + + &:first-child { + flex: 2 + } + + &:last-child { + text-align: right; + } + } + + &.return { + span { + color: @rui-danger + } + } + } + } + + .refund-include-shipping { + height: 30px; + + input { + margin-right: 8px; + } + + span { + top: -5px; + position: relative; + } + } +} + .invoice { margin-left: -15px; margin-right: -15px; @@ -189,10 +368,6 @@ list-style-type: none; } -.order-shipment-item { - -} - // Order form summary .order-summary-form-group { .display(flex); @@ -361,3 +536,310 @@ } } } + +// Order Table Styles +.rui.order.table { + border-top: none; + + // Table Headers + .order-table-header-name { + border-right: none; + text-align: left; + padding-top: 8px; + + flex: 250 0 auto; + width: 250px; + max-width: 250px; + } + .order-table-header-email { + border-right: none; + text-align: left; + padding-top: 19px; + } + .order-table-header-date { + border-right: none; + text-align: center; + padding-top: 19px; + + flex: 120 0 auto; + width: 120px; + max-width: 120px; + } + .order-table-header-id { + border-right: none; + text-align: center; + padding-top: 19px; + + flex: 90 0 auto; + width: 90px; + max-width: 90px; + } + .order-table-header-total { + border-right: none; + text-align: center; + padding-top: 19px; + + flex: 100 0 auto; + width: 100px; + max-width: 100px; + } + .order-table-header-shipping { + border-right: none; + text-align: center; + padding-top: 19px; + + flex: 100 0 auto; + width: 100px; + max-width: 100px; + } + .order-table-header-status { + border-right: none; + text-align: center; + padding-top: 19px; + + flex: 150 0 auto; + width: 150px; + max-width: 150px; + } + + .order-table-header-control { + flex: 50 0 auto; + width: 50px; + max-width: 50px; + } + + // Table Columns + .order-table-column-name { + border-right: 1px solid #e6e6e6; + + flex: 250 0 auto; + width: 250px; + max-width: 250px; + } + + .order-table-column-date { + text-align: center; + + flex: 120 0 auto; + width: 120px; + max-width: 120px; + } + .order-table-column-id { + text-align: center; + + flex: 90 0 auto; + width: 90px; + max-width: 90px; + } + .order-table-column-total { + text-align: center; + + flex: 100 0 auto; + width: 100px; + max-width: 100px; + } + .order-table-column-shipping { + text-align: right; + margin-top: 2px; + + flex: 100 0 auto; + width: 100px; + max-width: 100px; + } + .order-table-column-status { + text-align: right; + margin-top: 2px; + + flex: 150 0 auto; + width: 150px; + max-width: 150px; + } + + .order-table-column-control { + .display(flex); + .flex-direction(column); + // height: 100%; + padding: 0; + height: 49px; + flex: 50 0 auto; + width: 50px; + max-width: 50px; + button { + flex: 1 1 auto; + border-radius: 0; + height: 100%; + width: 100%; + background-color: transparent; + } + } + + .order-table-column-control .btn-success { + .btn-success(); + border-radius: 0; + } + + // Name cell display + .order-table-name-cell { + display: inline-flex; + padding-left: 5px + } + + // Checkbox Styling + .order-header-checkbox { + margin-top: 10px; + margin-bottom: 0px; + margin-right: 10px; + border-radius: 2px; + border: solid 2px #5e6264; + } + + .column-name-name { + margin-top: 10; + } + + .order-table-pagination-visible { + border-top: none; + margin-top: 10px; + margin-bottom: 10px; + } + + .order-table-pagination-hidden { + display: none; + } + + .order-table-thead { + border-top: 1px solid #e6e6e6; + border-right: 1px solid #e6e6e6; + border-left: 1px solid #e6e6e6; + } + + .order-table-details-tr-group { + border-right: none; + border-left: none; + border-bottom: none; + } + + .rt-tr.order-table-detail-tr:hover { + background: transparent + } + + .order-table-tr-group { + border-right: 1px solid #e6e6e6; + border-left: 1px solid #e6e6e6; + } + + .order-table-list { + border-bottom: 1px solid #e6e6e6; + } + + .order-table-detail { + border-bottom: none; + } + + .rt-tbody { + .rt-tr.selected-row { + background-color: #e8fcf6; + } + } +} + +.rollover-checkbox { + + .selected-checkbox { + padding-right: 6px; + + .checkbox-avatar { + margin-left: -6px; + border-radius: 2px; + border: solid 2px #5e6264; + background: transparent; + } + } + + .second-child { + padding-right: 6px; + + .checkbox-avatar { + margin-left: -6px; + border-radius: 2px; + border: solid 2px #5e6264; + } + } +} + +.rui.order.table.table-header-hidden { + .rt-thead { + border-bottom: none; + + .rt-tr { + display: none; + } + } +} + +.rui.order.table.table-header-visible { + .rt-thead { + .rt-tr { + height: 56px; + } + } +} + +.order-toggle-btn, .badge-basic.orders-badge { + background: transparent; +} + +.bulk-order-actions-bar { + height: 68px; + background-color: #f7f7f7; + padding-top: 20px; + margin-top: 78px; + + .orders-checkbox { + margin-left: 11px; + vertical-align: -7px; + } + + .selected-orders { + font-size: 15px; + font-weight: bold; + color: #1c98d4; + margin-left: 13px; + vertical-align: middle; + } + + .capture-orders-button, .bulk-actions-button { + width: 161px; + text-align: center; + margin-left: 20px; + vertical-align: middle; + } + + .bulk-actions-button { + i { + margin-left: 20px; + } + } +} + +.order-toggle-btn { + border: none; + background: transparent; + + &:focus { + outline: none; + } + + i { + width: 18.5px; + height: 16px + } +} + +.order-toggle-btn.order-icon-toggle { + color: #1999dd; +} + +.order-toggle-buttons { + float: right; +} diff --git a/imports/plugins/included/default-theme/client/styles/forms.less b/imports/plugins/included/default-theme/client/styles/forms.less index 951c6bcbea9..cb996b31107 100644 --- a/imports/plugins/included/default-theme/client/styles/forms.less +++ b/imports/plugins/included/default-theme/client/styles/forms.less @@ -136,3 +136,105 @@ .rui.list-item-action .rui.switch { padding: 0; } + +.checkbox-large { + -webkit-appearance: none; + // background-color: #fafafa; + // border: 1px solid #cacece; + box-shadow: 0 1px 2px rgba(0,0,0,0.05), inset 0px -15px 10px -12px rgba(0,0,0,0.05); + padding: 9px; + // border-radius: 3px; + display: inline-block; + position: relative; + width: 20px; + height: 20px; + border-radius: 2px; + border: solid 2px #5e6264; + + &:checked { + font-size: 13px; + + &:after { + font-family: FontAwesome; + content: "\f00c"; + position: absolute; + top: 2px; + left: 2px; + color: #5e6264; + } + } +} + +.number-input { + display: flex; + + .number-field { + width: 36px; + height: 40px; + margin-right: 5px; + text-align: center; + background-color: #f7f7f7; + border: solid 1px #cccccc; + flex: 1; + } + + .edited { + background-color: #edfdf8; + border: 1px solid #94e8d1 + } + .number-field:focus { + background-color: #edfdf8; + } + .number-field::-webkit-inner-spin-button { + -webkit-appearance: none; + } + + .stacked-buttons { + width: 25px; + flex: none; + + .button { + border: 1px solid #98afbc; + border-radius: 2px; + margin-bottom: -18px; + width: 20px; + align-items: center; + height: 19px; + display: flex; + justify-content: center; + padding: 0px; + + i { + font-size: 8px; + color: #5e6264; + position: absolute; + } + } + } +} + +.rollover-checkbox { + + .selected-checkbox { + padding-left: 10px; + -webkit-flex: 1; + flex: 0; + border: 1px + } + + .second-child { + display: none; + padding-left: 10px; + -webkit-flex: 1; + flex: 1; + border: 1px + } + + &:hover .first-child { + display: none; + } + + &:hover .second-child { + display: inline-block; + } +} diff --git a/imports/plugins/included/default-theme/client/styles/sortableTable.less b/imports/plugins/included/default-theme/client/styles/sortableTable.less index a509e9be945..1d5f652e305 100644 --- a/imports/plugins/included/default-theme/client/styles/sortableTable.less +++ b/imports/plugins/included/default-theme/client/styles/sortableTable.less @@ -88,12 +88,12 @@ .ReactTable .rt-thead .rt-th.-sort-asc, .ReactTable .rt-thead .rt-td.-sort-asc { - box-shadow: inset 0 3px 0 0 rgba(0, 0, 0, 0.6); + box-shadow: inset 0 3px 0 0 @rui-default; } .ReactTable .rt-thead .rt-th.-sort-desc, .ReactTable .rt-thead .rt-td.-sort-desc { - box-shadow: inset 0 -3px 0 0 rgba(0, 0, 0, 0.6); + box-shadow: inset 0 -3px 0 0 @rui-default; } .ReactTable .rt-thead .rt-th.-cursor-pointer, diff --git a/private/data/i18n/en.json b/private/data/i18n/en.json index 2a24bd3cca1..15f5d36d689 100644 --- a/private/data/i18n/en.json +++ b/private/data/i18n/en.json @@ -383,7 +383,10 @@ "canceled": "Canceled", "refunded": "Refunded" }, - "showMore": "Show More" + "showMore": "Show More", + "capture": "Capture", + "bulkActions": "Bulk Actions", + "selected": "Selected" }, "orderShipping": { "shipTo": "Ship To", diff --git a/server/publications/collections/orders.js b/server/publications/collections/orders.js index 83bc3a2ae0c..818545cb433 100644 --- a/server/publications/collections/orders.js +++ b/server/publications/collections/orders.js @@ -5,79 +5,6 @@ import { Counts } from "meteor/tmeasday:publish-counts"; import { Orders } from "/lib/collections"; import { Reaction } from "/server/api"; -const OrderHelper = { - makeQuery(filter, shopId = Reaction.getShopId()) { - let query = {}; - - switch (filter) { - // New orders - case "new": - query = { - "shopId": shopId, - "workflow.status": "new" - }; - break; - - // Orders that have yet to be captured & shipped - case "processing": - query = { - "shopId": shopId, - "workflow.status": "coreOrderWorkflow/processing" - }; - break; - - // Orders that have been shipped, based on if the items have been shipped - case "shipped": - query = { - "shopId": shopId, - "items.workflow.status": "coreOrderItemWorkflow/shipped" - }; - break; - - // Orders that are complete, including all items with complete status - case "completed": - query = { - "shopId": shopId, - "workflow.status": { - $in: ["coreOrderWorkflow/completed", "coreOrderWorkflow/canceled"] - }, - "items.workflow.status": { - $in: ["coreOrderItemWorkflow/completed", "coreOrderItemWorkflow/canceled"] - } - }; - break; - - // Orders that have been captured, but not yet shipped - case "captured": - query = { - "shopId": shopId, - "billing.paymentMethod.status": "completed", - "shipping.shipped": false - }; - break; - - case "canceled": - query = { - "shopId": shopId, - "workflow.status": "canceled" - }; - break; - - // Orders that have been refunded partially or fully - case "refunded": - query = { - "shopId": shopId, - "billing.paymentMethod.status": "captured", - "shipping.shipped": true - }; - break; - default: - } - - return query; - } -}; - /** * orders */ @@ -105,8 +32,7 @@ Meteor.publish("Orders", function () { * paginated orders */ -Meteor.publish("PaginatedOrders", function (filter, limit) { - check(filter, Match.OptionalOrNull(String)); +Meteor.publish("PaginatedOrders", function (limit) { check(limit, Number); if (this.userId === null) { @@ -117,10 +43,29 @@ Meteor.publish("PaginatedOrders", function (filter, limit) { return this.ready(); } if (Roles.userIsInRole(this.userId, ["admin", "owner", "orders"], shopId)) { - Counts.publish(this, "newOrder-count", Orders.find(OrderHelper.makeQuery("new", shopId)), { noReady: true }); - Counts.publish(this, "processingOrder-count", Orders.find(OrderHelper.makeQuery("processing", shopId)), { noReady: true }); - Counts.publish(this, "completedOrder-count", Orders.find(OrderHelper.makeQuery("completed", shopId)), { noReady: true }); - return Orders.find(OrderHelper.makeQuery(filter, shopId), { limit: limit }); + Counts.publish(this, "order-count", Orders.find({ shopId: shopId }), { noReady: true }); + return Orders.find({ shopId: shopId }, { limit: limit }); + } + return Orders.find({ + shopId: shopId, + userId: this.userId + }); +}); + +Meteor.publish("CustomPaginatedOrders", function (query, options) { + check(query, Match.Optional(Object)); + check(options, Match.Optional(Object)); + + if (this.userId === null) { + return this.ready(); + } + const shopId = Reaction.getShopId(this.userId); + if (!shopId) { + return this.ready(); + } + if (Roles.userIsInRole(this.userId, ["admin", "owner", "orders"], shopId)) { + Counts.publish(this, "order-count", Orders.find({ shopId: shopId }), { noReady: true }); + return Orders.find({ shopId: shopId }); } return Orders.find({ shopId: shopId,