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

View File

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

View File

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

View File

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

View File

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