Ticket in work,

Some counter and buttons in pending tickets added
skeleton loading ticket created,
fix the status tickets
create and fix input search
pull/14/head^2
RenatoDiGiacomo 2022-07-27 15:37:23 -03:00
parent 1230348bb8
commit 3c43d78cfc
28 changed files with 302 additions and 99 deletions

15
.vscode/launch.json vendored 100644
View File

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}"
}
]
}

View File

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.6667 18.6667H19.6133L19.24 18.3067C20.5467 16.7867 21.3333 14.8133 21.3333 12.6667C21.3333 7.88 17.4533 4 12.6667 4C7.88 4 4 7.88 4 12.6667C4 17.4533 7.88 21.3333 12.6667 21.3333C14.8133 21.3333 16.7867 20.5467 18.3067 19.24L18.6667 19.6133V20.6667L25.3333 27.32L27.32 25.3333L20.6667 18.6667ZM12.6667 18.6667C9.34667 18.6667 6.66667 15.9867 6.66667 12.6667C6.66667 9.34667 9.34667 6.66667 12.6667 6.66667C15.9867 6.66667 18.6667 9.34667 18.6667 12.6667C18.6667 15.9867 15.9867 18.6667 12.6667 18.6667Z" fill="current" />
</svg>

After

Width:  |  Height:  |  Size: 641 B

View File

@ -1,12 +0,0 @@
import React from "react";
import BtnBaseStyled from "./Btn.styled";
const BtnComponent = ({ text, bgcolor, fontcolor,...props }) => {
return (
<BtnBaseStyled bgcolor={bgcolor} fontcolor={fontcolor} {...props}>
{text}
</BtnBaseStyled>
);
};
export default BtnComponent;

View File

@ -0,0 +1,13 @@
import React from "react";
import BtnBaseStyled from "./Btn.styled";
const BtnComponent = ({ text, bgcolor, fontSize, fontcolor, ...props }) => {
return (
<BtnBaseStyled bgcolor={bgcolor} fontcolor={fontcolor} fontSize={fontSize} {...props}>
{text}
</BtnBaseStyled>
);
};
export default BtnComponent;

View File

@ -8,7 +8,7 @@ const BtnBaseStyled = styled.button`
padding: 6px 16px 3px;
border-radius: 5px;
margin: 12px 0;
font-size: 18px;
font-size: ${({ fontSize }) => fontSize ? fontSize: "18px"};
font-family: "Helvetica55";
vertical-align: baseline;
transition: all 0.2s linear;

View File

@ -1,5 +1,5 @@
import React from "react";
import BtnComponent from "../Base/BTN/Btn";
import BtnComponent from "../Base/Btn/Btn";
import {
ConfirmationModalStyled,

View File

@ -1,9 +0,0 @@
import React from "react";
import LoadingStyled from "./Loading.style"
const Loading = () => {
return <LoadingStyled/>;
};
export default Loading;

View File

@ -0,0 +1,8 @@
import React from "react";
import LoadingStyled from "./LoadingScreen.style"
const LoadingScreen = () => {
return <LoadingStyled/>;
};
export default LoadingScreen;

View File

@ -1,9 +1,8 @@
import React, { useState, useEffect } from "react";
import React from "react";
import { useParams, useHistory } from "react-router-dom";
import { toast } from "react-toastify";
import openSocket from "socket.io-client";
import clsx from "clsx";
import ContactDrawer from "../ContactDrawer";
import MessageInput from "../MessageInput";
@ -19,14 +18,14 @@ const Ticket = () => {
const { ticketId } = useParams();
const history = useHistory();
const [drawerOpen, setDrawerOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [contact, setContact] = useState({});
const [ticket, setTicket] = useState({});
const [drawerOpen, setDrawerOpen] = React.useState(false);
const [loading, setLoading] = React.useState(true);
const [contact, setContact] = React.useState({});
const [ticket, setTicket] = React.useState({});
const [statusChatEnd, setStatusChatEnd] = useState({});
const [statusChatEnd, setStatusChatEnd] = React.useState({});
useEffect(() => {
React.useEffect(() => {
setLoading(true);
const delayDebounceFn = setTimeout(() => {
const fetchTicket = async () => {
@ -49,7 +48,7 @@ const Ticket = () => {
return () => clearTimeout(delayDebounceFn);
}, [ticketId, history]);
useEffect(() => {
React.useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
socket.on("connect", () => socket.emit("joinChatBox", ticketId));

View File

@ -1,9 +1,9 @@
import React from 'react'
import TicketSearchInput from './TicketSearchInput/TicketSearchInput'
const TicketSearch = ({setNewTicketModalOpen, handleSearch}) => {
const TicketSearch = ({spinning,setNewTicketModalOpen, handleSearch}) => {
return (
<TicketSearchInput setNewTicketModalOpen={setNewTicketModalOpen} handleSearch={handleSearch}/>
<TicketSearchInput spinning={spinning} setNewTicketModalOpen={setNewTicketModalOpen} handleSearch={handleSearch}/>
)
}

View File

@ -1,11 +1,21 @@
import React from "react";
import TicketSearchBtn from "../TicketSearchBtn/TicketSearchBtn";
import { TicketSearchDivStyled, TicketSearchInputStyled } from "./TicketSearchInput.styled";
const TicketSearchInput = ({ setNewTicketModalOpen, handleSearch }) => {
import {
TicketSearchDivStyled,
TicketSearchInputBoxStyled,
TicketSearchInputStyled,
} from "./TicketSearchInput.styled";
import { ReactComponent as SearchIcon } from "../../../../assets/icons/SearchInput/search.svg";
import TicketSearchBtn from "../TicketSearchBtn/TicketSearchBtn";
const TicketSearchInput = ({ spinning, setNewTicketModalOpen, handleSearch }) => {
return (
<TicketSearchDivStyled>
<TicketSearchInputStyled onChange={handleSearch} />
<TicketSearchInputBoxStyled spinning={spinning}>
<SearchIcon />
<TicketSearchInputStyled onChange={handleSearch} />
</TicketSearchInputBoxStyled>
<TicketSearchBtn setNewTicketModalOpen={setNewTicketModalOpen} />
</TicketSearchDivStyled>
);

View File

@ -6,12 +6,53 @@ export const TicketSearchDivStyled = styled.div`
display: flex;
flex-direction: row;
`;
export const TicketSearchInputStyled = styled.input`
export const TicketSearchInputBoxStyled = styled.div`
position: relative;
display: flex;
align-items: center;
width: 100%;
margin-right: 12px;
background-color: ${color.complement.azulOscuro};
border: 2px solid ${color.pricinpal.blanco};
color: ${color.pricinpal.blanco};
border-radius: 4px;
padding: 4px;
padding: 6px;
& svg {
fill: ${color.gradient.border};
}
&:after {
position: absolute;
right: 6px;
content: "";
display: ${({ spinning }) => (!spinning ? "none" : "block")};
width: 16px;
height: 16px;
border-top: 2px solid ${color.gradient.border};
border-left: 2px solid ${color.gradient.border};
border-bottom: 2px solid ${color.gradient.border};
border-right: 2px solid ${color.pricinpal.naranja};
border-radius: 50%;
animation: spining 0.5s infinite linear;
}
@keyframes spining {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
`;
export const TicketSearchInputStyled = styled.input`
width: 100%;
border: none;
color: ${color.pricinpal.blanco};
background-color: ${color.complement.azulOscuro};
&:focus,
&:focus-visible {
border: none;
background-color: ${color.complement.azulOscuro};
outline: none;
}
`;

View File

@ -1,9 +0,0 @@
import React from 'react'
const TicketSkeleton = () => {
return (
<div>TicketSkeleton</div>
)
}
export default TicketSkeleton

View File

@ -1,4 +0,0 @@
import styled from "styled-components";
import {color} from "../../../style/varibles"
export const TicketSkeletonStyled = styled.div``;

View File

@ -1,22 +1,23 @@
import React, { useState, useEffect, useRef, useContext } from "react";
import { useHistory, useParams } from "react-router-dom";
import React from "react";
import {color} from "../../../../../style/varibles";
import { useHistory } from "react-router-dom";
import { parseISO, format, isSameDay } from "date-fns";
import { i18n } from "../../../translate/i18n";
import { i18n } from "../../../../../translate/i18n";
import api from "../../../services/api";
import { AuthContext } from "../../../context/Auth/AuthContext";
import toastError from "../../../errors/toastError";
import api from "../../../../../services/api";
import { AuthContext } from "../../../../../context/Auth/AuthContext";
import toastError from "../../../../../errors/toastError";
import Btn from "../../../../Base/Btn/Btn"
import {
TicketDateStyled,
TicketImgStyled,
TicketListItemStyled,
TicketTitleStyled,
} from "./TicketListItem.style";
import Loading from "../../LoadingScreen/Loading";
import BadgeComponent from "../../Base/Badge/BadgeComponent";
import DefaultUser from "../../../assets/images/User/clientDefault.png";
import LoadingScreen from "../../../../LoadingScreen/LoadingScreen";
import BadgeComponent from "../../../../Base/Badge/BadgeComponent";
import DefaultUser from "../../../../../assets/images/User/clientDefault.png";
const TicketListItem = ({ tickets }) => {
const history = useHistory();
@ -24,37 +25,39 @@ const TicketListItem = ({ tickets }) => {
const isMounted = React.useRef(true);
const { user } = React.useContext(AuthContext);
useEffect(() => {
React.useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
const handleAcepptTicket = async (id) => {
setLoading(true);
setLoading(true)
try {
await api.put(`/tickets/${id}`, {
status: tickets.status,
status: "open",
userId: user?.id,
});
console.log("Passou no try", tickets.status, user?.id, id);
} catch (err) {
setLoading(false);
toastError(err);
}
if (isMounted.current) {
setLoading(false);
}
history.push(`/tickets/${id}`);
};
const handleSelectTicket = (id) => {
history.push(`/tickets/${tickets.id}`);
history.push(`/tickets/${id}`);
};
console.log(tickets);
if (!tickets) return <Loading />;
if (loading) return <LoadingScreen />;
return (
<React.Fragment key={tickets.id} >
<TicketListItemStyled queuecolor={tickets.queue} onClick={handleSelectTicket}>
<TicketListItemStyled queuecolor={tickets.queue} onClick={()=>handleSelectTicket(tickets.id)}>
{tickets.contact.profilePicUrl ? (
<TicketImgStyled src={tickets.contact.profilePicUrl} alt={tickets.id} />
) : (
@ -82,6 +85,11 @@ const TicketListItem = ({ tickets }) => {
<>{format(parseISO(tickets.updatedAt), "dd/MM/yyyy")}</>
)}
</TicketDateStyled>
{tickets.status === "pending" ? (
<Btn text="Aceitar" bgcolor={color.complement.azulCielo} fontcolor={color.pricinpal.blanco} fontSize="12px"/>
) : (
""
)}
</TicketListItemStyled>
</React.Fragment>
);

View File

@ -1,5 +1,5 @@
import styled from "styled-components";
import { color } from "../../../style/varibles";
import { color } from "../../../../../style/varibles";
export const TicketListItemStyled = styled.li`
cursor: pointer;
@ -59,6 +59,7 @@ export const TicketDateStyled = styled.div`
font-family: "Helvetica55";
display: block;
color: white;
margin-right: 1rem;
`;
export const TicketImgStyled = styled.img`

View File

@ -1,10 +1,11 @@
import React from "react";
import { useHistory } from "react-router-dom";
import openSocket from "socket.io-client";
import TicketListStyled from "./TicketsList.style";
import useTickets from "../../../../hooks/useTickets";
import TicketListItem from "../../TicketListItem/TicketListItem";
import TicketsListSkeleton from "../../../TicketsListSkeleton";
import TicketListItem from "../TicketsList/TicketListItem/TicketListItem";
import TicketsListSkeleton from "../TicketsListSkeleton/TicketListSkeleton";
import { i18n } from "../../../../translate/i18n";
import { AuthContext } from "../../../../context/Auth/AuthContext";
@ -94,11 +95,10 @@ const reducer = (state, action) => {
}
};
const TicketsList = ({ status, searchParam, showAll, selectedQueueIds, updateCount }) => {
const history = useHistory();
const [pageNumber, setPageNumber] = React.useState(1);
const [ticketsList, dispatch] = React.useReducer(reducer, []);
const { user } = React.useContext(AuthContext);
const { tickets, hasMore, loading } = useTickets({
const { tickets, loading } = useTickets({
pageNumber,
searchParam,
status,
@ -184,15 +184,13 @@ const TicketsList = ({ status, searchParam, showAll, selectedQueueIds, updateCou
};
}, [status, showAll, user, selectedQueueIds]);
if (ticketsList <= 0) return <h1>carregando</h1>;
if (loading) return <TicketsListSkeleton />;
return (
<ul style={{ backgroundColor: "#55A5DC3F", height: "100vh", marginTop: "16px" }}>
<TicketListStyled>
{ticketsList.map((ticket) => (
<TicketListItem tickets={ticket} status={status} key={ticket.id} />
))}
</ul>
</TicketListStyled>
);
};

View File

@ -0,0 +1,10 @@
import styled from "styled-components";
import { color } from "../../../../style/varibles";
const TicketListStyled = styled.ul`
background-color: ${color.gradient.border};
height: 100vh;
margin-top: 16px;
`;
export default TicketListStyled;

View File

@ -0,0 +1,42 @@
import React from "react";
import {
TicketListSkeletonStyled,
TicketSkeletonItemStyled,
TicketSkeletonTitleStyled,
TicketSkeletonDateStyled,
TicketSkeletonImgStyled,
} from "./TicketListSkeleton.style";
const TicketsSkeleton = () => {
return (
<TicketListSkeletonStyled>
<TicketSkeletonItemStyled>
<TicketSkeletonImgStyled/>
<TicketSkeletonTitleStyled>
<div></div>
<div></div>
</TicketSkeletonTitleStyled>
<TicketSkeletonDateStyled/>
</TicketSkeletonItemStyled>
<TicketSkeletonItemStyled>
<TicketSkeletonImgStyled/>
<TicketSkeletonTitleStyled>
<div></div>
<div></div>
</TicketSkeletonTitleStyled>
<TicketSkeletonDateStyled/>
</TicketSkeletonItemStyled>
<TicketSkeletonItemStyled>
<TicketSkeletonImgStyled/>
<TicketSkeletonTitleStyled>
<div></div>
<div></div>
</TicketSkeletonTitleStyled>
<TicketSkeletonDateStyled/>
</TicketSkeletonItemStyled>
</TicketListSkeletonStyled>
);
};
export default TicketsSkeleton;

View File

@ -0,0 +1,80 @@
import styled from "styled-components";
import { color } from "../../../../style/varibles";
export const TicketListSkeletonStyled = styled.ul`
background-color: ${color.gradient.border};
height: 100vh;
margin-top: 16px;
`;
export const TicketSkeletonItemStyled = styled.li`
cursor: pointer;
position: relative;
background-color: ${color.complement.azulOscuro};
display: flex;
align-items: center;
padding: 0.5rem 6px;
border-bottom: 1.5px solid ${color.gradient.border};
height: fit-content;
transition: filter 0.2s linear;
&:nth-child(1) {
margin-top: 6px;
}
&:hover {
filter: brightness(1.2);
transition: filter 0.2s linear;
}
`;
export const TicketSkeletonTitleStyled = styled.div`
position: relative;
display: flex;
flex-direction: column;
flex-grow: 1;
row-gap: 6px;
opacity: 0.1;
& div {
color: ${color.pricinpal.blanco};
width: 190px;
font-size: 12px;
&:nth-child(1) {
width: 150px;
height: 18px;
background: linear-gradient(to right, ${color.gradient.border}, ${color.pricinpal.blanco});
animation: wave 0.8s infinite;
}
&:nth-child(2) {
width: 150px;
height: 18px;
background: linear-gradient(to right, ${color.gradient.border}, ${color.pricinpal.blanco});
animation: wave 0.8s infinite;
}
}
@keyframes wave {
to {
background-position: 150px;
}
}
`;
export const TicketSkeletonDateStyled = styled.div`
background: linear-gradient(to right, ${color.gradient.border}, ${color.pricinpal.blanco});
animation: wave 0.8s infinite;
opacity: 0.1;
display: block;
width: 40px;
height: 18px;
font-size: 12px;
margin-right: 1rem;
`;
export const TicketSkeletonImgStyled = styled.div`
background: linear-gradient(to right, ${color.gradient.border}, ${color.pricinpal.blanco});
animation: wave 0.8s infinite;
opacity: 0.1;
width: 40px;
height: 40px;
object-fit: contain;
border-radius: 50%;
margin: 0 6px;
`;

View File

@ -4,35 +4,39 @@ import TicketsManagerStyled from "./TicketsManager.style";
import TicketsTabs from "./TicketsTabs/TicketsTabs";
import TicketsList from "./TicketsList/TicketsList";
import NewTicketModal from "../../NewTicketModal";
import useTickets from "../../../hooks/useTickets";
import { AuthContext } from "../../../context/Auth/AuthContext";
import TicketSearch from "../TicketSearch/TicketSearch";
const TicketsManager = () => {
const [valueTab, setValueTab] = React.useState("open");
const [searchParam, setSearchParam] = React.useState("");
const [spinning, setSpinning] = React.useState(false);
const [newTicketModalOpen, setNewTicketModalOpen] = React.useState(false);
const [showAllTickets, setShowAllTickets] = React.useState(false);
const { user } = React.useContext(AuthContext);
const userQueueIds = user.queues.map((q) => q.id);
const [selectedQueueIds, setSelectedQueueIds] = React.useState(userQueueIds || []);
const { tickets } = useTickets({
searchParam
});
let searchTimeout;
const handleSearch = (e) => {
setSpinning(true);
const searchedTerm = e.target.value.toLowerCase();
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
setSearchParam(searchedTerm);
setSpinning(false);
}, 200);
if (searchedTerm === "") {
setSearchParam(searchedTerm);
return;
}
searchTimeout = setTimeout(() => {
setSearchParam(searchedTerm);
}, 200);
};
React.useEffect(() => {
@ -40,11 +44,15 @@ const TicketsManager = () => {
setShowAllTickets(true);
}
}, [user.profile]);
return (
<TicketsManagerStyled>
<TicketsTabs setValueTab={setValueTab} valueTab={valueTab} />
<TicketSearch handleSearch={handleSearch} setNewTicketModalOpen={setNewTicketModalOpen}/>
<TicketsTabs tickets={tickets} setValueTab={setValueTab} valueTab={valueTab} />
<TicketSearch
spinning={spinning}
handleSearch={handleSearch}
setNewTicketModalOpen={setNewTicketModalOpen}
/>
<TicketsList
showAll={showAllTickets}
status={valueTab}

View File

@ -3,7 +3,8 @@ import { TicketTabsStyled } from "./TicketsTabs.style";
import TicketsTab from "./TicketsTab/TicketsTab";
const TicketsTabs = ({ setValueTab, valueTab }) => {
const TicketsTabs = ({ tickets, setValueTab, valueTab }) => {
if (!setValueTab) return null;
return (
<TicketTabsStyled>

View File

@ -6,7 +6,7 @@ import api from "../../services/api";
import { AuthContext } from "../../context/Auth/AuthContext";
import { Can } from "../Can";
import BtnComponent from "../Base/BTN/Btn";
import BtnComponent from "../Base/Btn/Btn";
import FormComponent from "../Base/Form/FormComponent";
import InputComponent from "../Base/Form/Input/InputComponent";
import UserModalComponent from "./UserModalImg/UserModalComponent";

View File

@ -1,6 +1,6 @@
import React from "react";
import Loading from "../components/LoadingScreen/Loading";
import LoadingScreen from "../components/LoadingScreen/LoadingScreen";
import MainContainer from "../components/Base/MainContainer/MainContainer";
import { AuthContext } from "../context/Auth/AuthContext";
import { i18n } from "../translate/i18n";
@ -10,7 +10,7 @@ const LoggedInLayout = ({ children }) => {
const { loading, user } = React.useContext(AuthContext);
if (loading) {
return <Loading />;
return <LoadingScreen />;
}
return (

View File

@ -2,7 +2,7 @@ import React, { useContext } from "react";
import { Route as RouterRoute, Redirect } from "react-router-dom";
import { AuthContext } from "../context/Auth/AuthContext";
import Loading from "../components/LoadingScreen/Loading"
import LoadingScreen from "../components/LoadingScreen/LoadingScreen"
const Route = ({ component: Component, isPrivate = false, ...rest }) => {
const { isAuth, loading } = useContext(AuthContext);
@ -10,7 +10,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => {
if (!isAuth && isPrivate) {
return (
<>
{loading && <Loading />}
{loading && <LoadingScreen />}
<Redirect to={{ pathname: "/login", state: { from: rest.location } }} />
</>
);
@ -19,7 +19,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => {
if (isAuth && !isPrivate) {
return (
<>
{loading && <Loading />}
{loading && <LoadingScreen />}
<Redirect to={{ pathname: "/", state: { from: rest.location } }} />;
</>
);
@ -27,7 +27,7 @@ const Route = ({ component: Component, isPrivate = false, ...rest }) => {
return (
<>
{loading && <Loading />}
{loading && <LoadingScreen />}
<RouterRoute {...rest} component={Component} />
</>
);