diff --git a/lib/customers.js b/lib/customers.js index 2df001c5..4f01ea7b 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -138,6 +138,103 @@ async function updateCustomer (id, data, userToken) { return getCustomerById(id) } +/** + * Update all customer record + * + * @name save + * @function + * + * @param {string} id Customer's id + * @param {object} data Fields to update + * + * @returns {Promise} Newly updated Customer + */ + +function edit (id, data, userToken) { + const defaults = [ + 'front_camera', + 'id_card_data', + 'id_card_photo', + 'us_ssn', + 'subcriber_info', + 'name' + ] + const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, _.omitBy(_.isNil, data))) + if (_.isEmpty(filteredData)) return getCustomerById(id) + if (filteredData.front_camera_data || filteredData.id_card_photo_data) return getCustomerById(id) + const formattedData = enhanceEditedFields(filteredData, userToken) + const defaultDbData = { + customer_id: id, + created: new Date(), + ...formattedData + } + console.log(formattedData, 'FORMATED', defaultDbData, 'DEFAULT DB') + const cs = new Pgp.helpers.ColumnSet(_.keys(defaultDbData), + { table: 'edited_customer_data' }) + const onConflict = ' ON CONFLICT (customer_id) DO UPDATE SET ' + + cs.assignColumns({ from: 'EXCLUDED', skip: ['customer_id', 'created'] }) + const upsert = Pgp.helpers.insert(defaultDbData, cs) + onConflict + db.none(upsert) + .then(res => { + console.log(res) + return getCustomerById(id) + }) +} + +/** + * Add *edited_by and *edited_at fields with acting user's token + * and date of override respectively before saving to db. + * + * @name enhanceEditedFields + * @function + * + * @param {object} fields Fields to be enhanced + * @param {string} userToken Acting user's token + * @returns {object} fields enhanced with *_by and *_at fields + */ + +function enhanceEditedFields (fields, userToken) { + if (!userToken) return fields + _.mapKeys((field) => { + fields[field + '_edited_by'] = userToken + fields[field + '_edited_at'] = 'now()^' + }, fields) + return fields +} + +/** + * Remove the edited data from the db record + * + * @name enhanceOverrideFields + * @function + * + * @param {string} id Customer's id + * @param {object} data Fields to be deleted + * + * @returns {Promise} Newly updated Customer + * + */ + +function deleteEditedData (id, data) { + // NOT IMPLEMENTING THIS FEATURE FOR NOW + // const defaults = [ + // 'front_camera', + // 'id_card_data', + // 'id_card_photo', + // 'us_ssn', + // 'subcriber_info', + // 'name' + // ] + // const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, data)) + // if (_.isEmpty(filteredData)) return getCustomerById(id) + + // const cs = new Pgp.helpers.ColumnSet(_.keys(filteredData), + // { table: 'edited_customer_data' }) + // const update = Pgp.helpers.update(filteredData, cs) + // db.none(update) + return getCustomerById(id) +} + const invalidateCustomerNotifications = (id, data) => { if (data.authorized_override !== 'verified') return Promise.resolve() @@ -510,7 +607,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null) AND ($6 IS NULL OR id_card_data::json->>'address' = $6) AND ($7 IS NULL OR id_card_data::json->>'documentNumber' = $7) limit $3` - return db.any(sql, [passableErrorCodes, anonymous.uuid, NUM_RESULTS, phone, name, address, id ]) + return db.any(sql, [ passableErrorCodes, anonymous.uuid, NUM_RESULTS, phone, name, address, id ]) .then(customers => Promise.all(_.map(customer => { return populateOverrideUsernames(customer) .then(camelize) @@ -518,11 +615,11 @@ function getCustomersList (phone = null, name = null, address = null, id = null) } /** - * Query all customers, ordered by last activity + * Query a specific customer, ordered by last activity * and with aggregate columns based on their * transactions * - * @returns {array} Array of customers with it's transactions aggregations + * @returns {array} A single customer instance with non edited */ function getCustomerById (id) { const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',') @@ -560,6 +657,17 @@ function getCustomerById (id) { .then(camelize) } +/** + * Query the specific customer manually edited data + * + * @param {String} id customer id + * + * @returns {array} A single customer instance with the most recent data + */ +function getManuallyEditedData (id) { + +} + /** * @param {String} id customer id * @param {Object} patch customer update record @@ -768,35 +876,35 @@ function updateFrontCamera (id, patch) { }) } -function addCustomField(customerId, label, value) { +function addCustomField (customerId, label, value) { const sql = `SELECT * FROM custom_field_definitions WHERE label=$1 LIMIT 1` return db.oneOrNone(sql, [label]) .then(res => db.tx(t => { - if (_.isNil(res)) { - const fieldId = uuid.v4() - const q1 = t.none(`INSERT INTO custom_field_definitions (id, label) VALUES ($1, $2)`, [fieldId, label]) - const q2 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, fieldId, value]) - return t.batch([q1, q2]) - } + if (_.isNil(res)) { + const fieldId = uuid.v4() + const q1 = t.none(`INSERT INTO custom_field_definitions (id, label) VALUES ($1, $2)`, [fieldId, label]) + const q2 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, fieldId, value]) + return t.batch([q1, q2]) + } - if (!_.isNil(res) && !res.active) { - const q1 = t.none(`UPDATE custom_field_definitions SET active = true WHERE id=$1`, [res.id]) - const q2 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, res.id, value]) - return t.batch([q1, q2]) - } else if (!_.isNil(res) && res.active) { - const q1 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, res.id, value]) - return t.batch([q1]) - } - }) + if (!_.isNil(res) && !res.active) { + const q1 = t.none(`UPDATE custom_field_definitions SET active = true WHERE id=$1`, [res.id]) + const q2 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, res.id, value]) + return t.batch([q1, q2]) + } else if (!_.isNil(res) && res.active) { + const q1 = t.none(`INSERT INTO customer_custom_field_pairs (customer_id, custom_field_id, value) VALUES ($1, $2, $3)`, [customerId, res.id, value]) + return t.batch([q1]) + } + }) ) } -function saveCustomField(customerId, fieldId, newValue) { +function saveCustomField (customerId, fieldId, newValue) { const sql = `UPDATE customer_custom_field_pairs SET value=$1 WHERE customer_id=$2 AND custom_field_id=$3` return db.none(sql, [newValue, customerId, fieldId]) } -function removeCustomField(customerId, fieldId) { +function removeCustomField (customerId, fieldId) { const sql = `SELECT * FROM customer_custom_field_pairs WHERE custom_field_id=$1` return db.any(sql, [fieldId]) .then(res => db.tx(t => { @@ -827,5 +935,8 @@ module.exports = { addCustomField, saveCustomField, removeCustomField, + edit, + getManuallyEditedData, + deleteEditedData, updateTxCustomerPhoto } diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index a26a86fd..b22454bd 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -19,7 +19,14 @@ const resolvers = { }, addCustomField: (...[, { customerId, label, value }]) => customers.addCustomField(customerId, label, value), saveCustomField: (...[, { customerId, fieldId, newValue }]) => customers.saveCustomField(customerId, fieldId, newValue), - removeCustomField: (...[, [ { customerId, fieldId } ]]) => customers.removeCustomField(customerId, fieldId) + removeCustomField: (...[, [ { customerId, fieldId } ]]) => customers.removeCustomField(customerId, fieldId), + editCustomer: (root, { customerId, customerEdit }, context) => { + const token = !!context.req.cookies.lid && context.req.session.user.id + return customers.edit(customerId, customerEdit, token) + }, + deleteEditedData: (root, { customerId, customerEdit }) => { + return customers.deleteEditedData(customerId, customerEdit) + } } } diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index 134734a7..6d0129db 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -12,6 +12,7 @@ const typeDef = gql` authorizedOverride: String daysSuspended: Int isSuspended: Boolean + frontCamera: String frontCameraPath: String frontCameraAt: Date frontCameraOverride: String @@ -21,6 +22,7 @@ const typeDef = gql` idCardData: JSONObject idCardDataOverride: String idCardDataExpiration: Date + idCardPhoto: String idCardPhotoPath: String idCardPhotoOverride: String usSsn: String @@ -65,6 +67,13 @@ const typeDef = gql` subscriberInfo: Boolean } + input CustomerEdit { + frontCamera: String + idCardData: JSONObject + idCardPhoto: String + usSsn: String + } + type Query { customers(phone: String, name: String, address: String, id: String): [Customer] @auth customer(customerId: ID!): Customer @auth @@ -76,6 +85,8 @@ const typeDef = gql` addCustomField(customerId: ID!, label: String!, value: String!): CustomerCustomField @auth saveCustomField(customerId: ID!, fieldId: ID!, value: String!): CustomerCustomField @auth removeCustomField(customerId: ID!, fieldId: ID!): CustomerCustomField @auth + editCustomer(customerId: ID!, customerEdit: CustomerEdit): Customer @auth + deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer @auth } ` diff --git a/migrations/1635159374499-editable-customer-data.js b/migrations/1635159374499-editable-customer-data.js new file mode 100644 index 00000000..76d6618c --- /dev/null +++ b/migrations/1635159374499-editable-customer-data.js @@ -0,0 +1,33 @@ +const db = require('./db') + +exports.up = function (next) { + var sql = [ + `CREATE TABLE edited_customer_data ( + customer_id uuid PRIMARY KEY REFERENCES customers(id), + id_card_data JSON, + id_card_data_edited_at TIMESTAMPTZ, + id_card_data_edited_by UUID REFERENCES users(id), + front_camera_path TEXT, + front_camera_edited_at TIMESTAMPTZ, + front_camera_edited_by UUID REFERENCES users(id), + id_card_photo_path TEXT, + id_card_photo_edited_at TIMESTAMPTZ, + id_card_photo_edited_by UUID REFERENCES users(id), + subscriber_info JSON, + subscriber_info_edited_at TIMESTAMPTZ, + subscriber_info_edited_by UUID REFERENCES users(id), + name TEXT, + name_info_edited_at TIMESTAMPTZ, + name_info_edited_by UUID REFERENCES users(id), + us_ssn TEXT, + us_ssn_edited_at TIMESTAMPTZ, + us_ssn_edited_by UUID REFERENCES users(id), + created TIMESTAMPTZ NOT NULL DEFAULT now() )` + ] + + db.multi(sql, next) +} + +exports.down = function (next) { + next() +} diff --git a/new-lamassu-admin/src/components/buttons/ActionButton.js b/new-lamassu-admin/src/components/buttons/ActionButton.js index 264adac8..02d7d107 100644 --- a/new-lamassu-admin/src/components/buttons/ActionButton.js +++ b/new-lamassu-admin/src/components/buttons/ActionButton.js @@ -12,7 +12,9 @@ const ActionButton = memo( const classNames = { [classes.actionButton]: true, [classes.primary]: color === 'primary', - [classes.secondary]: color === 'secondary' + [classes.secondary]: color === 'secondary', + [classes.spring]: color === 'spring', + [classes.tomato]: color === 'tomato' } return ( diff --git a/new-lamassu-admin/src/components/buttons/ActionButton.styles.js b/new-lamassu-admin/src/components/buttons/ActionButton.styles.js index a775f811..5d0cde44 100644 --- a/new-lamassu-admin/src/components/buttons/ActionButton.styles.js +++ b/new-lamassu-admin/src/components/buttons/ActionButton.styles.js @@ -5,7 +5,12 @@ import { subheaderColor, subheaderDarkColor, offColor, - offDarkColor + offDarkColor, + secondaryColor, + secondaryColorDark, + secondaryColorDarker, + errorColor, + errorColorDarker } from 'src/styling/variables' const { p } = typographyStyles @@ -68,6 +73,42 @@ export default { display: 'flex' } }, + spring: { + extend: colors(secondaryColor, secondaryColorDark, secondaryColorDarker), + color: white, + '&:active': { + '& $actionButtonIcon': { + display: 'flex' + }, + '& $actionButtonIconActive': { + display: 'none' + } + }, + '& $actionButtonIcon': { + display: 'none' + }, + '& $actionButtonIconActive': { + display: 'flex' + } + }, + tomato: { + extend: colors(errorColor, errorColorDarker, errorColor), + color: white, + '&:active': { + '& $actionButtonIcon': { + display: 'flex' + }, + '& $actionButtonIconActive': { + display: 'none' + } + }, + '& $actionButtonIcon': { + display: 'none' + }, + '& $actionButtonIconActive': { + display: 'flex' + } + }, actionButtonIcon: { display: 'flex', paddingRight: 7, diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js index bb034bbd..77934e0c 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js @@ -57,7 +57,12 @@ const Photo = ({ show, src }) => { ) } -const CustomerData = ({ customer, updateCustomer }) => { +const CustomerData = ({ + customer, + updateCustomer, + editCustomer, + deleteEditedData +}) => { const classes = useStyles() const [listView, setListView] = useState(false) @@ -75,11 +80,14 @@ const CustomerData = ({ customer, updateCustomer }) => { : 'Failed' const customEntries = null // get customer custom entries + const customRequirements = null // get customer custom requirements const isEven = elem => elem % 2 === 0 const getVisibleCards = _.filter( - elem => !_.isEmpty(elem.data) || !_.isNil(elem.children) + elem => + !_.isEmpty(elem.fields) || + (!_.isNil(elem.children) && !_.isNil(elem.state)) ) const getAvailableFields = _.filter(({ value }) => value !== '') @@ -96,6 +104,12 @@ const CustomerData = ({ customer, updateCustomer }) => { }), usSsn: Yup.object().shape({ usSsn: Yup.string() + }), + idCardPhoto: Yup.object().shape({ + idCardPhoto: Yup.mixed() + }), + frontCamera: Yup.object().shape({ + frontCamera: Yup.mixed() }) } @@ -160,7 +174,7 @@ const CustomerData = ({ customer, updateCustomer }) => { const usSsnElements = [ { - name: 'us ssn', + name: 'usSsn', label: 'US SSN', value: `${customer.usSsn ?? ''}`, component: TextInput, @@ -168,6 +182,9 @@ const CustomerData = ({ customer, updateCustomer }) => { } ] + const idCardPhotoElements = [{ name: 'idCardPhoto' }] + const frontCameraElements = [{ name: 'frontCamera' }] + const initialValues = { idScan: { name: '', @@ -180,19 +197,26 @@ const CustomerData = ({ customer, updateCustomer }) => { }, usSsn: { usSsn: '' + }, + frontCamera: { + frontCamera: null + }, + idCardPhoto: { + idCardPhoto: null } } const cards = [ { - data: getAvailableFields(idScanElements), + fields: getAvailableFields(idScanElements), title: 'ID Scan', titleIcon: , state: R.path(['idCardDataOverride'])(customer), authorize: () => updateCustomer({ idCardDataOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ idCardDataOverride: OVERRIDE_REJECTED }), - save: values => console.log(values), + deleteEditedData: () => deleteEditedData({ idCardData: null }), + save: values => editCustomer({ idCardData: values }), validationSchema: schemas.idScan, initialValues: initialValues.idScan }, @@ -217,17 +241,18 @@ const CustomerData = ({ customer, updateCustomer }) => { authorize: () => updateCustomer({ sanctionsOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ sanctionsOverride: OVERRIDE_REJECTED }), - save: () => {}, children: {sanctionsDisplay} }, { + fields: getAvailableFields(frontCameraElements), title: 'Front facing camera', titleIcon: , state: R.path(['frontCameraOverride'])(customer), authorize: () => updateCustomer({ frontCameraOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ frontCameraOverride: OVERRIDE_REJECTED }), - save: () => {}, + save: values => editCustomer({ frontCamera: values.frontCamera }), + deleteEditedData: () => deleteEditedData({ frontCamera: null }), children: customer.frontCameraPath ? ( { customer )}`} /> - ) : null + ) : null, + hasImage: true, + validationSchema: schemas.frontCamera, + initialValues: initialValues.frontCamera }, { + fields: getAvailableFields(idCardPhotoElements), title: 'ID card image', titleIcon: , state: R.path(['idCardPhotoOverride'])(customer), authorize: () => updateCustomer({ idCardPhotoOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ idCardPhotoOverride: OVERRIDE_REJECTED }), - save: () => {}, + save: values => editCustomer({ idCardPhoto: values.idCardPhoto }), + deleteEditedData: () => deleteEditedData({ idCardPhoto: null }), children: customer.idCardPhotoPath ? ( - ) : null + ) : null, + hasImage: true, + validationSchema: schemas.idCardPhoto, + initialValues: initialValues.idCardPhoto }, { - data: getAvailableFields(usSsnElements), + fields: getAvailableFields(usSsnElements), title: 'US SSN', titleIcon: , state: R.path(['usSsnOverride'])(customer), authorize: () => updateCustomer({ usSsnOverride: OVERRIDE_AUTHORIZED }), reject: () => updateCustomer({ usSsnOverride: OVERRIDE_REJECTED }), - save: () => {}, + save: values => editCustomer({ usSsn: values.usSsn }), + deleteEditedData: () => deleteEditedData({ usSsn: null }), validationSchema: schemas.usSsn, initialValues: initialValues.usSsn } @@ -272,11 +306,13 @@ const CustomerData = ({ customer, updateCustomer }) => { reject, state, titleIcon, - data, + fields, save, + deleteEditedData, children, validationSchema, - initialValues + initialValues, + hasImage }, idx ) => { @@ -288,11 +324,13 @@ const CustomerData = ({ customer, updateCustomer }) => { reject={reject} state={state} titleIcon={titleIcon} - data={data} + hasImage={hasImage} + fields={fields} children={children} validationSchema={validationSchema} initialValues={initialValues} - save={save}> + save={save} + deleteEditedData={deleteEditedData}> ) } @@ -317,7 +355,7 @@ const CustomerData = ({ customer, updateCustomer }) => { onClick={() => setListView(true)}>
- {!listView && ( + {!listView && customer && ( {visibleCards.map((elem, idx) => { @@ -333,7 +371,12 @@ const CustomerData = ({ customer, updateCustomer }) => { )} {customEntries && (
-
{'Custom data entry'}
+ Custom data entry +
+ )} + {customRequirements && ( +
+ Custom requirements
)}
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js b/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js index 5977bbfe..373e2f0c 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerData.styles.js @@ -20,22 +20,30 @@ export default { marginRight: 12 }, wrapper: { - display: 'flex' + display: 'block', + overflow: 'hidden', + whiteSpace: 'nowrap' }, separator: { - display: 'flex', - flexBasis: '100%', - justifyContent: 'center', color: offColor, - margin: [[8, 0, 8, 0]], - '&::before, &::after': { - content: '', - flexGrow: 1, + margin: [[8, 0, 8, 150]], + position: 'relative', + display: 'inline-block', + '&:before, &:after': { + content: '""', + position: 'absolute', background: offColor, - height: 1, - fontSize: 1, - lineHeight: 0, - margin: [[0, 8, 0, 8]] + top: '50%', + width: 1000, + height: 1 + }, + '&:before': { + right: '100%', + marginRight: 15 + }, + '&:after': { + left: '100%', + marginLeft: 15 } } } diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index e197416b..15e36f4e 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -116,6 +116,29 @@ const SET_CUSTOMER = gql` } } ` +const EDIT_CUSTOMER = gql` + mutation editCustomer($customerId: ID!, $customerEdit: CustomerEdit) { + editCustomer(customerId: $customerId, customerEdit: $customerEdit) { + id + frontCamera + idCardData + idCardPhoto + usSsn + } + } +` + +const DELETE_EDITED_CUSTOMER = gql` + mutation deleteEditedData($customerId: ID!, $customerEdit: CustomerEdit) { + deleteEditedData(customerId: $customerId, customerEdit: $customerEdit) { + id + frontCamera + idCardData + idCardPhoto + usSsn + } + } +` const CustomerProfile = memo(() => { const history = useHistory() @@ -133,6 +156,14 @@ const CustomerProfile = memo(() => { } ) + const [editCustomerData] = useMutation(EDIT_CUSTOMER, { + onCompleted: () => getCustomer() + }) + + const [deleteCustomerEditedData] = useMutation(DELETE_EDITED_CUSTOMER, { + onCompleted: () => getCustomer() + }) + const [setCustomer] = useMutation(SET_CUSTOMER, { onCompleted: () => getCustomer() }) @@ -145,6 +176,22 @@ const CustomerProfile = memo(() => { } }) + const editCustomer = it => + editCustomerData({ + variables: { + customerId, + customerEdit: it + } + }) + + const deleteEditedData = it => + deleteCustomerEditedData({ + variables: { + customerId, + customerEdit: it + } + }) + const onClickSidebarItem = code => setClickedItem(code) const configData = R.path(['config'])(customerResponse) ?? [] @@ -295,7 +342,9 @@ const CustomerProfile = memo(() => {
+ updateCustomer={updateCustomer} + editCustomer={editCustomer} + deleteEditedData={deleteEditedData}>
)} diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js index c4792419..4f05559f 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js @@ -2,6 +2,7 @@ import { CardContent, Card, Grid } from '@material-ui/core' import { makeStyles } from '@material-ui/core/styles' import classnames from 'classnames' import { Form, Formik, Field as FormikField } from 'formik' +import * as R from 'ramda' import { useState, React } from 'react' import ErrorMessage from 'src/components/ErrorMessage' @@ -15,12 +16,16 @@ import { OVERRIDE_REJECTED, OVERRIDE_PENDING } from 'src/pages/Customers/components/propertyCard' +import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg' +import { ReactComponent as DeleteReversedIcon } from 'src/styling/icons/action/delete/white.svg' import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' import { ReactComponent as EditReversedIcon } from 'src/styling/icons/action/edit/white.svg' -import { ReactComponent as AuthorizeReversedIcon } from 'src/styling/icons/button/authorize/white.svg' -import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/zodiac.svg' +import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/white.svg' +import { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/white.svg' import { ReactComponent as CancelReversedIcon } from 'src/styling/icons/button/cancel/white.svg' import { ReactComponent as CancelIcon } from 'src/styling/icons/button/cancel/zodiac.svg' +import { ReactComponent as ReplaceReversedIcon } from 'src/styling/icons/button/replace/white.svg' +import { ReactComponent as ReplaceIcon } from 'src/styling/icons/button/replace/zodiac.svg' import { ReactComponent as SaveReversedIcon } from 'src/styling/icons/circle buttons/save/white.svg' import { ReactComponent as SaveIcon } from 'src/styling/icons/circle buttons/save/zodiac.svg' import { comet } from 'src/styling/variables' @@ -70,7 +75,7 @@ const fieldUseStyles = makeStyles(fieldStyles) const EditableField = ({ editing, field, size, ...props }) => { const classes = fieldUseStyles() - + console.log('FIELDDDDDDDD', field) const classNames = { [classes.field]: true, [classes.notEditing]: !editing @@ -90,8 +95,9 @@ const EditableField = ({ editing, field, size, ...props }) => { { } const EditableCard = ({ - data, + fields, save, authorize, + hasImage, reject, state, title, titleIcon, - children + children, + validationSchema, + initialValues, + deleteEditedData }) => { const classes = useStyles() const [editing, setEditing] = useState(false) + const [input, setInput] = useState(null) const [error, setError] = useState(null) + const triggerInput = () => input.click() + const label1ClassNames = { [classes.label1]: true, [classes.label1Pending]: state === OVERRIDE_PENDING, @@ -130,9 +143,7 @@ const EditableCard = ({ ? { label: 'Rejected', type: 'error' } : { label: 'Accepted', type: 'success' } - const editableField = field => { - return - } + const reader = new FileReader() return (
@@ -142,99 +153,172 @@ const EditableCard = ({ {titleIcon}

{title}

-
- -
+ {state && ( +
+ +
+ )}
+ {children} save(values)} onReset={() => { setEditing(false) setError(false) }}> -
- -
- - - {data?.map((field, idx) => { - return idx >= 0 && idx < 4 ? editableField(field) : null - })} + {({ values, touched, errors, setFieldValue }) => ( + + + {console.log(values, touched, errors, 'FORMIK STATUS')} +
+ + + {!hasImage && + fields?.map((field, idx) => { + return idx >= 0 && idx < 4 ? ( + + ) : null + })} + + + {!hasImage && + fields?.map((field, idx) => { + return idx >= 4 ? ( + + ) : null + })} + - - {data?.map((field, idx) => { - return idx >= 4 ? editableField(field) : null - })} - - -
- {children} -
- {!editing && ( -
- setEditing(true)}> - {`Edit`} - -
- )} - {editing && ( -
- {data && ( -
+
+
+ {!editing && ( +
+
- Save + color="primary" + type="button" + Icon={DeleteIcon} + InverseIcon={DeleteReversedIcon} + onClick={() => deleteEditedData()}> + {`Delete`}
- )} -
+ - Cancel + color="primary" + Icon={EditIcon} + InverseIcon={EditReversedIcon} + onClick={() => setEditing(true)}> + {`Edit`}
- {authorized.label !== 'Accepted' && ( -
- authorize()}> - {'Authorize'} - + )} + {editing && ( +
+
+ {hasImage && ( + triggerInput()}> + { +
+ setInput(fileInput)} + onChange={event => { + // need to store it locally if we want to display it even after saving to db + + const file = R.head(event.target.files) + + reader.onloadend = () => { + // use a regex to remove data url part + setFieldValue( + R.head(fields).name, + reader.result + .replace('data:', '') + .replace(/^.+,/, '') + ) + } + reader.readAsDataURL(file) + event.target.value = null + }} + /> + Replace +
+ } +
+ )}
- )} - {authorized.label !== 'Rejected' && ( - reject()}> - {'Reject'} - - )} - {error && ( - Failed to save changes - )} -
- )} -
- +
+ {fields && ( +
+ + Save + +
+ )} +
+ + Cancel + +
+ {authorized.label !== 'Accepted' && ( +
+ authorize()}> + Authorize + +
+ )} + {authorized.label !== 'Rejected' && ( + reject()}> + Reject + + )} + {error && ( + Failed to save changes + )} +
+
+ )} +
+ + )} diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js index 1c2a8603..f97fea44 100644 --- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js +++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.styles.js @@ -20,6 +20,20 @@ export default { display: 'flex', justifyContent: 'right' }, + deleteButton: { + marginRight: 8 + }, + editingWrapper: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between' + }, + replace: { + marginTop: 30 + }, + input: { + display: 'none' + }, button: { marginRight: 8 }, diff --git a/new-lamassu-admin/src/styling/variables.js b/new-lamassu-admin/src/styling/variables.js index 5c27fb7f..c2fb2a9b 100644 --- a/new-lamassu-admin/src/styling/variables.js +++ b/new-lamassu-admin/src/styling/variables.js @@ -25,6 +25,7 @@ const white = '#ffffff' // Error const tomato = '#ff584a' +const tomato1 = '#E45043' const mistyRose = '#ffeceb' const pumpkin = '#ff7311' const linen = '#fbf3ec' @@ -47,6 +48,7 @@ const offColor = comet const offDarkColor = comet2 const placeholderColor = comet const errorColor = tomato +const errorColorDarker = tomato1 const offErrorColor = mistyRose const inputBorderColor = primaryColor @@ -148,6 +150,7 @@ export { linkPrimaryColor, linkSecondaryColor, errorColor, + errorColorDarker, offErrorColor, inputBorderColor, // font sizes