feat: add contact option on ticket creation modal

This commit is contained in:
canove
2020-10-20 21:29:45 -03:00
parent 20adc6a11e
commit b51c7904e0
5 changed files with 151 additions and 80 deletions

View File

@@ -63,7 +63,7 @@ const ContactSchema = Yup.object().shape({
email: Yup.string().email("Invalid email"), email: Yup.string().email("Invalid email"),
}); });
const ContactModal = ({ open, onClose, contactId }) => { const ContactModal = ({ open, onClose, contactId, initialValues, onSave }) => {
const classes = useStyles(); const classes = useStyles();
const isMounted = useRef(true); const isMounted = useRef(true);
@@ -83,7 +83,14 @@ const ContactModal = ({ open, onClose, contactId }) => {
useEffect(() => { useEffect(() => {
const fetchContact = async () => { const fetchContact = async () => {
if (initialValues) {
setContact(prevState => {
return { ...prevState, ...initialValues };
});
}
if (!contactId) return; if (!contactId) return;
try { try {
const { data } = await api.get(`/contacts/${contactId}`); const { data } = await api.get(`/contacts/${contactId}`);
if (isMounted.current) { if (isMounted.current) {
@@ -104,10 +111,14 @@ const ContactModal = ({ open, onClose, contactId }) => {
}; };
fetchContact(); fetchContact();
}, [contactId, open]); }, [contactId, open, initialValues]);
const handleClose = () => { const handleClose = contactId => {
onClose(); if (contactId) {
onClose(contactId);
} else {
onClose();
}
setContact(initialState); setContact(initialState);
}; };
@@ -115,8 +126,13 @@ const ContactModal = ({ open, onClose, contactId }) => {
try { try {
if (contactId) { if (contactId) {
await api.put(`/contacts/${contactId}`, values); await api.put(`/contacts/${contactId}`, values);
handleClose();
} else { } else {
await api.post("/contacts", values); const { data } = await api.post("/contacts", values);
if (onSave) {
onSave(data);
}
handleClose();
} }
toast.success(i18n.t("contactModal.success")); toast.success(i18n.t("contactModal.success"));
} catch (err) { } catch (err) {
@@ -131,7 +147,6 @@ const ContactModal = ({ open, onClose, contactId }) => {
toast.error("Unknown error"); toast.error("Unknown error");
} }
} }
handleClose();
}; };
return ( return (

View File

@@ -19,6 +19,7 @@ import { makeStyles } from "@material-ui/core/styles";
import { i18n } from "../../translate/i18n"; import { i18n } from "../../translate/i18n";
import api from "../../services/api"; import api from "../../services/api";
import ButtonWithSpinner from "../ButtonWithSpinner"; import ButtonWithSpinner from "../ButtonWithSpinner";
import ContactModal from "../ContactModal";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles(theme => ({
root: { root: {
@@ -27,7 +28,7 @@ const useStyles = makeStyles(theme => ({
}, },
})); }));
const filterOptions = createFilterOptions({ const filter = createFilterOptions({
trim: true, trim: true,
}); });
@@ -40,9 +41,14 @@ const NewTicketModal = ({ modalOpen, onClose }) => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [searchParam, setSearchParam] = useState(""); const [searchParam, setSearchParam] = useState("");
const [selectedContact, setSelectedContact] = useState(null); const [selectedContact, setSelectedContact] = useState(null);
const [newContact, setNewContact] = useState({});
const [contactModalOpen, setContactModalOpen] = useState(false);
useEffect(() => { useEffect(() => {
if (!modalOpen || searchParam.length < 3) return; if (!modalOpen || searchParam.length < 3) {
setLoading(false);
return;
}
setLoading(true); setLoading(true);
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
const fetchContacts = async () => { const fetchContacts = async () => {
@@ -53,6 +59,7 @@ const NewTicketModal = ({ modalOpen, onClose }) => {
setOptions(data.contacts); setOptions(data.contacts);
setLoading(false); setLoading(false);
} catch (err) { } catch (err) {
setLoading(false);
const errorMsg = err.response?.data?.error; const errorMsg = err.response?.data?.error;
if (errorMsg) { if (errorMsg) {
if (i18n.exists(`backendErrors.${errorMsg}`)) { if (i18n.exists(`backendErrors.${errorMsg}`)) {
@@ -77,13 +84,12 @@ const NewTicketModal = ({ modalOpen, onClose }) => {
setSelectedContact(null); setSelectedContact(null);
}; };
const handleSaveTicket = async e => { const handleSaveTicket = async contactId => {
e.preventDefault(); if (!contactId) return;
if (!selectedContact) return;
setLoading(true); setLoading(true);
try { try {
const { data: ticket } = await api.post("/tickets", { const { data: ticket } = await api.post("/tickets", {
contactId: selectedContact.id, contactId: contactId,
userId: userId, userId: userId,
status: "open", status: "open",
}); });
@@ -104,73 +110,123 @@ const NewTicketModal = ({ modalOpen, onClose }) => {
handleClose(); handleClose();
}; };
const handleSelectOption = (e, newValue) => {
if (newValue?.number) {
setSelectedContact(newValue);
} else if (newValue?.name) {
setNewContact({ name: newValue.name });
setContactModalOpen(true);
}
};
const handleCloseContactModal = () => {
setContactModalOpen(false);
};
const handleAddNewContactTicket = contact => {
handleSaveTicket(contact.id);
};
const createAddContactOption = (options, params) => {
const filtered = filter(options, params);
if (params.inputValue !== "" && !loading && searchParam.length >= 3) {
filtered.push({
name: `${params.inputValue}`,
});
}
return filtered;
};
const renderOption = option => {
if (option.number) {
return `${option.name} - ${option.number}`;
} else {
return `${i18n.t("newTicketModal.add")} ${option.name}`;
}
};
const renderOptionLabel = option => {
if (option.number) {
return `${option.name} - ${option.number}`;
} else {
return `${option.name}`;
}
};
return ( return (
<div className={classes.root}> <div className={classes.root}>
<Dialog <ContactModal
open={modalOpen} open={contactModalOpen}
onClose={handleClose} initialValues={newContact}
maxWidth="lg" onClose={handleCloseContactModal}
scroll="paper" onSave={handleAddNewContactTicket}
> ></ContactModal>
<form onSubmit={handleSaveTicket}> <Dialog open={modalOpen} onClose={handleClose}>
<DialogTitle id="form-dialog-title"> <DialogTitle id="form-dialog-title">
{i18n.t("newTicketModal.title")} {i18n.t("newTicketModal.title")}
</DialogTitle> </DialogTitle>
<DialogContent dividers> <DialogContent dividers>
<Autocomplete <Autocomplete
id="contacts-finder" options={options}
style={{ width: 300 }} loading={loading}
getOptionLabel={option => `${option.name} - ${option.number}`} style={{ width: 300 }}
onChange={(e, newValue) => { clearOnBlur
setSelectedContact(newValue); freeSolo
}} clearOnEscape
options={options} getOptionLabel={renderOptionLabel}
filterOptions={filterOptions} renderOption={renderOption}
noOptionsText={i18n.t("newTicketModal.noOptions")} filterOptions={createAddContactOption}
loading={loading} onChange={(e, newValue) => handleSelectOption(e, newValue)}
renderInput={params => ( renderInput={params => (
<TextField <TextField
{...params} {...params}
label={i18n.t("newTicketModal.fieldLabel")} label={i18n.t("newTicketModal.fieldLabel")}
variant="outlined" variant="outlined"
required autoFocus
autoFocus onChange={e => setSearchParam(e.target.value)}
onChange={e => setSearchParam(e.target.value)} onKeyPress={e => {
id="my-input" if (loading || !selectedContact) return;
InputProps={{ else if (e.key === "Enter") {
...params.InputProps, handleSaveTicket(selectedContact.id);
endAdornment: ( }
<React.Fragment> }}
{loading ? ( InputProps={{
<CircularProgress color="inherit" size={20} /> ...params.InputProps,
) : null} endAdornment: (
{params.InputProps.endAdornment} <React.Fragment>
</React.Fragment> {loading ? (
), <CircularProgress color="inherit" size={20} />
}} ) : null}
/> {params.InputProps.endAdornment}
)} </React.Fragment>
/> ),
</DialogContent> }}
<DialogActions> />
<Button )}
onClick={handleClose} />
color="secondary" </DialogContent>
disabled={loading} <DialogActions>
variant="outlined" <Button
> onClick={handleClose}
{i18n.t("newTicketModal.buttons.cancel")} color="secondary"
</Button> disabled={loading}
<ButtonWithSpinner variant="outlined"
variant="contained" >
type="submit" {i18n.t("newTicketModal.buttons.cancel")}
color="primary" </Button>
loading={loading} <ButtonWithSpinner
> variant="contained"
{i18n.t("newTicketModal.buttons.ok")} type="button"
</ButtonWithSpinner> disabled={!selectedContact}
</DialogActions> onClick={() => handleSaveTicket(selectedContact.id)}
</form> color="primary"
loading={loading}
>
{i18n.t("newTicketModal.buttons.ok")}
</ButtonWithSpinner>
</DialogActions>
</Dialog> </Dialog>
</div> </div>
); );

View File

@@ -177,7 +177,7 @@ const messages = {
newTicketModal: { newTicketModal: {
title: "Create Ticket", title: "Create Ticket",
fieldLabel: "Type to search for a contact", fieldLabel: "Type to search for a contact",
noOptions: "No contacts found. Try another name.", add: "Add",
buttons: { buttons: {
ok: "Save", ok: "Save",
cancel: "Cancel", cancel: "Cancel",

View File

@@ -181,7 +181,7 @@ const messages = {
newTicketModal: { newTicketModal: {
title: "Crear Ticket", title: "Crear Ticket",
fieldLabel: "Escribe para buscar un contacto", fieldLabel: "Escribe para buscar un contacto",
noOptions: "No se encontraron contactos. Prueba con otro nombre.", add: "Añadir",
buttons: { buttons: {
ok: "Guardar", ok: "Guardar",
cancel: "Cancelar", cancel: "Cancelar",

View File

@@ -178,7 +178,7 @@ const messages = {
newTicketModal: { newTicketModal: {
title: "Criar Ticket", title: "Criar Ticket",
fieldLabel: "Digite para pesquisar o contato", fieldLabel: "Digite para pesquisar o contato",
noOptions: "Nenhum contato encontrado. Tente outro nome.", add: "Adicionar",
buttons: { buttons: {
ok: "Salvar", ok: "Salvar",
cancel: "Cancelar", cancel: "Cancelar",