From 0327fa9969db1b3736b2a8d5c0dd4c3c770a58f1 Mon Sep 17 00:00:00 2001 From: leandrofars Date: Tue, 30 Apr 2024 17:26:16 -0300 Subject: [PATCH] feat(frontend): users crud | close #252 --- frontend/src/layouts/dashboard/config.js | 20 + frontend/src/pages/customers.js | 290 ----------- frontend/src/pages/users.js | 449 ++++++++++++++++++ .../src/sections/customer/customers-table.js | 145 ++++-- 4 files changed, 575 insertions(+), 329 deletions(-) delete mode 100644 frontend/src/pages/customers.js create mode 100644 frontend/src/pages/users.js diff --git a/frontend/src/layouts/dashboard/config.js b/frontend/src/layouts/dashboard/config.js index 612a933..ea00338 100644 --- a/frontend/src/layouts/dashboard/config.js +++ b/frontend/src/layouts/dashboard/config.js @@ -2,6 +2,8 @@ import ChartBarIcon from '@heroicons/react/24/solid/ChartBarIcon'; import CogIcon from '@heroicons/react/24/solid/CogIcon'; import ChatBubbleLeftRightIcon from '@heroicons/react/24/solid/ChatBubbleLeftRightIcon' import MapIcon from '@heroicons/react/24/solid/MapIcon' +import UserGroupIcon from '@heroicons/react/24/solid/UserGroupIcon' +import KeyIcon from '@heroicons/react/24/solid/KeyIcon' import CpuChip from '@heroicons/react/24/solid/CpuChipIcon'; import { SvgIcon } from '@mui/material'; @@ -42,6 +44,24 @@ export const items = [ // // ) // }, + // { + // title: 'Credentials', + // path: '/credentials', + // icon: ( + // + // + // + // ) + // }, + { + title: 'Users', + path: '/users', + icon: ( + + + + ) + }, { title: 'Settings', path: '/settings', diff --git a/frontend/src/pages/customers.js b/frontend/src/pages/customers.js deleted file mode 100644 index d31a34a..0000000 --- a/frontend/src/pages/customers.js +++ /dev/null @@ -1,290 +0,0 @@ -import { useCallback, useMemo, useState } from 'react'; -import Head from 'next/head'; -import { subDays, subHours } from 'date-fns'; -import ArrowDownOnSquareIcon from '@heroicons/react/24/solid/ArrowDownOnSquareIcon'; -import ArrowUpOnSquareIcon from '@heroicons/react/24/solid/ArrowUpOnSquareIcon'; -import PlusIcon from '@heroicons/react/24/solid/PlusIcon'; -import { Box, Button, Container, Stack, SvgIcon, Typography } from '@mui/material'; -import { useSelection } from 'src/hooks/use-selection'; -import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout'; -import { CustomersTable } from 'src/sections/customer/customers-table'; -import { CustomersSearch } from 'src/sections/customer/customers-search'; -import { applyPagination } from 'src/utils/apply-pagination'; - -const now = new Date(); - -const data = [ - { - id: '5e887ac47eed253091be10cb', - address: { - city: 'Cleveland', - country: 'USA', - state: 'Ohio', - street: '2849 Fulton Street' - }, - avatar: '/assets/avatars/avatar-carson-darrin.png', - createdAt: subDays(subHours(now, 7), 1).getTime(), - email: 'carson.darrin@devias.io', - name: 'Carson Darrin', - phone: '304-428-3097' - }, - { - id: '5e887b209c28ac3dd97f6db5', - address: { - city: 'Atlanta', - country: 'USA', - state: 'Georgia', - street: '1865 Pleasant Hill Road' - }, - avatar: '/assets/avatars/avatar-fran-perez.png', - createdAt: subDays(subHours(now, 1), 2).getTime(), - email: 'fran.perez@devias.io', - name: 'Fran Perez', - phone: '712-351-5711' - }, - { - id: '5e887b7602bdbc4dbb234b27', - address: { - city: 'North Canton', - country: 'USA', - state: 'Ohio', - street: '4894 Lakeland Park Drive' - }, - avatar: '/assets/avatars/avatar-jie-yan-song.png', - createdAt: subDays(subHours(now, 4), 2).getTime(), - email: 'jie.yan.song@devias.io', - name: 'Jie Yan Song', - phone: '770-635-2682' - }, - { - id: '5e86809283e28b96d2d38537', - address: { - city: 'Madrid', - country: 'Spain', - name: 'Anika Visser', - street: '4158 Hedge Street' - }, - avatar: '/assets/avatars/avatar-anika-visser.png', - createdAt: subDays(subHours(now, 11), 2).getTime(), - email: 'anika.visser@devias.io', - name: 'Anika Visser', - phone: '908-691-3242' - }, - { - id: '5e86805e2bafd54f66cc95c3', - address: { - city: 'San Diego', - country: 'USA', - state: 'California', - street: '75247' - }, - avatar: '/assets/avatars/avatar-miron-vitold.png', - createdAt: subDays(subHours(now, 7), 3).getTime(), - email: 'miron.vitold@devias.io', - name: 'Miron Vitold', - phone: '972-333-4106' - }, - { - id: '5e887a1fbefd7938eea9c981', - address: { - city: 'Berkeley', - country: 'USA', - state: 'California', - street: '317 Angus Road' - }, - avatar: '/assets/avatars/avatar-penjani-inyene.png', - createdAt: subDays(subHours(now, 5), 4).getTime(), - email: 'penjani.inyene@devias.io', - name: 'Penjani Inyene', - phone: '858-602-3409' - }, - { - id: '5e887d0b3d090c1b8f162003', - address: { - city: 'Carson City', - country: 'USA', - state: 'Nevada', - street: '2188 Armbrester Drive' - }, - avatar: '/assets/avatars/avatar-omar-darboe.png', - createdAt: subDays(subHours(now, 15), 4).getTime(), - email: 'omar.darobe@devias.io', - name: 'Omar Darobe', - phone: '415-907-2647' - }, - { - id: '5e88792be2d4cfb4bf0971d9', - address: { - city: 'Los Angeles', - country: 'USA', - state: 'California', - street: '1798 Hickory Ridge Drive' - }, - avatar: '/assets/avatars/avatar-siegbert-gottfried.png', - createdAt: subDays(subHours(now, 2), 5).getTime(), - email: 'siegbert.gottfried@devias.io', - name: 'Siegbert Gottfried', - phone: '702-661-1654' - }, - { - id: '5e8877da9a65442b11551975', - address: { - city: 'Murray', - country: 'USA', - state: 'Utah', - street: '3934 Wildrose Lane' - }, - avatar: '/assets/avatars/avatar-iulia-albu.png', - createdAt: subDays(subHours(now, 8), 6).getTime(), - email: 'iulia.albu@devias.io', - name: 'Iulia Albu', - phone: '313-812-8947' - }, - { - id: '5e8680e60cba5019c5ca6fda', - address: { - city: 'Salt Lake City', - country: 'USA', - state: 'Utah', - street: '368 Lamberts Branch Road' - }, - avatar: '/assets/avatars/avatar-nasimiyu-danai.png', - createdAt: subDays(subHours(now, 1), 9).getTime(), - email: 'nasimiyu.danai@devias.io', - name: 'Nasimiyu Danai', - phone: '801-301-7894' - } -]; - -const useCustomers = (page, rowsPerPage) => { - return useMemo( - () => { - return applyPagination(data, page, rowsPerPage); - }, - [page, rowsPerPage] - ); -}; - -const useCustomerIds = (customers) => { - return useMemo( - () => { - return customers.map((customer) => customer.id); - }, - [customers] - ); -}; - -const Page = () => { - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(5); - const customers = useCustomers(page, rowsPerPage); - const customersIds = useCustomerIds(customers); - const customersSelection = useSelection(customersIds); - - const handlePageChange = useCallback( - (event, value) => { - setPage(value); - }, - [] - ); - - const handleRowsPerPageChange = useCallback( - (event) => { - setRowsPerPage(event.target.value); - }, - [] - ); - - return ( - <> - - - Customers | Devias Kit - - - - - - - - - Customers - - - - - - -
- -
-
- - -
-
-
- - ); -}; - -Page.getLayout = (page) => ( - - {page} - -); - -export default Page; diff --git a/frontend/src/pages/users.js b/frontend/src/pages/users.js new file mode 100644 index 0000000..3549a49 --- /dev/null +++ b/frontend/src/pages/users.js @@ -0,0 +1,449 @@ +import { useCallback, useMemo, useState, useEffect } from 'react'; +import Head from 'next/head'; +import { subDays, subHours } from 'date-fns'; +import ArrowDownOnSquareIcon from '@heroicons/react/24/solid/ArrowDownOnSquareIcon'; +import ArrowUpOnSquareIcon from '@heroicons/react/24/solid/ArrowUpOnSquareIcon'; +import PlusIcon from '@heroicons/react/24/solid/PlusIcon'; +import { Box, Button, CircularProgress, Container, Dialog, DialogContent, DialogTitle, Stack, SvgIcon, Typography, + DialogActions, + TextField, + Backdrop, +} from '@mui/material'; +import { useSelection } from 'src/hooks/use-selection'; +import { Layout as DashboardLayout } from 'src/layouts/dashboard/layout'; +import { CustomersTable } from 'src/sections/customer/customers-table'; +import { CustomersSearch } from 'src/sections/customer/customers-search'; +import { applyPagination } from 'src/utils/apply-pagination'; +import { useAuth } from 'src/hooks/use-auth'; +import { useRouter } from 'next/router'; +import { is } from 'date-fns/locale'; +import { set } from 'nprogress'; + +const Page = () => { + + const auth = useAuth(); + const router = useRouter(); + + const validateEmail = (email) => { + return email.match( + /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + ); + }; + + //const [page, setPage] = useState(0); + //const [rowsPerPage, setRowsPerPage] = useState(5); + const [loading, setLoading] = useState(true); + const [creatingNewUser, setCreatingNewUser] = useState(false); + const [users, setUsers] = useState([]); + const [selected, setSelected] = useState([]); + const [addDeviceDialogOpen, setAddDeviceDialogOpen] = useState(false); + const [newUserData, setNewUserData] = useState({}); + const [isPasswordEmpty, setIsPasswordEmpty] = useState(false); + const [isEmailEmpty, setIsEmailEmpty] = useState(false); + const [isEmailExistent, setIsEmailExistent] = useState(false); + + const deleteUser = (id) => { + console.log("request to delete user: ", id) + + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", auth.user.token); + + var requestOptions = { + method: 'DELETE', + headers: myHeaders, + redirect: 'follow' + } + + return fetch(process.env.NEXT_PUBLIC_REST_ENDPOINT + '/auth/delete/' + id, requestOptions) + .then(response => { + if (response.status === 401) { + router.push("/auth/login") + } else if (response.status === 403) { + return router.push("/403") + } + setUsers(users.filter(user => user.email !== id)) + }) + .catch(error => { + return console.error('Error:', error) + }); + } + + + const fetchUsers = async () => { + console.log("fetching users data...") + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", auth.user.token); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + } + + return fetch(process.env.NEXT_PUBLIC_REST_ENDPOINT + '/users', requestOptions) + .then(response => { + if (response.status === 401) { + router.push("/auth/login") + } else if (response.status === 403) { + return router.push("/403") + } + return response.json() + }) + .then(json => { + console.log("users: ", json) + setUsers(json) + // setPages(json.pages + 1) + // setPage(json.page +1) + // setDevices(json.devices) + setLoading(false) + }) + .catch(error => { + return console.error('Error:', error) + }); + } + + useEffect(() => { + // if (auth.user.token) { + // console.log("auth.user.token =", auth.user.token) + // }else{ + // auth.user.token = localStorage.getItem("token") + // } + //console.log("auth.user.token =", auth.user.token) + fetchUsers() + }, []); + + // const handlePageChange = useCallback( + // (event, value) => { + // setPage(value); + // }, + // [] + // ); + + // const handleRowsPerPageChange = useCallback( + // (event) => { + // setRowsPerPage(event.target.value); + // }, + // [] + // ); + + const createUser = async (data) => { + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", auth.user.token); + + var raw = JSON.stringify(data); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' + }; + + let result = await fetch(process.env.NEXT_PUBLIC_REST_ENDPOINT+"/auth/register", requestOptions) + + if (result.status == 200) { + console.log("user created: deu boa raça !!") + }else if (result.status == 403) { + console.log("num tenx permissão, seu boca de sandália") + setCreatingNewUser(false) + return router.push("/403") + }else if (result.status == 401){ + console.log("taix nem autenticado, sai fora oh") + setCreatingNewUser(false) + return router.push("/auth/login") + }else if (result.status == 409){ + console.log("usuário já existe, seu boca de bagre") + setIsEmailExistent(true) + setCreatingNewUser(false) + return + }else if (result.status == 400){ + console.log("faltou mandar dados jow") + setAddDeviceDialogOpen(false) + setNewUserData({}) + setIsPasswordEmpty(false) + setIsEmailEmpty(false) + setIsEmailExistent(false) + setCreatingNewUser(false) + return + }else { + console.log("agora quebrasse ux córno mô quiridu") + const content = await result.json() + setCreatingNewUser(false) + throw new Error(content); + } + setAddDeviceDialogOpen(false) + data["_id"] = data.email + data["createdAt"] = new Date().toLocaleDateString('es-pa') + data["level"] = 0 + + setUsers([...users, data]) + setNewUserData({}) + setIsPasswordEmpty(false) + setIsEmailEmpty(false) + setIsEmailExistent(false) + setCreatingNewUser(false) + } + + + + return ( + <> + + + Oktopus | Users + + + + + + + + + Users + + + {/* */} + {/* */} + + +
+ +
+
+ {/* */} + {users && !loading ? + { + setSelected(selected.filter((item) => item !== id)) + }} + //onPageChange={handlePageChange} + //onRowsPerPageChange={handleRowsPerPageChange} + //onSelectAll={customersSelection.handleSelectAll} + onSelectOne={(id) => { + setSelected([...selected, id]) + console.log("added user " + id + " to selected array") + }} + //page={page} + //rowsPerPage={rowsPerPage} + deleteUser={deleteUser} + selected={selected} + /> : + + } +
+
+
+ { + setAddDeviceDialogOpen(false) + setIsEmailEmpty(false) + setIsEmailExistent(false) + setIsPasswordEmpty(false) + setNewUserData({}) + }} + > + Create User + + + { + setNewUserData({...newUserData, email: event.target.value}) + } + } + variant="standard"> + + { + setNewUserData({...newUserData, password: event.target.value}) + } + } + variant="standard"> + + + + { + setNewUserData({...newUserData, name: event.target.value}) + } + } + > + + { + setNewUserData({...newUserData, phone: event.target.value}) + } + } + > + + + + + + + + { + theme.zIndex.drawer + 1 }} + open={creatingNewUser} + > + + + } + + + ); +}; + +Page.getLayout = (page) => ( + + {page} + +); + +export default Page; diff --git a/frontend/src/sections/customer/customers-table.js b/frontend/src/sections/customer/customers-table.js index 2d1fe88..f790415 100644 --- a/frontend/src/sections/customer/customers-table.js +++ b/frontend/src/sections/customer/customers-table.js @@ -1,21 +1,31 @@ import PropTypes from 'prop-types'; -import { format } from 'date-fns'; import { Avatar, Box, Card, Checkbox, + Icon, Stack, + Tab, Table, TableBody, TableCell, TableHead, - TablePagination, + //TablePagination, TableRow, - Typography + Typography, + SvgIcon, + Dialog, + DialogActions, + DialogTitle, + DialogContent, + DialogContentText, + Button } from '@mui/material'; import { Scrollbar } from 'src/components/scrollbar'; import { getInitials } from 'src/utils/get-initials'; +import TrashIcon from '@heroicons/react/24/outline/TrashIcon'; +import { useState } from 'react'; export const CustomersTable = (props) => { const { @@ -27,14 +37,18 @@ export const CustomersTable = (props) => { onRowsPerPageChange, onSelectAll, onSelectOne, + deleteUser, page = 0, rowsPerPage = 0, selected = [] } = props; - const selectedSome = (selected.length > 0) && (selected.length < items.length); - const selectedAll = (items.length > 0) && (selected.length === items.length); + // const selectedSome = (selected.length > 0) && (selected.length < items.length); + // const selectedAll = (items.length > 0) && (selected.length === items.length); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [userToDelete, setUserToDelete] = useState("") + return ( @@ -42,8 +56,8 @@ export const CustomersTable = (props) => { - - */} + {/* { @@ -53,55 +67,60 @@ export const CustomersTable = (props) => { onDeselectAll?.(); } }} - /> - - + /> */} + {/* */} + Name Email - + {/* Location - + */} Phone - Signed Up + Created At + + + Level + + + Actions {items.map((customer) => { - const isSelected = selected.includes(customer.id); - const createdAt = format(customer.createdAt, 'dd/MM/yyyy'); - + const isSelected = selected.includes(customer._id); return ( - - */} + {/* { if (event.target.checked) { - onSelectOne?.(customer.id); + console.log(customer._id+" is selected"); + onSelectOne(customer._id); } else { - onDeselectOne?.(customer.id); + onDeselectOne(customer._id); } }} - /> - - + /> */} + {/* */} + - + {getInitials(customer.name)} @@ -112,14 +131,33 @@ export const CustomersTable = (props) => { {customer.email} - - {customer.address.city}, {customer.address.state}, {customer.address.country} - + {/* + {customer.address} + */} {customer.phone} - {createdAt} + {customer.createdAt} + + + {customer.level == 1 ? "Admin" : "User"} + + + { customer.level == 0 ? : } ); @@ -128,15 +166,43 @@ export const CustomersTable = (props) => {
- + //onPageChange={onPageChange} + //onRowsPerPageChange={onRowsPerPageChange} + //page={page} + //rowsPerPage={rowsPerPage} + //rowsPerPageOptions={[5, 10, 25]} + /> */} + setShowDeleteDialog(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + {"Delete User"} + + + Are you sure you want to delete this user? + + + + + + +
); }; @@ -147,10 +213,11 @@ CustomersTable.propTypes = { onDeselectAll: PropTypes.func, onDeselectOne: PropTypes.func, onPageChange: PropTypes.func, - onRowsPerPageChange: PropTypes.func, + //onRowsPerPageChange: PropTypes.func, onSelectAll: PropTypes.func, onSelectOne: PropTypes.func, - page: PropTypes.number, - rowsPerPage: PropTypes.number, + deleteUser: PropTypes.func, + //page: PropTypes.number, + //rowsPerPage: PropTypes.number, selected: PropTypes.array };