From 2b93f016ac2f578fceea5b0ca92bc77d92bf738b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Salgado?= Date: Mon, 31 May 2021 15:58:24 +0100 Subject: [PATCH] feat: add search functionality to customer page --- lib/customers.js | 8 +- lib/new-admin/filters.js | 13 +- .../graphql/resolvers/customer.resolver.js | 6 +- lib/new-admin/graphql/types/customer.type.js | 3 +- .../src/components/layout/TitleSection.js | 5 +- .../src/pages/Customers/Customers.js | 132 ++++++++++++++++-- .../src/pages/Customers/CustomersList.js | 8 -- 7 files changed, 151 insertions(+), 24 deletions(-) diff --git a/lib/customers.js b/lib/customers.js index bd410744..b92a110d 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -450,7 +450,7 @@ function batch () { * * @returns {array} Array of customers with it's transactions aggregations */ -function getCustomersList () { +function getCustomersList (phone = null, name = null, address = null, id = null) { const sql = `select id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_override, phone, sms_override, id_card_data, id_card_data_override, id_card_data_expiration, id_card_photo_path, id_card_photo_override, us_ssn, us_ssn_override, sanctions, sanctions_at, @@ -474,8 +474,12 @@ function getCustomersList () { from cash_out_txs where confirmed_at is not null) t on c.id = t.customer_id where c.id != $1 ) as cl where rn = 1 + and ($3 is null or phone = $3) + and ($4 is null or concat(id_card_data::json->>'firstName', ' ', id_card_data::json->>'lastName') = $4) + and ($5 is null or id_card_data::json->>'address' = $5) + and ($6 is null or id_card_data::json->>'documentNumber' = $6) limit $2` - return db.any(sql, [ anonymous.uuid, NUM_RESULTS ]) + return db.any(sql, [ anonymous.uuid, NUM_RESULTS, phone, name, address, id ]) .then(customers => Promise.all(_.map(customer => { return populateOverrideUsernames(customer) .then(camelize) diff --git a/lib/new-admin/filters.js b/lib/new-admin/filters.js index 3e9cf658..ce3bb954 100644 --- a/lib/new-admin/filters.js +++ b/lib/new-admin/filters.js @@ -27,4 +27,15 @@ function transaction() { return db.any(sql) } -module.exports = { transaction } +function customer() { + const sql = `select distinct * from ( + select 'phone' as type, phone as value from customers where phone is not null union + select 'name' as type, concat(id_card_data::json->>'firstName', ' ', id_card_data::json->>'lastName') as value from customers where concat(id_card_data::json->>'firstName', ' ', id_card_data::json->>'lastName') is not null union + select 'address' as type, id_card_data::json->>'address' as value from customers where id_card_data::json->>'address' is not null union + select 'id' as type, id_card_data::json->>'documentNumber' as value from customers where id_card_data::json->>'documentNumber' is not null + ) f` + + return db.any(sql) +} + +module.exports = { transaction, customer } diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index ad8cf1b9..f33c3276 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -1,13 +1,15 @@ const anonymous = require('../../../constants').anonymousCustomer const customers = require('../../../customers') +const filters = require('../../filters') const resolvers = { Customer: { isAnonymous: parent => (parent.customerId === anonymous.uuid) }, Query: { - customers: () => customers.getCustomersList(), - customer: (...[, { customerId }]) => customers.getCustomerById(customerId) + customers: (...[, { phone, name, address, id }]) => customers.getCustomersList(phone, name, address, id), + customer: (...[, { customerId }]) => customers.getCustomerById(customerId), + customerFilters: () => filters.customer() }, Mutation: { setCustomer: (root, { customerId, customerInput }, context, info) => { diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index bfe04a29..8ee9cdeb 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -56,8 +56,9 @@ const typeDef = gql` } type Query { - customers: [Customer] @auth + customers(phone: String, name: String, address: String, id: String): [Customer] @auth customer(customerId: ID!): Customer @auth + customerFilters: [Filter] @auth } type Mutation { diff --git a/new-lamassu-admin/src/components/layout/TitleSection.js b/new-lamassu-admin/src/components/layout/TitleSection.js index d778fd19..95c36367 100644 --- a/new-lamassu-admin/src/components/layout/TitleSection.js +++ b/new-lamassu-admin/src/components/layout/TitleSection.js @@ -17,13 +17,16 @@ const TitleSection = ({ error, labels, button, - children + children, + appendix, + appendixClassName }) => { const classes = useStyles() return (
{title} + {appendix &&
{appendix}
} {error && ( Failed to save )} diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js index 6566fedf..c597bb85 100644 --- a/new-lamassu-admin/src/pages/Customers/Customers.js +++ b/new-lamassu-admin/src/pages/Customers/Customers.js @@ -1,12 +1,33 @@ import { useQuery } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core/styles' import gql from 'graphql-tag' import * as R from 'ramda' -import React from 'react' +import React, { useState } from 'react' import { useHistory } from 'react-router-dom' +import Chip from 'src/components/Chip' +import SearchBox from 'src/components/SearchBox' +import TitleSection from 'src/components/layout/TitleSection' +import { P } from 'src/components/typography' +import baseStyles from 'src/pages/Logs.styles' +import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg' +import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' +import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg' import { fromNamespace, namespaces } from 'src/utils/config' +import { chipStyles } from '../Transactions/Transactions.styles' + import CustomersList from './CustomersList' +import styles from './CustomersList.styles' + +const GET_CUSTOMER_FILTERS = gql` + query filters { + customerFilters { + type + value + } + } +` const GET_CUSTOMERS = gql` { @@ -28,26 +49,119 @@ const GET_CUSTOMERS = gql` } ` +const useStyles = makeStyles(styles) +const useChipStyles = makeStyles(chipStyles) +const useBaseStyles = makeStyles(baseStyles) + const Customers = () => { + const classes = useStyles() + const chipClasses = useChipStyles() + const baseStyles = useBaseStyles() const history = useHistory() - const { data: customersResponse, loading } = useQuery(GET_CUSTOMERS) const handleCustomerClicked = customer => history.push(`/compliance/customer/${customer.id}`) + const [filteredCustomers, setFilteredCustomers] = useState([]) + const [variables, setVariables] = useState({}) + const [filters, setFilters] = useState([]) + + const { + data: customersResponse, + loading: customerLoading, + refetch + } = useQuery(GET_CUSTOMERS, { + variables, + onCompleted: data => setFilteredCustomers(R.path(['customers'])(data)) + }) + + const { data: filtersResponse, loading: loadingFilters } = useQuery( + GET_CUSTOMER_FILTERS + ) + const configData = R.path(['config'])(customersResponse) ?? [] const locale = configData && fromNamespace(namespaces.LOCALE, configData) const customersData = R.sortWith([R.descend(R.prop('lastActive'))])( - R.path(['customers'])(customersResponse) ?? [] + filteredCustomers ?? [] ) + const onFilterChange = filters => { + const filtersObject = R.compose( + R.mergeAll, + R.map(f => ({ + [f.type]: f.value + })) + )(filters) + + setFilters(filters) + + setVariables({ + phone: filtersObject.phone, + name: filtersObject.name, + address: filtersObject.address, + id: filtersObject.id + }) + + refetch && refetch() + } + + const onFilterDelete = filter => + setFilters( + R.filter(f => !R.whereEq(R.pick(['type', 'value'], f), filter))(filters) + ) + + const filterOptions = R.path(['customerFilters'])(filtersResponse) + return ( - + <> + + +
+ } + appendixClassName={baseStyles.buttonsWrapper} + labels={[ + { label: 'Cash-in', icon: }, + { label: 'Cash-out', icon: } + ]} + /> + {filters.length > 0 && ( + <> +

{'Filters:'}

+
+ {filters.map((f, idx) => ( + onFilterDelete(f)} + deleteIcon={} + /> + ))} + setFilters([])} + deleteIcon={} + /> +
+ + )} + + ) } diff --git a/new-lamassu-admin/src/pages/Customers/CustomersList.js b/new-lamassu-admin/src/pages/Customers/CustomersList.js index e65efcf1..943aadfe 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomersList.js +++ b/new-lamassu-admin/src/pages/Customers/CustomersList.js @@ -4,7 +4,6 @@ import * as R from 'ramda' import React from 'react' import { MainStatus } from 'src/components/Status' -import TitleSection from 'src/components/layout/TitleSection' import DataTable from 'src/components/tables/DataTable' import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg' @@ -74,13 +73,6 @@ const CustomersList = ({ data, locale, onClick, loading }) => { return ( <> - }, - { label: 'Cash-out', icon: } - ]} - />