mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-20 12:49:32 +00:00
feat: add contact option on ticket creation modal
This commit is contained in:
@@ -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 (
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user