diff --git a/client/src/components/invoices-by-vendor-list/invoices-by-vendor-list.component.jsx b/client/src/components/invoices-by-vendor-list/invoices-by-vendor-list.component.jsx index d0f70cea9..953a655c8 100644 --- a/client/src/components/invoices-by-vendor-list/invoices-by-vendor-list.component.jsx +++ b/client/src/components/invoices-by-vendor-list/invoices-by-vendor-list.component.jsx @@ -1,159 +1,161 @@ -import React, { useState } from "react"; -import { QUERY_INVOICES_BY_VENDOR_PAGINATED } from "../../graphql/invoices.queries"; -import { useQuery } from "@apollo/react-hooks"; -import queryString from "query-string"; -import { useHistory, useLocation } from "react-router-dom"; -import { Table, Input } from "antd"; -import { useTranslation } from "react-i18next"; -import { alphaSort } from "../../utils/sorters"; -import AlertComponent from "../alert/alert.component"; -import { DateFormatter } from "../../utils/DateFormatter"; -import CurrencyFormatter from "../../utils/CurrencyFormatter"; +//DEPRECATED. -export default function InvoicesByVendorList() { - const search = queryString.parse(useLocation().search); - const history = useHistory(); - const { page, sortcolumn, sortorder } = search; +// import React, { useState } from "react"; +// import { QUERY_INVOICES_BY_VENDOR_PAGINATED } from "../../graphql/invoices.queries"; +// import { useQuery } from "@apollo/react-hooks"; +// import queryString from "query-string"; +// import { useHistory, useLocation } from "react-router-dom"; +// import { Table, Input } from "antd"; +// import { useTranslation } from "react-i18next"; +// import { alphaSort } from "../../utils/sorters"; +// import AlertComponent from "../alert/alert.component"; +// import { DateFormatter } from "../../utils/DateFormatter"; +// import CurrencyFormatter from "../../utils/CurrencyFormatter"; - const { loading, error, data } = useQuery( - QUERY_INVOICES_BY_VENDOR_PAGINATED, - { - variables: { - vendorId: search.vendorid, - offset: page ? (page - 1) * 25 : 0, - limit: 25, - order: [ - { - [sortcolumn || "date"]: sortorder - ? sortorder === "descend" - ? "desc" - : "asc" - : "desc", - }, - ], - }, - skip: !!!search.vendorid, - } - ); +// export default function InvoicesByVendorList() { +// const search = queryString.parse(useLocation().search); +// const history = useHistory(); +// const { page, sortcolumn, sortorder } = search; - const { t } = useTranslation(); +// const { loading, error, data } = useQuery( +// QUERY_INVOICES_BY_VENDOR_PAGINATED, +// { +// variables: { +// vendorId: search.vendorid, +// offset: page ? (page - 1) * 25 : 0, +// limit: 25, +// order: [ +// { +// [sortcolumn || "date"]: sortorder +// ? sortorder === "descend" +// ? "desc" +// : "asc" +// : "desc", +// }, +// ], +// }, +// skip: !!!search.vendorid, +// } +// ); - const [state, setState] = useState({ - sortedInfo: {}, - search: "", - }); +// const { t } = useTranslation(); - const handleTableChange = (pagination, filters, sorter) => { - setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); - search.page = pagination.current; - search.sortcolumn = sorter.columnKey; - search.sortorder = sorter.order; - history.push({ search: queryString.stringify(search) }); - }; +// const [state, setState] = useState({ +// sortedInfo: {}, +// search: "", +// }); - const handleOnRowClick = (record) => { - if (record) { - if (record.id) { - search.invoiceid = record.id; - history.push({ search: queryString.stringify(search) }); - } - } else { - delete search.invoiceid; - history.push({ search: queryString.stringify(search) }); - } - }; +// const handleTableChange = (pagination, filters, sorter) => { +// setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); +// search.page = pagination.current; +// search.sortcolumn = sorter.columnKey; +// search.sortorder = sorter.order; +// history.push({ search: queryString.stringify(search) }); +// }; - const columns = [ - { - title: t("invoices.fields.invoice_number"), - dataIndex: "invoice_number", - key: "invoice_number", - sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), - sortOrder: - state.sortedInfo.columnKey === "invoice_number" && - state.sortedInfo.order, - }, - { - title: t("invoices.fields.date"), - dataIndex: "date", - key: "date", +// const handleOnRowClick = (record) => { +// if (record) { +// if (record.id) { +// search.invoiceid = record.id; +// history.push({ search: queryString.stringify(search) }); +// } +// } else { +// delete search.invoiceid; +// history.push({ search: queryString.stringify(search) }); +// } +// }; - sorter: (a, b) => a.date - b.date, - sortOrder: - state.sortedInfo.columnKey === "date" && state.sortedInfo.order, - render: (text, record) => {record.date}, - }, - { - title: t("invoices.fields.total"), - dataIndex: "total", - key: "total", +// const columns = [ +// { +// title: t("invoices.fields.invoice_number"), +// dataIndex: "invoice_number", +// key: "invoice_number", +// sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), +// sortOrder: +// state.sortedInfo.columnKey === "invoice_number" && +// state.sortedInfo.order, +// }, +// { +// title: t("invoices.fields.date"), +// dataIndex: "date", +// key: "date", - sorter: (a, b) => a.total - b.total, - sortOrder: - state.sortedInfo.columnKey === "total" && state.sortedInfo.order, - render: (text, record) => ( - {record.total} - ), - }, - ]; +// sorter: (a, b) => a.date - b.date, +// sortOrder: +// state.sortedInfo.columnKey === "date" && state.sortedInfo.order, +// render: (text, record) => {record.date}, +// }, +// { +// title: t("invoices.fields.total"), +// dataIndex: "total", +// key: "total", - const handleSearch = (e) => { - setState({ ...state, search: e.target.value }); - }; +// sorter: (a, b) => a.total - b.total, +// sortOrder: +// state.sortedInfo.columnKey === "total" && state.sortedInfo.order, +// render: (text, record) => ( +// {record.total} +// ), +// }, +// ]; - const dataSource = state.search - ? data.invoices.filter( - (i) => - (i.invoice_number || "") - .toLowerCase() - .includes(state.search.toLowerCase()) || - (i.amount || "").toString().includes(state.search) - ) - : (data && data.invoices) || []; +// const handleSearch = (e) => { +// setState({ ...state, search: e.target.value }); +// }; - if (error) return ; +// const dataSource = state.search +// ? data.invoices.filter( +// (i) => +// (i.invoice_number || "") +// .toLowerCase() +// .includes(state.search.toLowerCase()) || +// (i.amount || "").toString().includes(state.search) +// ) +// : (data && data.invoices) || []; - return ( - { - return ( -
- -
- ); - }} - dataSource={dataSource} - size='small' - scroll={{ x: true }} - pagination={{ - position: "top", - pageSize: 25, - current: parseInt(page || 1), - total: data ? data.invoices_aggregate.aggregate.count : 0, - }} - columns={columns} - rowKey='id' - onChange={handleTableChange} - rowSelection={{ - onSelect: (record) => { - handleOnRowClick(record); - }, - selectedRowKeys: [search.invoiceid], - type: "radio", - }} - onRow={(record, rowIndex) => { - return { - onClick: (event) => { - handleOnRowClick(record); - }, // click row - }; - }} - /> - ); -} +// if (error) return ; + +// return ( +//
{ +// return ( +//
+// +//
+// ); +// }} +// dataSource={dataSource} +// size='small' +// scroll={{ x: true }} +// pagination={{ +// position: "top", +// pageSize: 25, +// current: parseInt(page || 1), +// total: data ? data.invoices_aggregate.aggregate.count : 0, +// }} +// columns={columns} +// rowKey='id' +// onChange={handleTableChange} +// rowSelection={{ +// onSelect: (record) => { +// handleOnRowClick(record); +// }, +// selectedRowKeys: [search.invoiceid], +// type: "radio", +// }} +// onRow={(record, rowIndex) => { +// return { +// onClick: (event) => { +// handleOnRowClick(record); +// }, // click row +// }; +// }} +// /> +// ); +// } diff --git a/client/src/components/invoices-list-table/invoices-list-table.component.jsx b/client/src/components/invoices-list-table/invoices-list-table.component.jsx index aa1c1ef61..6a350520f 100644 --- a/client/src/components/invoices-list-table/invoices-list-table.component.jsx +++ b/client/src/components/invoices-list-table/invoices-list-table.component.jsx @@ -94,7 +94,8 @@ export function InvoicesListTableComponent({ render: (text, record) => (
+ to={`/manage/invoices?invoiceid=${record.id}&vendorid=${record.vendorid}`} + >
@@ -243,11 +245,11 @@ export function InvoicesListTableComponent({
@@ -261,37 +263,45 @@ export function InvoicesListTableComponent({
( -
+
- - {" "} -
+ {job ? ( +
+ + +
+ ) : null} + +
{ @@ -305,7 +315,7 @@ export function InvoicesListTableComponent({ expandedRowRender={rowExpander} pagination={{ position: "top", defaultPageSize: 25 }} columns={columns} - rowKey='id' + rowKey="id" dataSource={invoices} onChange={handleTableChange} expandable={{ diff --git a/client/src/graphql/invoices.queries.js b/client/src/graphql/invoices.queries.js index d2a765c3d..9c98f5820 100644 --- a/client/src/graphql/invoices.queries.js +++ b/client/src/graphql/invoices.queries.js @@ -11,13 +11,27 @@ export const INSERT_NEW_INVOICE = gql` `; export const QUERY_ALL_INVOICES_PAGINATED = gql` - query QUERY_ALL_INVOICES_PAGINATED($offset: Int, $limit: Int) { - invoices(offset: $offset, limit: $limit, order_by: { date: desc }) { + query QUERY_ALL_INVOICES_PAGINATED( + $search: String + $offset: Int + $limit: Int + $order: [invoices_order_by!]! + ) { + search_invoices( + args: { search: $search } + offset: $offset + limit: $limit + order_by: $order + ) { id vendor { id name } + federal_tax_rate + local_tax_rate + state_tax_rate + is_credit_memo total invoice_number date @@ -34,6 +48,11 @@ export const QUERY_ALL_INVOICES_PAGINATED = gql` line_desc } } + search_invoices_aggregate(args: { search: $search }) { + aggregate { + count(distinct: true) + } + } } `; diff --git a/client/src/pages/invoices/invoices.page.component.jsx b/client/src/pages/invoices/invoices.page.component.jsx new file mode 100644 index 000000000..6c979ee71 --- /dev/null +++ b/client/src/pages/invoices/invoices.page.component.jsx @@ -0,0 +1,195 @@ +import { SyncOutlined } from "@ant-design/icons"; +import { Button, Checkbox, Input, Table, Typography } from "antd"; +import queryString from "query-string"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { Link, useHistory, useLocation } from "react-router-dom"; +import { setModalContext } from "../../redux/modals/modals.actions"; +import CurrencyFormatter from "../../utils/CurrencyFormatter"; +import { DateFormatter } from "../../utils/DateFormatter"; +import { alphaSort } from "../../utils/sorters"; + +const mapDispatchToProps = (dispatch) => ({ + setPartsOrderContext: (context) => + dispatch(setModalContext({ context: context, modal: "partsOrder" })), + setInvoiceEnterContext: (context) => + dispatch(setModalContext({ context: context, modal: "invoiceEnter" })), +}); + +export function InvoicesListPage({ + loading, + data, + refetch, + total, + setPartsOrderContext, + setInvoiceEnterContext, +}) { + const { t } = useTranslation(); + const [state, setState] = useState({ + sortedInfo: {}, + }); + const history = useHistory(); + const search = queryString.parse(useLocation().search); + const { page } = search; + + const selectedInvoice = search.invoiceid; + const columns = [ + { + title: t("invoices.fields.vendorname"), + dataIndex: "vendorname", + key: "vendorname", + // sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name), + // sortOrder: + // state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order, + render: (text, record) => {record.vendor.name}, + }, + { + title: t("invoices.fields.invoice_number"), + dataIndex: "invoice_number", + key: "invoice_number", + sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number), + sortOrder: + state.sortedInfo.columnKey === "invoice_number" && + state.sortedInfo.order, + }, + { + title: t("invoices.fields.date"), + dataIndex: "date", + key: "date", + sorter: (a, b) => a.date - b.date, + sortOrder: + state.sortedInfo.columnKey === "date" && state.sortedInfo.order, + render: (text, record) => {record.date}, + }, + { + title: t("invoices.fields.total"), + dataIndex: "total", + key: "total", + sorter: (a, b) => a.total - b.total, + sortOrder: + state.sortedInfo.columnKey === "total" && state.sortedInfo.order, + render: (text, record) => ( + {record.total} + ), + }, + { + title: t("invoices.fields.is_credit_memo"), + dataIndex: "is_credit_memo", + key: "is_credit_memo", + sorter: (a, b) => a.is_credit_memo - b.is_credit_memo, + sortOrder: + state.sortedInfo.columnKey === "is_credit_memo" && + state.sortedInfo.order, + render: (text, record) => , + }, + { + title: t("general.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( +
+ + + + +
+ ), + }, + ]; + + const handleTableChange = (pagination, filters, sorter) => { + setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); + search.page = pagination.current; + search.sortcolumn = sorter.columnKey; + search.sortorder = sorter.order; + history.push({ search: queryString.stringify(search) }); + }; + + const handleOnRowClick = (record) => { + if (record) { + if (record.id) { + search.invoiceid = record.id; + history.push({ search: queryString.stringify(search) }); + } + } else { + delete search.invoiceid; + history.push({ search: queryString.stringify(search) }); + } + }; + + return ( +
+ + {t("invoices.labels.invoices")} + +
( +
+ + +
+ { + search.search = value; + history.push({ search: queryString.stringify(search) }); + }} + /> +
+
+ )} + scroll={{ x: "50%", y: "40rem" }} + pagination={{ + position: "top", + pageSize: 25, + current: parseInt(page || 1), + total: total, + }} + columns={columns} + rowKey="id" + dataSource={data} + onChange={handleTableChange} + /> + + ); +} +export default connect(null, mapDispatchToProps)(InvoicesListPage); diff --git a/client/src/pages/invoices/invoices.page.container.jsx b/client/src/pages/invoices/invoices.page.container.jsx index e42355029..791ac8d20 100644 --- a/client/src/pages/invoices/invoices.page.container.jsx +++ b/client/src/pages/invoices/invoices.page.container.jsx @@ -1,11 +1,13 @@ -import { Col, Row } from "antd"; +import queryString from "query-string"; import React, { useEffect } from "react"; +import { useQuery } from "react-apollo"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; +import { useLocation } from "react-router-dom"; import InvoiceDetailEditContainer from "../../components/invoice-detail-edit/invoice-detail-edit.container"; -import InvoicesByVendorList from "../../components/invoices-by-vendor-list/invoices-by-vendor-list.component"; -import VendorsList from "../../components/invoices-vendors-list/invoices-vendors-list.component"; +import { QUERY_ALL_INVOICES_PAGINATED } from "../../graphql/invoices.queries"; import { setBreadcrumbs } from "../../redux/application/application.actions"; +import InvoicesPageComponent from "./invoices.page.component"; const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -13,6 +15,9 @@ const mapDispatchToProps = (dispatch) => ({ export function InvoicesPageContainer({ setBreadcrumbs }) { const { t } = useTranslation(); + const searchParams = queryString.parse(useLocation().search); + const { page, sortcolumn, sortorder, search } = searchParams; + useEffect(() => { document.title = t("titles.invoices-list"); setBreadcrumbs([ @@ -20,20 +25,37 @@ export function InvoicesPageContainer({ setBreadcrumbs }) { ]); }, [t, setBreadcrumbs]); + const { loading, error, data, refetch } = useQuery( + QUERY_ALL_INVOICES_PAGINATED, + { + variables: { + search: search || "", + offset: page ? (page - 1) * 25 : 0, + limit: 25, + order: [ + { + [sortcolumn || "date"]: sortorder + ? sortorder === "descend" + ? "desc" + : "asc" + : "desc", + }, + ], + }, + } + ); + return ( - - - - - - - - - - - - - +
+ + + +
); } export default connect(null, mapDispatchToProps)(InvoicesPageContainer); diff --git a/hasura/migrations/1595971170482_run_sql_migration/down.yaml b/hasura/migrations/1595971170482_run_sql_migration/down.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/hasura/migrations/1595971170482_run_sql_migration/down.yaml @@ -0,0 +1 @@ +[] diff --git a/hasura/migrations/1595971170482_run_sql_migration/up.yaml b/hasura/migrations/1595971170482_run_sql_migration/up.yaml new file mode 100644 index 000000000..12d51c766 --- /dev/null +++ b/hasura/migrations/1595971170482_run_sql_migration/up.yaml @@ -0,0 +1,6 @@ +- args: + cascade: true + read_only: false + sql: CREATE INDEX idx_invoices_invoicenumber ON invoices USING GIN (invoice_number + gin_trgm_ops); + type: run_sql diff --git a/hasura/migrations/1595971216422_run_sql_migration/down.yaml b/hasura/migrations/1595971216422_run_sql_migration/down.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/hasura/migrations/1595971216422_run_sql_migration/down.yaml @@ -0,0 +1 @@ +[] diff --git a/hasura/migrations/1595971216422_run_sql_migration/up.yaml b/hasura/migrations/1595971216422_run_sql_migration/up.yaml new file mode 100644 index 000000000..5e9aff249 --- /dev/null +++ b/hasura/migrations/1595971216422_run_sql_migration/up.yaml @@ -0,0 +1,12 @@ +- args: + cascade: true + read_only: false + sql: "CREATE OR REPLACE FUNCTION public.search_invoices(search text)\n RETURNS + SETOF invoices\n LANGUAGE plpgsql\n STABLE\nAS $function$\n\nBEGIN\n if search + = '' then\n return query select * from invoices ;\n else \n return query + SELECT\n *\nFROM\n payments\nWHERE\n search <% (invoice_number);\n end if;\n\n\tEND\n$function$;" + type: run_sql +- args: + name: search_invoices + schema: public + type: track_function diff --git a/hasura/migrations/1595973678869_run_sql_migration/down.yaml b/hasura/migrations/1595973678869_run_sql_migration/down.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/hasura/migrations/1595973678869_run_sql_migration/down.yaml @@ -0,0 +1 @@ +[] diff --git a/hasura/migrations/1595973678869_run_sql_migration/up.yaml b/hasura/migrations/1595973678869_run_sql_migration/up.yaml new file mode 100644 index 000000000..410506db0 --- /dev/null +++ b/hasura/migrations/1595973678869_run_sql_migration/up.yaml @@ -0,0 +1,8 @@ +- args: + cascade: true + read_only: false + sql: "CREATE OR REPLACE FUNCTION public.search_invoices(search text)\n RETURNS + SETOF invoices\n LANGUAGE plpgsql\n STABLE\nAS $function$\n\nBEGIN\n if search + = '' then\n return query select * from invoices ;\n else \n return query + SELECT\n *\nFROM\n invoices\nWHERE\n search <% (invoice_number);\n end if;\n\n\tEND\n$function$;" + type: run_sql diff --git a/hasura/migrations/metadata.yaml b/hasura/migrations/metadata.yaml index ec3eb9d2c..0c32e1e3c 100644 --- a/hasura/migrations/metadata.yaml +++ b/hasura/migrations/metadata.yaml @@ -4053,6 +4053,9 @@ tables: - active: _eq: true functions: +- function: + schema: public + name: search_invoices - function: schema: public name: search_jobs