diff --git a/lib/customer-notes.js b/lib/customer-notes.js index e89c457d..b9e4ae81 100644 --- a/lib/customer-notes.js +++ b/lib/customer-notes.js @@ -4,27 +4,28 @@ const _ = require('lodash/fp') const db = require('./db') const getCustomerNotes = customerId => { - const sql = `SELECT * FROM customer_notes WHERE customer_id=$1 LIMIT 1` + const sql = `SELECT * FROM customer_notes WHERE customer_id=$1` return db.oneOrNone(sql, [customerId]).then(res => _.mapKeys((_, key) => _.camelize(key), res)) } -const createCustomerNotes = (customerId, userId, content) => { - const sql = `INSERT INTO customer_notes (id, customer_id, last_edited_by, last_edited_at, content) VALUES ($1, $2, $3, now(), $4)` - return db.none(sql, [uuid.v4(), customerId, userId, content]) +const createCustomerNote = (customerId, userId, title, content) => { + const sql = `INSERT INTO customer_notes (id, customer_id, last_edited_by, last_edited_at, title, content) VALUES ($1, $2, $3, now(), $4, $5)` + return db.none(sql, [uuid.v4(), customerId, userId, title, content]) } -const updateCustomerNotes = (customerId, userId, content) => { - const sql = `UPDATE customer_notes SET last_edited_at=now(), last_edited_by=$1, content=$2 WHERE customer_id=$3 RETURNING *` - return db.any(sql, [userId, content, customerId]) - .then(res => { - if (_.isEmpty(res)) { - createCustomerNotes(customerId, userId, content) - } - }) +const deleteCustomerNote = noteId => { + const sql = `DELETE FROM customer_notes WHERE id=$1` + return db.none(sql, [noteId]) +} + +const updateCustomerNote = (noteId, userId, content) => { + const sql = `UPDATE customer_notes SET last_edited_at=now(), last_edited_by=$1, content=$2 WHERE id=$3` + return db.none(sql, [userId, content, noteId]) } module.exports = { getCustomerNotes, - createCustomerNotes, - updateCustomerNotes + createCustomerNote, + deleteCustomerNote, + updateCustomerNote } diff --git a/lib/customers.js b/lib/customers.js index fc089d05..89ea723f 100644 --- a/lib/customers.js +++ b/lib/customers.js @@ -632,7 +632,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null) 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, sanctions_override, total_txs, total_spent, created AS last_active, fiat AS last_tx_fiat, - fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, custom_fields, content AS notes + fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, custom_fields, notes FROM ( SELECT c.id, c.authorized_override, greatest(0, date_part('day', c.suspended_until - NOW())) AS days_suspended, @@ -640,7 +640,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null) c.front_camera_path, c.front_camera_override, c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration, c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions, - c.sanctions_at, c.sanctions_override, t.tx_class, t.fiat, t.fiat_code, t.created, cn.content, + c.sanctions_at, c.sanctions_override, t.tx_class, t.fiat, t.fiat_code, t.created, cn.notes, row_number() OVER (partition by c.id order by t.created desc) AS rn, sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (partition by c.id) AS total_txs, coalesce(sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (partition by c.id), 0) AS total_spent, ccf.custom_fields @@ -656,7 +656,8 @@ function getCustomersList (phone = null, name = null, address = null, id = null) ) cf GROUP BY cf.customer_id ) ccf ON c.id = ccf.customer_id LEFT OUTER JOIN ( - SELECT customer_id, content FROM customer_notes + SELECT customer_id, coalesce(json_agg(customer_notes.*), '[]'::json) AS notes FROM customer_notes + GROUP BY customer_notes.customer_id ) cn ON c.id = cn.customer_id WHERE c.id != $2 ) AS cl WHERE rn = 1 @@ -669,6 +670,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null) .then(customers => Promise.all(_.map(customer => { return populateOverrideUsernames(customer) .then(camelize) + .then(it => ({ ...it, notes: it.notes.map(camelize) })) }, customers))) } @@ -685,7 +687,7 @@ function getCustomerById (id) { 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, sanctions_override, total_txs, total_spent, created AS last_active, fiat AS last_tx_fiat, - fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, content AS notes + fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, notes FROM ( SELECT c.id, c.authorized_override, greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended, @@ -693,7 +695,7 @@ function getCustomerById (id) { c.front_camera_path, c.front_camera_override, c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration, c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions, - c.sanctions_at, c.sanctions_override, c.subscriber_info, t.tx_class, t.fiat, t.fiat_code, t.created, cn.content, + c.sanctions_at, c.sanctions_override, c.subscriber_info, t.tx_class, t.fiat, t.fiat_code, t.created, cn.notes, row_number() OVER (PARTITION BY c.id ORDER BY t.created DESC) AS rn, sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (PARTITION BY c.id) AS total_txs, sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (PARTITION BY c.id) AS total_spent, ccf.custom_fields @@ -709,7 +711,8 @@ function getCustomerById (id) { ) cf GROUP BY cf.customer_id ) ccf ON c.id = ccf.customer_id LEFT OUTER JOIN ( - SELECT customer_id, content FROM customer_notes + SELECT customer_id, coalesce(json_agg(customer_notes.*), '[]'::json) AS notes FROM customer_notes + GROUP BY customer_notes.customer_id ) cn ON c.id = cn.customer_id WHERE c.id = $2 ) AS cl WHERE rn = 1` @@ -720,6 +723,7 @@ function getCustomerById (id) { }) .then(populateOverrideUsernames) .then(camelize) + .then(it => ({ ...it, notes: it.notes.map(camelize) })) } /** diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js index bc258284..d3563669 100644 --- a/lib/new-admin/graphql/resolvers/customer.resolver.js +++ b/lib/new-admin/graphql/resolvers/customer.resolver.js @@ -1,4 +1,4 @@ -const authentication = require('../modules/authentication') +const authentication = require('../modules/userManagement') const anonymous = require('../../../constants').anonymousCustomer const customers = require('../../../customers') const filters = require('../../filters') @@ -39,9 +39,16 @@ const resolvers = { // TODO: NOT IMPLEMENTING THIS FEATURE FOR THE CURRENT VERSION return customers.getCustomerById(customerId) }, - setCustomerNotes: (...[, { customerId, newContent }, context]) => { + createCustomerNote: (...[, { customerId, title, content }, context]) => { const token = authentication.getToken(context) - return customerNotes.updateCustomerNotes(customerId, token, newContent) + return customerNotes.createCustomerNote(customerId, token, title, content) + }, + editCustomerNote: (...[, { noteId, newContent }, context]) => { + const token = authentication.getToken(context) + return customerNotes.updateCustomerNote(noteId, token, newContent) + }, + deleteCustomerNote: (...[, { noteId }]) => { + return customerNotes.deleteCustomerNote(noteId) } } } diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js index adc6d2d6..bdbf3a94 100644 --- a/lib/new-admin/graphql/types/customer.type.js +++ b/lib/new-admin/graphql/types/customer.type.js @@ -41,7 +41,7 @@ const typeDef = gql` subscriberInfo: JSONObject customFields: [CustomerCustomField] customInfoRequests: [CustomRequestData] - notes: String + notes: [CustomerNote] } input CustomerInput { @@ -76,6 +76,16 @@ const typeDef = gql` usSsn: String } + type CustomerNote { + id: ID + customerId: ID + created: Date + lastEditedAt: Date + lastEditedBy: ID + title: String + content: String + } + type Query { customers(phone: String, name: String, address: String, id: String): [Customer] @auth customer(customerId: ID!): Customer @auth @@ -90,7 +100,9 @@ const typeDef = gql` editCustomer(customerId: ID!, customerEdit: CustomerEdit): Customer @auth deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer @auth replacePhoto(customerId: ID!, photoType: String, newPhoto: UploadGQL): Customer @auth - setCustomerNotes(customerId: ID!, newContent: String!): Customer @auth + createCustomerNote(customerId: ID!, title: String!, content: String!): Boolean @auth + editCustomerNote(noteId: ID!, newContent: String!): Boolean @auth + deleteCustomerNote(noteId: ID!): Boolean @auth } ` diff --git a/migrations/1627868356883-customer-custom-notes.js b/migrations/1627868356883-customer-custom-notes.js index 87a16e5e..4e8497ec 100644 --- a/migrations/1627868356883-customer-custom-notes.js +++ b/migrations/1627868356883-customer-custom-notes.js @@ -8,6 +8,7 @@ exports.up = function (next) { created TIMESTAMPTZ NOT NULL DEFAULT now(), last_edited_at TIMESTAMPTZ, last_edited_by UUID REFERENCES users(id), + title TEXT NOT NULL DEFAULT '' content TEXT NOT NULL DEFAULT '' )` ] diff --git a/new-lamassu-admin/src/pages/Customers/CustomerNotes.js b/new-lamassu-admin/src/pages/Customers/CustomerNotes.js new file mode 100644 index 00000000..b7f055ba --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/CustomerNotes.js @@ -0,0 +1,94 @@ +import { makeStyles } from '@material-ui/core' +import * as R from 'ramda' +import { React, useState } from 'react' + +import { H3 } from 'src/components/typography' + +import styles from './CustomerNotes.styles' +import NewNoteCard from './components/notes/NewNoteCard' +import NewNoteModal from './components/notes/NewNoteModal' +import NoteCard from './components/notes/NoteCard' +import NoteEdit from './components/notes/NoteEdit' + +const useStyles = makeStyles(styles) + +const CustomerNotes = ({ + customer, + createNote, + deleteNote, + editNote, + timezone +}) => { + const classes = useStyles() + const [openModal, setOpenModal] = useState(false) + const [editing, setEditing] = useState(null) + + const customerNotes = R.sort( + (a, b) => new Date(b?.created).getTime() - new Date(a?.created).getTime(), + customer.notes ?? [] + ) + + const handleModalClose = () => { + setOpenModal(false) + } + + const handleModalSubmit = it => { + createNote(it) + return handleModalClose() + } + + const cancelNoteEditing = () => { + setEditing(null) + } + + const submitNoteEditing = it => { + if (!R.equals(it.newContent, it.oldContent)) { + editNote({ + noteId: it.noteId, + newContent: it.newContent + }) + } + setEditing(null) + } + + return ( +
+
+

{'Notes'}

+
+ {R.isNil(editing) && ( +
+ + {R.map( + it => ( + + ), + customerNotes + )} +
+ )} + {!R.isNil(editing) && ( + + )} + {openModal && ( + + )} +
+ ) +} + +export default CustomerNotes diff --git a/new-lamassu-admin/src/pages/Customers/CustomerNotes.styles.js b/new-lamassu-admin/src/pages/Customers/CustomerNotes.styles.js new file mode 100644 index 00000000..4ba177b2 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/CustomerNotes.styles.js @@ -0,0 +1,17 @@ +const styles = { + header: { + display: 'flex', + flexDirection: 'row' + }, + title: { + marginTop: 7, + marginRight: 24 + }, + notesChipList: { + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap' + } +} + +export default styles diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js index e0be120c..626c5f38 100644 --- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js +++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js @@ -23,6 +23,7 @@ import { ReactComponent as Discount } from 'src/styling/icons/button/discount/zo import { fromNamespace, namespaces } from 'src/utils/config' import CustomerData from './CustomerData' +import CustomerNotes from './CustomerNotes' import styles from './CustomerProfile.styles' import { CustomerDetails, @@ -69,7 +70,14 @@ const GET_CUSTOMER = gql` label value } - notes + notes { + id + customerId + title + content + created + lastEditedAt + } transactions { txClass id @@ -195,6 +203,38 @@ const SET_CUSTOMER_CUSTOM_INFO_REQUEST = gql` } ` +const CREATE_NOTE = gql` + mutation createCustomerNote( + $customerId: ID! + $title: String! + $content: String! + ) { + createCustomerNote( + customerId: $customerId + title: $title + content: $content + ) + } +` + +const DELETE_NOTE = gql` + mutation deleteCustomerNote($noteId: ID!) { + deleteCustomerNote(noteId: $noteId) + } +` + +const EDIT_NOTE = gql` + mutation editCustomerNote($noteId: ID!, $newContent: String!) { + editCustomerNote(noteId: $noteId, newContent: $newContent) + } +` + +const GET_DATA = gql` + query getData { + config + } +` + const CustomerProfile = memo(() => { const history = useHistory() @@ -204,12 +244,15 @@ const CustomerProfile = memo(() => { const [clickedItem, setClickedItem] = useState('overview') const { id: customerId } = useParams() - const { data: customerResponse, refetch: getCustomer, loading } = useQuery( - GET_CUSTOMER, - { - variables: { customerId } - } - ) + const { + data: customerResponse, + refetch: getCustomer, + loading: customerLoading + } = useQuery(GET_CUSTOMER, { + variables: { customerId } + }) + + const { data: configResponse, loading: configLoading } = useQuery(GET_DATA) const [replaceCustomerPhoto] = useMutation(REPLACE_CUSTOMER_PHOTO, { onCompleted: () => getCustomer() @@ -238,6 +281,18 @@ const CustomerProfile = memo(() => { } ) + const [createNote] = useMutation(CREATE_NOTE, { + onCompleted: () => getCustomer() + }) + + const [deleteNote] = useMutation(DELETE_NOTE, { + onCompleted: () => getCustomer() + }) + + const [editNote] = useMutation(EDIT_NOTE, { + onCompleted: () => getCustomer() + }) + const updateCustomer = it => setCustomer({ variables: { @@ -271,6 +326,30 @@ const CustomerProfile = memo(() => { } }) + const createCustomerNote = it => + createNote({ + variables: { + customerId, + title: it.title, + content: it.content + } + }) + + const deleteCustomerNote = it => + deleteNote({ + variables: { + noteId: it.noteId + } + }) + + const editCustomerNote = it => + editNote({ + variables: { + noteId: it.noteId, + newContent: it.newContent + } + }) + const onClickSidebarItem = code => setClickedItem(code) const configData = R.path(['config'])(customerResponse) ?? [] @@ -287,6 +366,11 @@ const CustomerProfile = memo(() => { const isSuspended = customerData.isSuspended const isCustomerData = clickedItem === 'customerData' const isOverview = clickedItem === 'overview' + const isNotes = clickedItem === 'notes' + + const loading = customerLoading && configLoading + + const timezone = R.path(['config', 'locale_timezone'], configResponse) const classes = useStyles({ blocked }) @@ -430,6 +514,16 @@ const CustomerProfile = memo(() => { authorizeCustomRequest={authorizeCustomRequest}> )} + {isNotes && ( +
+ +
+ )} {wizard && ( { - const classes = useStyles() - - const [editing, setEditing] = useState(false) - const [setNotes] = useMutation(SAVE_CUSTOMER_NOTES, { - refetchQueries: () => ['customer'] - }) - - const initialValues = { - notes: R.path(['notes'])(customer) ?? '' - } - - const handleConfirm = values => { - setNotes({ - variables: { - customerId: customer.id, - newContent: values.notes - } - }) - setEditing(false) - } - - const getFormattedNotes = content => { - const fragments = R.split(/\n/)(content ?? '') - return R.map((it, idx) => { - if (idx === fragments.length) return <>{it} - return ( - <> - {it} -
- - ) - }, fragments) - } - - return ( - setEditing(true)} - confirm={true} - isEditing={editing} - formName="notes-form" - className={classes.root} - contentClassName={classes.content}> - {!editing && ( - {getFormattedNotes(R.path(['notes'])(customer))} - )} - {editing && ( - handleConfirm(values)}> -
- - -
- )} -
- ) -} - -export default CustomerNotes diff --git a/new-lamassu-admin/src/pages/Customers/components/CustomerNotes.styles.js b/new-lamassu-admin/src/pages/Customers/components/CustomerNotes.styles.js deleted file mode 100644 index 67fc82b5..00000000 --- a/new-lamassu-admin/src/pages/Customers/components/CustomerNotes.styles.js +++ /dev/null @@ -1,16 +0,0 @@ -const styles = { - root: { - height: 'auto' - }, - content: { - height: 'auto' - }, - form: { - display: 'flex', - flexBasis: '90%', - flexShrink: 1, - marginTop: 8 - } -} - -export default styles diff --git a/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.js b/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.js index 57c430ff..a6190f13 100644 --- a/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.js +++ b/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.js @@ -4,6 +4,8 @@ import React from 'react' import { ReactComponent as CustomerDataReversedIcon } from 'src/styling/icons/customer-nav/data/comet.svg' import { ReactComponent as CustomerDataIcon } from 'src/styling/icons/customer-nav/data/white.svg' +import { ReactComponent as NoteReversedIcon } from 'src/styling/icons/customer-nav/note/comet.svg' +import { ReactComponent as NoteIcon } from 'src/styling/icons/customer-nav/note/white.svg' import { ReactComponent as OverviewReversedIcon } from 'src/styling/icons/customer-nav/overview/comet.svg' import { ReactComponent as OverviewIcon } from 'src/styling/icons/customer-nav/overview/white.svg' @@ -25,6 +27,12 @@ const CustomerSidebar = ({ isSelected, onClick }) => { display: 'Customer Data', Icon: CustomerDataIcon, InverseIcon: CustomerDataReversedIcon + }, + { + code: 'notes', + display: 'Notes', + Icon: NoteIcon, + InverseIcon: NoteReversedIcon } ] diff --git a/new-lamassu-admin/src/pages/Customers/components/notes/NewNoteCard.js b/new-lamassu-admin/src/pages/Customers/components/notes/NewNoteCard.js new file mode 100644 index 00000000..72a1e2b9 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/components/notes/NewNoteCard.js @@ -0,0 +1,24 @@ +import { makeStyles, Paper } from '@material-ui/core' +import classNames from 'classnames' +import { React } from 'react' + +import { P } from 'src/components/typography' +import { ReactComponent as AddIcon } from 'src/styling/icons/button/add/zodiac.svg' + +import styles from './NoteCard.styles' + +const useStyles = makeStyles(styles) + +const NewNoteCard = ({ setOpenModal }) => { + const classes = useStyles() + return ( +
setOpenModal(true)}> + + +

Add new

+
+
+ ) +} + +export default NewNoteCard diff --git a/new-lamassu-admin/src/pages/Customers/components/notes/NewNoteModal.js b/new-lamassu-admin/src/pages/Customers/components/notes/NewNoteModal.js new file mode 100644 index 00000000..eb832607 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/components/notes/NewNoteModal.js @@ -0,0 +1,81 @@ +import { makeStyles } from '@material-ui/core/styles' +import { Form, Formik, Field } from 'formik' +import { React } from 'react' +import * as Yup from 'yup' + +import ErrorMessage from 'src/components/ErrorMessage' +import Modal from 'src/components/Modal' +import { Button } from 'src/components/buttons' +import { TextInput } from 'src/components/inputs/formik' + +import styles from './NewNoteModal.styles' + +const useStyles = makeStyles(styles) + +const initialValues = { + title: '', + content: '' +} + +const validationSchema = Yup.object().shape({ + title: Yup.string() + .required() + .trim() + .max(25), + content: Yup.string().required() +}) + +const NewNoteModal = ({ showModal, onClose, onSubmit, errorMsg }) => { + const classes = useStyles() + + return ( + <> + + { + onSubmit({ title, content }) + }}> +
+ + +
+ {errorMsg && {errorMsg}} + +
+ +
+
+ + ) +} + +export default NewNoteModal diff --git a/new-lamassu-admin/src/pages/Customers/components/notes/NewNoteModal.styles.js b/new-lamassu-admin/src/pages/Customers/components/notes/NewNoteModal.styles.js new file mode 100644 index 00000000..a42ecd41 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/components/notes/NewNoteModal.styles.js @@ -0,0 +1,25 @@ +import { spacer } from 'src/styling/variables' + +const styles = { + form: { + display: 'flex', + flexDirection: 'column', + height: '100%', + '& > *': { + marginTop: 20 + }, + '& > *:last-child': { + marginTop: 'auto' + } + }, + submit: { + margin: [['auto', 0, 0, 'auto']] + }, + footer: { + display: 'flex', + flexDirection: 'row', + margin: [['auto', 0, spacer * 3, 0]] + } +} + +export default styles diff --git a/new-lamassu-admin/src/pages/Customers/components/notes/NoteCard.js b/new-lamassu-admin/src/pages/Customers/components/notes/NoteCard.js new file mode 100644 index 00000000..68f779d6 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/components/notes/NoteCard.js @@ -0,0 +1,55 @@ +import { makeStyles, Paper } from '@material-ui/core' +import * as R from 'ramda' +import { React } from 'react' + +import { H3, P } from 'src/components/typography' +import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg' +import { formatDate } from 'src/utils/timezones' + +import styles from './NoteCard.styles' + +const useStyles = makeStyles(styles) + +const formatContent = content => { + const fragments = R.split(/\n/)(content) + return R.map((it, idx) => { + if (idx === fragments.length) return <>{it} + return ( + <> + {it} +
+ + ) + }, fragments) +} + +const NoteCard = ({ note, deleteNote, handleClick, timezone }) => { + const classes = useStyles() + + return ( +
+ handleClick(note)}> +
+
+

{note?.title}

+

{formatDate(note?.created, timezone, 'yyyy-MM-dd')}

+
+
+ { + e.stopPropagation() + deleteNote({ noteId: note.id }) + }} + /> +
+
+

+ {formatContent(note?.content)} +

+
+
+ ) +} + +export default NoteCard diff --git a/new-lamassu-admin/src/pages/Customers/components/notes/NoteCard.styles.js b/new-lamassu-admin/src/pages/Customers/components/notes/NoteCard.styles.js new file mode 100644 index 00000000..9712fa77 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/components/notes/NoteCard.styles.js @@ -0,0 +1,93 @@ +import { zircon } from 'src/styling/variables' + +const styles = { + noteCardWrapper: { + flexGrow: 0, + flexShrink: 0, + flexBasis: `25%`, + minWidth: 0, + maxWidth: 500, + '&:nth-child(4n+1)': { + '& > div': { + margin: [[0, 10, 0, 0]] + } + }, + '&:nth-child(4n)': { + '& > div': { + margin: [[0, 0, 0, 10]] + } + }, + margin: [[10, 0]] + }, + noteCardChip: { + height: 200, + margin: [[0, 10]], + padding: [[10, 10]], + cursor: 'pointer' + }, + newNoteCard: { + backgroundColor: zircon, + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center' + }, + noteCardHeader: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%' + }, + noteCardTitle: { + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + marginRight: 10 + }, + noteCardContent: { + display: 'box', + lineClamp: 7, + boxOrient: 'vertical', + margin: [[15, 0]], + overflow: 'hidden', + textOverflow: 'ellipsis', + wordWrap: 'break-word' + }, + editCardChip: { + height: 325, + padding: 15 + }, + editCardHeader: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 15 + }, + editCardActions: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + '& > *': { + marginRight: 10 + }, + '& > *:last-child': { + marginRight: 0 + } + }, + editNotesContent: { + '& > div': { + '&:after': { + borderBottom: 'none' + }, + '&:before': { + borderBottom: 'none' + }, + '&:hover:not(.Mui-disabled)::before': { + borderBottom: 'none' + } + } + } +} + +export default styles diff --git a/new-lamassu-admin/src/pages/Customers/components/notes/NoteEdit.js b/new-lamassu-admin/src/pages/Customers/components/notes/NoteEdit.js new file mode 100644 index 00000000..dcfc36a8 --- /dev/null +++ b/new-lamassu-admin/src/pages/Customers/components/notes/NoteEdit.js @@ -0,0 +1,103 @@ +import { makeStyles, Paper } from '@material-ui/core' +import { formatDurationWithOptions, intervalToDuration } from 'date-fns/fp' +import { Form, Formik, Field } from 'formik' +import { React, useRef } from 'react' +import * as Yup from 'yup' + +import { ActionButton } from 'src/components/buttons' +import { TextInput } from 'src/components/inputs/formik' +import { P } from 'src/components/typography' +import { ReactComponent as CancelIconInverse } from 'src/styling/icons/button/cancel/white.svg' +import { ReactComponent as CancelIcon } from 'src/styling/icons/button/cancel/zodiac.svg' +import { ReactComponent as SaveIconInverse } from 'src/styling/icons/circle buttons/save/white.svg' +import { ReactComponent as SaveIcon } from 'src/styling/icons/circle buttons/save/zodiac.svg' +import { toTimezone } from 'src/utils/timezones' + +import styles from './NoteCard.styles' + +const useStyles = makeStyles(styles) + +const NoteEdit = ({ note, cancel, edit, timezone }) => { + const formRef = useRef() + const classes = useStyles() + + const validationSchema = Yup.object().shape({ + content: Yup.string() + }) + + const initialValues = { + content: note.content + } + + return ( + +
+

+ {`Last edited `} + {formatDurationWithOptions( + { delimited: ', ' }, + intervalToDuration({ + start: toTimezone(new Date(note.lastEditedAt), timezone), + end: toTimezone(new Date(), timezone) + }) + )} + {` ago`} +

+
+ + {`Cancel`} + + + {`Save changes`} + + formRef.current.setFieldValue('content', '')}> + {`Clear content`} + +
+
+ + edit({ + noteId: note.id, + newContent: content, + oldContent: note.content + }) + } + innerRef={formRef}> +
+ + +
+
+ ) +} + +export default NoteEdit diff --git a/new-lamassu-admin/src/styling/icons/customer-nav/note/comet.svg b/new-lamassu-admin/src/styling/icons/customer-nav/note/comet.svg new file mode 100644 index 00000000..951e3108 --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/customer-nav/note/comet.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/customer-nav/note/white.svg b/new-lamassu-admin/src/styling/icons/customer-nav/note/white.svg new file mode 100644 index 00000000..def24ed8 --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/customer-nav/note/white.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/customer-nav/note/zodiac.svg b/new-lamassu-admin/src/styling/icons/customer-nav/note/zodiac.svg new file mode 100644 index 00000000..6388cc81 --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/customer-nav/note/zodiac.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file