feat: advanced table for customers
This commit is contained in:
parent
16c1709e99
commit
e3335d69b4
10 changed files with 2854 additions and 5568 deletions
8056
package-lock.json
generated
8056
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -10,6 +10,7 @@
|
||||||
"@lamassu/coins": "v1.6.1",
|
"@lamassu/coins": "v1.6.1",
|
||||||
"@mui/icons-material": "^7.1.0",
|
"@mui/icons-material": "^7.1.0",
|
||||||
"@mui/material": "^7.1.0",
|
"@mui/material": "^7.1.0",
|
||||||
|
"@mui/x-date-pickers": "^8.3.1",
|
||||||
"@simplewebauthn/browser": "^3.0.0",
|
"@simplewebauthn/browser": "^3.0.0",
|
||||||
"apollo-upload-client": "^18.0.0",
|
"apollo-upload-client": "^18.0.0",
|
||||||
"bignumber.js": "9.0.0",
|
"bignumber.js": "9.0.0",
|
||||||
|
|
@ -25,6 +26,7 @@
|
||||||
"jszip": "^3.6.0",
|
"jszip": "^3.6.0",
|
||||||
"libphonenumber-js": "^1.11.15",
|
"libphonenumber-js": "^1.11.15",
|
||||||
"match-sorter": "^4.2.0",
|
"match-sorter": "^4.2.0",
|
||||||
|
"material-react-table": "^3.2.1",
|
||||||
"pretty-ms": "^2.1.0",
|
"pretty-ms": "^2.1.0",
|
||||||
"qrcode.react": "4.2.0",
|
"qrcode.react": "4.2.0",
|
||||||
"ramda": "^0.26.1",
|
"ramda": "^0.26.1",
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Router } from 'wouter'
|
import { Router } from 'wouter'
|
||||||
import ApolloProvider from './utils/apollo'
|
import ApolloProvider from './utils/apollo'
|
||||||
|
import { LocalizationProvider } from '@mui/x-date-pickers'
|
||||||
|
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV2'
|
||||||
|
|
||||||
import AppContext from './AppContext'
|
import AppContext from './AppContext'
|
||||||
import theme from './styling/theme'
|
import theme from './styling/theme'
|
||||||
|
|
@ -33,6 +35,7 @@ const App = () => {
|
||||||
isDirtyForm,
|
isDirtyForm,
|
||||||
setDirtyForm,
|
setDirtyForm,
|
||||||
}}>
|
}}>
|
||||||
|
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||||
<Router hook={useLocationWithConfirmation}>
|
<Router hook={useLocationWithConfirmation}>
|
||||||
<ApolloProvider>
|
<ApolloProvider>
|
||||||
<StyledEngineProvider enableCssLayer>
|
<StyledEngineProvider enableCssLayer>
|
||||||
|
|
@ -43,6 +46,7 @@ const App = () => {
|
||||||
</StyledEngineProvider>
|
</StyledEngineProvider>
|
||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
</Router>
|
</Router>
|
||||||
|
</LocalizationProvider>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,7 @@ const CustomerProfile = memo(() => {
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
const [clickedItem, setClickedItem] = useState('overview')
|
const [clickedItem, setClickedItem] = useState('overview')
|
||||||
const { id: customerId } = useParams()
|
const { id: customerId } = useParams()
|
||||||
|
console.log(customerId)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: customerResponse,
|
data: customerResponse,
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ import { useQuery, useMutation, gql } from '@apollo/client'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { useLocation } from 'wouter'
|
import { useLocation } from 'wouter'
|
||||||
import SearchBox from '../../components/SearchBox'
|
|
||||||
import SearchFilter from '../../components/SearchFilter'
|
|
||||||
import TitleSection from '../../components/layout/TitleSection'
|
import TitleSection from '../../components/layout/TitleSection'
|
||||||
import TxInIcon from '../../styling/icons/direction/cash-in.svg?react'
|
import TxInIcon from '../../styling/icons/direction/cash-in.svg?react'
|
||||||
import TxOutIcon from '../../styling/icons/direction/cash-out.svg?react'
|
import TxOutIcon from '../../styling/icons/direction/cash-out.svg?react'
|
||||||
|
|
@ -15,15 +13,6 @@ import CustomersList from './CustomersList'
|
||||||
import CreateCustomerModal from './components/CreateCustomerModal'
|
import CreateCustomerModal from './components/CreateCustomerModal'
|
||||||
import { getAuthorizedStatus } from './helper'
|
import { getAuthorizedStatus } from './helper'
|
||||||
|
|
||||||
const GET_CUSTOMER_FILTERS = gql`
|
|
||||||
query filters {
|
|
||||||
customerFilters {
|
|
||||||
type
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const GET_CUSTOMERS = gql`
|
const GET_CUSTOMERS = gql`
|
||||||
query configAndCustomers(
|
query configAndCustomers(
|
||||||
$phone: String
|
$phone: String
|
||||||
|
|
@ -91,9 +80,6 @@ const CREATE_CUSTOMER = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const getFiltersObj = filters =>
|
|
||||||
R.reduce((s, f) => ({ ...s, [f.type]: f.value }), {}, filters)
|
|
||||||
|
|
||||||
const Customers = () => {
|
const Customers = () => {
|
||||||
const [, navigate] = useLocation()
|
const [, navigate] = useLocation()
|
||||||
|
|
||||||
|
|
@ -101,28 +87,20 @@ const Customers = () => {
|
||||||
navigate(`/compliance/customer/${customer.id}`)
|
navigate(`/compliance/customer/${customer.id}`)
|
||||||
|
|
||||||
const [filteredCustomers, setFilteredCustomers] = useState([])
|
const [filteredCustomers, setFilteredCustomers] = useState([])
|
||||||
const [variables, setVariables] = useState({})
|
|
||||||
const [filters, setFilters] = useState([])
|
|
||||||
const [showCreationModal, setShowCreationModal] = useState(false)
|
const [showCreationModal, setShowCreationModal] = useState(false)
|
||||||
|
|
||||||
const {
|
const { data: customersResponse, loading: customerLoading } = useQuery(
|
||||||
data: customersResponse,
|
GET_CUSTOMERS,
|
||||||
loading: customerLoading,
|
{
|
||||||
refetch,
|
|
||||||
} = useQuery(GET_CUSTOMERS, {
|
|
||||||
variables,
|
|
||||||
onCompleted: data => setFilteredCustomers(R.path(['customers'])(data)),
|
onCompleted: data => setFilteredCustomers(R.path(['customers'])(data)),
|
||||||
})
|
},
|
||||||
|
)
|
||||||
const { data: filtersResponse, loading: loadingFilters } =
|
|
||||||
useQuery(GET_CUSTOMER_FILTERS)
|
|
||||||
|
|
||||||
const [createNewCustomer] = useMutation(CREATE_CUSTOMER, {
|
const [createNewCustomer] = useMutation(CREATE_CUSTOMER, {
|
||||||
onCompleted: () => setShowCreationModal(false),
|
onCompleted: () => setShowCreationModal(false),
|
||||||
refetchQueries: () => [
|
refetchQueries: () => [
|
||||||
{
|
{
|
||||||
query: GET_CUSTOMERS,
|
query: GET_CUSTOMERS,
|
||||||
variables,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
@ -147,74 +125,10 @@ const Customers = () => {
|
||||||
R.sortWith([R.ascend(byAuthorized), R.descend(byLastActive)]),
|
R.sortWith([R.ascend(byAuthorized), R.descend(byLastActive)]),
|
||||||
)(filteredCustomers ?? [])
|
)(filteredCustomers ?? [])
|
||||||
|
|
||||||
const onFilterChange = filters => {
|
|
||||||
const filtersObject = getFiltersObj(filters)
|
|
||||||
|
|
||||||
setFilters(filters)
|
|
||||||
|
|
||||||
setVariables({
|
|
||||||
phone: filtersObject.phone,
|
|
||||||
name: filtersObject.name,
|
|
||||||
email: filtersObject.email,
|
|
||||||
address: filtersObject.address,
|
|
||||||
id: filtersObject.id,
|
|
||||||
})
|
|
||||||
|
|
||||||
refetch && refetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
const onFilterDelete = filter => {
|
|
||||||
const newFilters = R.filter(
|
|
||||||
f => !R.whereEq(R.pick(['type', 'value'], f), filter),
|
|
||||||
)(filters)
|
|
||||||
|
|
||||||
setFilters(newFilters)
|
|
||||||
|
|
||||||
const filtersObject = getFiltersObj(newFilters)
|
|
||||||
|
|
||||||
setVariables({
|
|
||||||
phone: filtersObject.phone,
|
|
||||||
name: filtersObject.name,
|
|
||||||
email: filtersObject.email,
|
|
||||||
address: filtersObject.address,
|
|
||||||
id: filtersObject.id,
|
|
||||||
})
|
|
||||||
|
|
||||||
refetch && refetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteAllFilters = () => {
|
|
||||||
setFilters([])
|
|
||||||
const filtersObject = getFiltersObj([])
|
|
||||||
|
|
||||||
setVariables({
|
|
||||||
phone: filtersObject.phone,
|
|
||||||
name: filtersObject.name,
|
|
||||||
email: filtersObject.email,
|
|
||||||
address: filtersObject.address,
|
|
||||||
id: filtersObject.id,
|
|
||||||
})
|
|
||||||
|
|
||||||
refetch && refetch()
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterOptions = R.path(['customerFilters'])(filtersResponse)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TitleSection
|
<TitleSection
|
||||||
title="Customers"
|
title="Customers"
|
||||||
appendix={
|
|
||||||
<div className="flex ml-4">
|
|
||||||
<SearchBox
|
|
||||||
loading={loadingFilters}
|
|
||||||
filters={filters}
|
|
||||||
options={filterOptions}
|
|
||||||
inputPlaceholder={'Search customers'}
|
|
||||||
onChange={onFilterChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
appendixRight={
|
appendixRight={
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Link color="primary" onClick={() => setShowCreationModal(true)}>
|
<Link color="primary" onClick={() => setShowCreationModal(true)}>
|
||||||
|
|
@ -227,21 +141,11 @@ const Customers = () => {
|
||||||
{ label: 'Cash-out', icon: <TxOutIcon /> },
|
{ label: 'Cash-out', icon: <TxOutIcon /> },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
{filters.length > 0 && (
|
|
||||||
<SearchFilter
|
|
||||||
entries={customersData.length}
|
|
||||||
filters={filters}
|
|
||||||
onFilterDelete={onFilterDelete}
|
|
||||||
deleteAllFilters={deleteAllFilters}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<CustomersList
|
<CustomersList
|
||||||
data={customersData}
|
data={customersData}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
onClick={handleCustomerClicked}
|
onClick={handleCustomerClicked}
|
||||||
loading={customerLoading}
|
loading={customerLoading}
|
||||||
triggers={triggers}
|
|
||||||
customRequests={customRequirementsData}
|
|
||||||
/>
|
/>
|
||||||
<CreateCustomerModal
|
<CreateCustomerModal
|
||||||
showModal={showCreationModal}
|
showModal={showCreationModal}
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,81 @@
|
||||||
import { format } from 'date-fns/fp'
|
import { format } from 'date-fns/fp'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React, { useMemo } from 'react'
|
||||||
|
import { MaterialReactTable, useMaterialReactTable } from 'material-react-table'
|
||||||
import { MainStatus } from '../../components/Status'
|
import { MainStatus } from '../../components/Status'
|
||||||
import DataTable from '../../components/tables/DataTable'
|
|
||||||
import TxInIcon from '../../styling/icons/direction/cash-in.svg?react'
|
import TxInIcon from '../../styling/icons/direction/cash-in.svg?react'
|
||||||
import TxOutIcon from '../../styling/icons/direction/cash-out.svg?react'
|
import TxOutIcon from '../../styling/icons/direction/cash-out.svg?react'
|
||||||
|
import {
|
||||||
|
defaultMaterialTableOpts,
|
||||||
|
alignRight,
|
||||||
|
} from '../../utils/materialReactTableOpts'
|
||||||
|
|
||||||
import { getFormattedPhone, getName } from './helper'
|
import { getFormattedPhone, getName } from './helper'
|
||||||
|
|
||||||
const CustomersList = ({ data, locale, onClick, loading }) => {
|
const CustomersList = ({ data, locale, onClick, loading }) => {
|
||||||
const elements = [
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
{
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
size: 315,
|
||||||
|
enableColumnFilter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'phone-email',
|
||||||
|
accessorFn: it =>
|
||||||
|
`${getFormattedPhone(it.phone, locale.country) || ''} ${it.email || ''}`,
|
||||||
|
size: 180,
|
||||||
header: 'Phone/email',
|
header: 'Phone/email',
|
||||||
width: 199,
|
|
||||||
view: it => `${getFormattedPhone(it.phone, locale.country) || ''}
|
|
||||||
${it.email || ''}`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'name',
|
||||||
header: 'Name',
|
header: 'Name',
|
||||||
width: 241,
|
accessorFn: getName,
|
||||||
view: getName,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Total Txs',
|
accessorKey: 'totalTxs',
|
||||||
width: 126,
|
header: 'Total txs',
|
||||||
textAlign: 'right',
|
size: 126,
|
||||||
view: it => `${Number.parseInt(it.totalTxs)}`,
|
enableColumnFilter: false,
|
||||||
|
...alignRight,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 'totalSpent',
|
||||||
|
accessorKey: 'totalSpent',
|
||||||
|
size: 152,
|
||||||
|
enableColumnFilter: false,
|
||||||
|
Cell: ({ cell, row }) =>
|
||||||
|
`${Number.parseFloat(cell.getValue())} ${row.original.lastTxFiatCode ?? ''}`,
|
||||||
header: 'Total spent',
|
header: 'Total spent',
|
||||||
width: 152,
|
...alignRight,
|
||||||
textAlign: 'right',
|
|
||||||
view: it =>
|
|
||||||
`${Number.parseFloat(it.totalSpent)} ${it.lastTxFiatCode ?? ''}`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Last active',
|
header: 'Last active',
|
||||||
width: 133,
|
// accessorKey: 'lastActive',
|
||||||
view: it =>
|
accessorFn: it => new Date(it.lastActive),
|
||||||
(it.lastActive && format('yyyy-MM-dd', new Date(it.lastActive))) ?? '',
|
size: 133,
|
||||||
|
enableColumnFilter: false,
|
||||||
|
Cell: ({ cell }) =>
|
||||||
|
(cell.getValue() &&
|
||||||
|
format('yyyy-MM-dd', new Date(cell.getValue()))) ??
|
||||||
|
'',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Last transaction',
|
header: 'Last transaction',
|
||||||
width: 161,
|
...alignRight,
|
||||||
textAlign: 'right',
|
size: 170,
|
||||||
view: it => {
|
enableColumnFilter: false,
|
||||||
const hasLastTx = !R.isNil(it.lastTxFiatCode)
|
accessorKey: 'lastTxFiat',
|
||||||
const LastTxIcon = it.lastTxClass === 'cashOut' ? TxOutIcon : TxInIcon
|
Cell: ({ cell, row }) => {
|
||||||
|
const hasLastTx = !R.isNil(row.original.lastTxFiatCode)
|
||||||
|
const LastTxIcon =
|
||||||
|
row.original.lastTxClass === 'cashOut' ? TxOutIcon : TxInIcon
|
||||||
const lastIcon = <LastTxIcon className="ml-3" />
|
const lastIcon = <LastTxIcon className="ml-3" />
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasLastTx &&
|
{hasLastTx &&
|
||||||
`${parseFloat(it.lastTxFiat)} ${it.lastTxFiatCode ?? ''}`}
|
`${parseFloat(cell.getValue())} ${row.original.lastTxFiatCode ?? ''}`}
|
||||||
{hasLastTx && lastIcon}
|
{hasLastTx && lastIcon}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
@ -59,20 +83,37 @@ const CustomersList = ({ data, locale, onClick, loading }) => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Status',
|
header: 'Status',
|
||||||
width: 191,
|
id: 'status',
|
||||||
view: it => <MainStatus statuses={[it.authorizedStatus]} />,
|
size: 100,
|
||||||
|
enableColumnFilter: false,
|
||||||
|
accessorKey: 'authorizedStatus',
|
||||||
|
Cell: ({ cell }) => <MainStatus statuses={[cell.getValue()]} />,
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
|
||||||
|
const table = useMaterialReactTable({
|
||||||
|
...defaultMaterialTableOpts,
|
||||||
|
columns: columns,
|
||||||
|
data,
|
||||||
|
initialState: {
|
||||||
|
...defaultMaterialTableOpts.initialState,
|
||||||
|
columnVisibility: {
|
||||||
|
id: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
state: { isLoading: loading },
|
||||||
|
getRowId: it => it.id,
|
||||||
|
muiTableBodyRowProps: ({ row }) => ({
|
||||||
|
onClick: () => onClick(row),
|
||||||
|
sx: { cursor: 'pointer' },
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DataTable
|
<MaterialReactTable table={table} />
|
||||||
loading={loading}
|
|
||||||
emptyText="No customers so far"
|
|
||||||
elements={elements}
|
|
||||||
data={data}
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ const { p } = typographyStyles
|
||||||
let theme = createTheme({
|
let theme = createTheme({
|
||||||
typography: {
|
typography: {
|
||||||
fontFamily: inputFontFamily,
|
fontFamily: inputFontFamily,
|
||||||
|
root: { ...p },
|
||||||
|
body1: { ...p },
|
||||||
},
|
},
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
|
|
@ -56,6 +58,18 @@ theme = createTheme(theme, {
|
||||||
body1: { ...p },
|
body1: { ...p },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
MuiCircularProgress: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
color: primaryColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiTableCell: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: { ...p },
|
||||||
|
},
|
||||||
|
},
|
||||||
MuiIconButtonBase: {
|
MuiIconButtonBase: {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
disableRipple: true,
|
disableRipple: true,
|
||||||
|
|
|
||||||
33
packages/admin-ui/src/utils/materialReactTableOpts.js
Normal file
33
packages/admin-ui/src/utils/materialReactTableOpts.js
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
const defaultMaterialTableOpts = {
|
||||||
|
enableGlobalFilter: false,
|
||||||
|
paginationDisplayMode: 'pages',
|
||||||
|
enableColumnActions: false,
|
||||||
|
initialState: { density: 'compact' },
|
||||||
|
mrtTheme: it => ({
|
||||||
|
...it,
|
||||||
|
baseBackgroundColor: '#fff',
|
||||||
|
}),
|
||||||
|
muiTopToolbarProps: () => ({
|
||||||
|
sx: {
|
||||||
|
backgroundColor: 'var(--zodiac)',
|
||||||
|
'& .MuiButtonBase-root': { color: '#fff' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
muiTableHeadRowProps: () => ({
|
||||||
|
sx: { backgroundColor: 'var(--zircon)' },
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const alignRight = {
|
||||||
|
muiTableHeadCellProps: {
|
||||||
|
align: 'right',
|
||||||
|
},
|
||||||
|
muiTableBodyCellProps: {
|
||||||
|
align: 'right',
|
||||||
|
},
|
||||||
|
muiTableFooterCellProps: {
|
||||||
|
align: 'right',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export { defaultMaterialTableOpts, alignRight }
|
||||||
|
|
@ -27,18 +27,5 @@ function transaction() {
|
||||||
|
|
||||||
return db.any(sql)
|
return db.any(sql)
|
||||||
}
|
}
|
||||||
function customer() {
|
|
||||||
const sql = `SELECT DISTINCT * FROM (
|
|
||||||
SELECT 'phone' AS type, phone AS value FROM customers WHERE phone IS NOT NULL UNION
|
|
||||||
SELECT 'email' AS type, email AS value FROM customers WHERE email IS NOT NULL UNION
|
|
||||||
SELECT 'name' AS type, id_card_data::json->>'firstName' AS value FROM customers WHERE id_card_data::json->>'firstName' IS NOT NULL AND id_card_data::json->>'lastName' IS NULL UNION
|
|
||||||
SELECT 'name' AS type, id_card_data::json->>'lastName' AS value FROM customers WHERE id_card_data::json->>'firstName' IS NULL AND id_card_data::json->>'lastName' IS NOT NULL UNION
|
|
||||||
SELECT 'name' AS type, concat(id_card_data::json->>'firstName', ' ', id_card_data::json->>'lastName') AS value FROM customers WHERE id_card_data::json->>'firstName' IS NOT NULL AND 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 }
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { transaction, customer }
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
const authentication = require('../modules/userManagement')
|
const authentication = require('../modules/userManagement')
|
||||||
const anonymous = require('../../../constants').anonymousCustomer
|
const anonymous = require('../../../constants').anonymousCustomer
|
||||||
const customers = require('../../../customers')
|
const customers = require('../../../customers')
|
||||||
const filters = require('../../filters')
|
|
||||||
const customerNotes = require('../../../customer-notes')
|
const customerNotes = require('../../../customer-notes')
|
||||||
const machineLoader = require('../../../machine-loader')
|
const machineLoader = require('../../../machine-loader')
|
||||||
|
|
||||||
|
|
@ -22,7 +21,6 @@ const resolvers = {
|
||||||
customers.getCustomersList(phone, name, address, id, email),
|
customers.getCustomersList(phone, name, address, id, email),
|
||||||
customer: (...[, { customerId }]) =>
|
customer: (...[, { customerId }]) =>
|
||||||
customers.getCustomerById(customerId).then(addLastUsedMachineName),
|
customers.getCustomerById(customerId).then(addLastUsedMachineName),
|
||||||
customerFilters: () => filters.customer(),
|
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
setCustomer: (root, { customerId, customerInput }, context) => {
|
setCustomer: (root, { customerId, customerInput }, context) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue