40 | {/*
*/}
41 | {
43 | navigate(`/${slug}/${params.row.id}`);
44 | }}
45 | className="btn btn-square btn-ghost"
46 | >
47 |
48 |
49 | {
51 | toast('Jangan diedit!', {
52 | icon: '😠',
53 | });
54 | }}
55 | className="btn btn-square btn-ghost"
56 | >
57 |
58 |
59 | {
61 | toast('Jangan dihapus!', {
62 | icon: '😠',
63 | });
64 | }}
65 | className="btn btn-square btn-ghost"
66 | >
67 |
68 |
69 |
70 | );
71 | },
72 | };
73 |
74 | if (includeActionColumn === true) {
75 | return (
76 |
77 | 'auto'}
82 | initialState={{
83 | pagination: {
84 | paginationModel: {
85 | pageSize: 10,
86 | },
87 | },
88 | }}
89 | slots={{ toolbar: GridToolbar }}
90 | slotProps={{
91 | toolbar: {
92 | showQuickFilter: true,
93 | quickFilterProps: { debounceMs: 500 },
94 | },
95 | }}
96 | pageSizeOptions={[5]}
97 | checkboxSelection
98 | disableRowSelectionOnClick
99 | disableColumnFilter
100 | disableDensitySelector
101 | disableColumnSelector
102 | />
103 |
104 | );
105 | } else {
106 | return (
107 |
108 | 'auto'}
113 | initialState={{
114 | pagination: {
115 | paginationModel: {
116 | pageSize: 10,
117 | },
118 | },
119 | }}
120 | slots={{ toolbar: GridToolbar }}
121 | slotProps={{
122 | toolbar: {
123 | showQuickFilter: true,
124 | quickFilterProps: { debounceMs: 500 },
125 | },
126 | }}
127 | pageSizeOptions={[5]}
128 | checkboxSelection
129 | disableRowSelectionOnClick
130 | disableColumnFilter
131 | disableDensitySelector
132 | disableColumnSelector
133 | />
134 |
135 | );
136 | }
137 | };
138 |
139 | export default DataTable;
140 |
--------------------------------------------------------------------------------
/frontend/src/components/notes/data.ts:
--------------------------------------------------------------------------------
1 | export const allNotes = [
2 | {
3 | id: 1,
4 | title: 'The Importance of Product Descriptions',
5 | body: 'Crafting compelling product descriptions is crucial in e-commerce. It helps to highlight the features, benefits, and unique selling points of your products, ultimately enticing potential customers to make a purchase.',
6 | date: '2024-02-27',
7 | author: 'Frans AHW',
8 | },
9 | {
10 | id: 2,
11 | title: 'Optimizing Images for E-commerce',
12 | body: 'High-quality images are essential for e-commerce success. Ensure your product images are clear, well-lit, and showcase the product from various angles. Additionally, optimize image sizes for faster page loading speeds.',
13 | date: '2024-02-27',
14 | author: 'Frans AHW',
15 | },
16 | {
17 | id: 3,
18 | title: 'Effective Product Categorization Strategies',
19 | body: 'Organizing products into logical categories and subcategories simplifies navigation for customers, making it easier for them to find what they`re looking for. Consider factors like product type, usage, and target audience when creating your categorization structure.',
20 | date: '2024-02-27',
21 | author: 'Frans AHW',
22 | },
23 | {
24 | id: 4,
25 | title: 'The Power of Customer Reviews',
26 | body: 'Customer reviews play a significant role in influencing purchasing decisions. Encourage satisfied customers to leave positive reviews and promptly address any negative feedback to maintain a positive brand image.',
27 | date: '2024-02-27',
28 | author: 'Frans AHW',
29 | },
30 | {
31 | id: 5,
32 | title: 'Streamlining Checkout Processes',
33 | body: 'A streamlined checkout process is essential for reducing cart abandonment rates. Minimize the number of steps required to complete a purchase, offer guest checkout options, and provide multiple payment methods to accommodate diverse customer preferences.',
34 | date: '2024-02-27',
35 | author: 'Frans AHW',
36 | },
37 | {
38 | id: 6,
39 | title: 'Harnessing the Power of Social Media Marketing',
40 | body: 'Social media platforms offer valuable opportunities for e-commerce businesses to connect with their target audience, build brand awareness, and drive sales. Develop a cohesive social media strategy that includes engaging content, influencer partnerships, and targeted advertising campaigns.',
41 | date: '2024-02-27',
42 | author: 'Frans AHW',
43 | },
44 | {
45 | id: 7,
46 | title: 'Implementing Personalization in E-commerce',
47 | body: 'Personalization enhances the customer experience by delivering tailored product recommendations, customized offers, and relevant content based on individual preferences and browsing history. Leverage customer data and advanced analytics to implement effective personalization strategies.',
48 | date: '2024-02-27',
49 | author: 'Frans AHW',
50 | },
51 | {
52 | id: 8,
53 | title: 'Utilizing Email Marketing for E-commerce Success',
54 | body: 'Email marketing remains a powerful tool for driving customer engagement and sales in e-commerce. Develop targeted email campaigns that deliver valuable content, promotional offers, and personalized recommendations to subscribers.',
55 | date: '2024-02-27',
56 | author: 'Frans AHW',
57 | },
58 | {
59 | id: 9,
60 | title: 'Managing Inventory and Fulfillment Efficiently',
61 | body: 'Efficient inventory management and fulfillment processes are essential for maintaining product availability, minimizing stockouts, and delivering orders to customers in a timely manner. Implement inventory tracking systems and establish strategic partnerships with reliable suppliers and logistics providers.',
62 | date: '2024-02-27',
63 | author: 'Frans AHW',
64 | },
65 | {
66 | id: 10,
67 | title: 'Embracing Mobile Commerce Trends',
68 | body: 'With the increasing prevalence of smartphones and mobile devices, optimizing your e-commerce platform for mobile users is paramount. Implement responsive design principles, streamline mobile checkout processes, and leverage mobile-specific marketing channels to capitalize on the growing trend of mobile commerce.',
69 | date: '2024-02-27',
70 | author: 'Frans AHW',
71 | },
72 | ];
73 |
--------------------------------------------------------------------------------
/backend/src/data/notes.ts:
--------------------------------------------------------------------------------
1 | const notes = [
2 | {
3 | id: 1,
4 | title: 'The Importance of Product Descriptions',
5 | body: 'Crafting compelling product descriptions is crucial in e-commerce. It helps to highlight the features, benefits, and unique selling points of your products, ultimately enticing potential customers to make a purchase.',
6 | date: '2024-02-27',
7 | author: 'Frans AHW',
8 | topic: 'e-commerce',
9 | },
10 | {
11 | id: 2,
12 | title: 'Optimizing Images for E-commerce',
13 | body: 'High-quality images are essential for e-commerce success. Ensure your product images are clear, well-lit, and showcase the product from various angles. Additionally, optimize image sizes for faster page loading speeds.',
14 | date: '2024-02-27',
15 | author: 'Frans AHW',
16 | topic: 'e-commerce',
17 | },
18 | {
19 | id: 3,
20 | title: 'Effective Product Categorization Strategies',
21 | body: "Organizing products into logical categories and subcategories simplifies navigation for customers, making it easier for them to find what they're looking for. Consider factors like product type, usage, and target audience when creating your categorization structure.",
22 | date: '2024-02-27',
23 | author: 'Frans AHW',
24 | topic: 'e-commerce',
25 | },
26 | {
27 | id: 4,
28 | title: 'The Power of Customer Reviews',
29 | body: 'Customer reviews play a significant role in influencing purchasing decisions. Encourage satisfied customers to leave positive reviews and promptly address any negative feedback to maintain a positive brand image.',
30 | date: '2024-02-27',
31 | author: 'Frans AHW',
32 | topic: 'marketing',
33 | },
34 | {
35 | id: 5,
36 | title: 'Streamlining Checkout Processes',
37 | body: 'A streamlined checkout process is essential for reducing cart abandonment rates. Minimize the number of steps required to complete a purchase, offer guest checkout options, and provide multiple payment methods to accommodate diverse customer preferences.',
38 | date: '2024-02-27',
39 | author: 'Frans AHW',
40 | topic: 'e-commerce',
41 | },
42 | {
43 | id: 6,
44 | title: 'The Art of Storytelling in Marketing',
45 | body: 'Compelling storytelling can captivate audiences, evoke emotions, and establish strong connections with your brand. Incorporate storytelling techniques into your marketing campaigns to engage customers on a deeper level and differentiate your brand from competitors.',
46 | date: '2024-02-27',
47 | author: 'Frans AHW',
48 | topic: 'marketing',
49 | },
50 | {
51 | id: 7,
52 | title: 'Maximizing Social Media Engagement',
53 | body: 'Social media platforms offer unparalleled opportunities for businesses to engage with their target audience, build brand awareness, and drive website traffic. Develop a cohesive social media strategy that incorporates engaging content, interactive features, and timely responses to audience interactions.',
54 | date: '2024-02-27',
55 | author: 'Frans AHW',
56 | topic: 'social-media',
57 | },
58 | {
59 | id: 8,
60 | title: 'The Science of SEO: Ranking Strategies',
61 | body: "Search engine optimization (SEO) is essential for improving your website's visibility and attracting organic traffic from search engines. Stay updated on the latest SEO trends and algorithms, optimize your website structure and content, and build quality backlinks to enhance your search engine rankings.",
62 | date: '2024-02-27',
63 | author: 'Frans AHW',
64 | topic: 'SEO',
65 | },
66 | {
67 | id: 9,
68 | title: 'Effective Time Management Techniques',
69 | body: 'Time management is crucial for maximizing productivity and achieving your personal and professional goals. Prioritize tasks, set realistic deadlines, minimize distractions, and leverage productivity tools and techniques to make the most of your time.',
70 | date: '2024-02-27',
71 | author: 'Frans AHW',
72 | topic: 'productivity',
73 | },
74 | {
75 | id: 10,
76 | title: 'The Art of Negotiation: Essential Skills',
77 | body: 'Negotiation skills are valuable in both personal and professional contexts. Learn to communicate effectively, understand the needs and perspectives of others, seek mutually beneficial outcomes, and remain adaptable and open-minded during the negotiation process.',
78 | date: '2024-02-27',
79 | author: 'Frans AHW',
80 | topic: 'communication',
81 | },
82 | ];
83 |
84 | export default notes;
85 |
--------------------------------------------------------------------------------
/frontend/src/pages/Logs.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { GridColDef } from '@mui/x-data-grid';
3 | import DataTable from '../components/DataTable';
4 | import { useQuery } from '@tanstack/react-query';
5 | import toast from 'react-hot-toast';
6 | import { fetchLogs } from '../api/ApiCollection';
7 |
8 | const Logs = () => {
9 | const { isLoading, isError, isSuccess, data } = useQuery({
10 | queryKey: ['all-logs'],
11 | queryFn: fetchLogs,
12 | });
13 |
14 | const columns: GridColDef[] = [
15 | { field: 'id', headerName: 'ID', width: 90 },
16 | {
17 | field: 'firstName',
18 | headerName: 'Name',
19 | minWidth: 220,
20 | flex: 1,
21 | renderCell: (params) => {
22 | return (
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 | {params.row.firstName} {params.row.lastName}
34 |
35 |
36 | );
37 | },
38 | },
39 | {
40 | field: 'role',
41 | headerName: 'Role',
42 | minWidth: 100,
43 | type: 'string',
44 | flex: 1,
45 | },
46 | {
47 | field: 'email',
48 | type: 'string',
49 | headerName: 'Email',
50 | minWidth: 200,
51 | flex: 1,
52 | },
53 | {
54 | field: 'date',
55 | headerName: 'Date',
56 | minWidth: 120,
57 | type: 'string',
58 | flex: 1,
59 | },
60 | {
61 | field: 'time',
62 | headerName: 'Time',
63 | minWidth: 100,
64 | type: 'string',
65 | flex: 1,
66 | },
67 | {
68 | field: 'action',
69 | headerName: 'Action',
70 | minWidth: 350,
71 | type: 'string',
72 | flex: 1,
73 | renderCell: (params) => {
74 | return (
75 |
76 | {params.row.action}
77 |
78 | );
79 | },
80 | },
81 | ];
82 |
83 | React.useEffect(() => {
84 | if (isLoading) {
85 | toast.loading('Loading...', { id: 'promiseLogs' });
86 | }
87 | if (isError) {
88 | toast.error('Error while getting the data!', {
89 | id: 'promiseLogs',
90 | });
91 | }
92 | if (isSuccess) {
93 | toast.success('Got the data successfully!', {
94 | id: 'promiseLogs',
95 | });
96 | }
97 | }, [isError, isLoading, isSuccess]);
98 | return (
99 | // screen
100 |
101 | {/* container */}
102 |
103 | {/* block 1 */}
104 |
105 |
106 |
107 | Logs
108 |
109 | {data && data.length > 0 && (
110 |
111 | {data.length} Logs Found
112 |
113 | )}
114 |
115 |
116 |
117 | {/* table */}
118 | {isLoading ? (
119 |
125 | ) : isSuccess ? (
126 |
132 | ) : (
133 | <>
134 |
140 |
141 | Error while getting the data!
142 |
143 | >
144 | )}
145 |
146 |
147 | );
148 | };
149 |
150 | export default Logs;
151 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | [React Admin UI](https://react-admin-ui-v1.vercel.app/) is a beautiful and open-source **Dashboard User Interface Prototype** built with TypeScript and React based. Surprisingly, this is my first time building a User Interface prototype with a bit complex components. So, my goal is IT agencies or even individual developers could use this prototype to brings insight for their future projects.
19 |
20 | Have a look at the preview of [React Admin UI](https://react-admin-ui-v1.vercel.app/) for a comprehensive list of prototype's features, core values and use cases.
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | This repository contains the **core system of React Admin UI Prototype**, splitted into two different directories. Backend is for the JSON API (It is already configured for Vercel deployment), and Frontend is for the whole User Interface prototype.
29 |
30 | ## 💎 Features and Consist of
31 |
32 | - ⚡️ React 18 TypeScript with Vite
33 | - 🎯 Declarative Routing with React Router v6
34 | - 📋 Seamless Data Fetching with React Query v5
35 | - ✨ Optimized Icons with React Icons v5
36 | - 🎨 Tailwind CSS v3 as the Styling Foundation
37 | - 👓 Daisy UI v4 as the Base Design System
38 | - 🕶 Material UI v5 for optimized Data Grid
39 | - 📊 Beautiful Charts with Recharts v2
40 | - 🤯 And many more...
41 |
42 | ## 🚀 Installation and How to use
43 |
44 | See below for a quickstart installation and usage examples.
45 |
46 |
47 | Backend
48 |
49 | Install all dependencies listed in `package.json` inside backend directory.
50 |
51 | ```bash
52 | cd backend
53 | ```
54 |
55 | ```bash
56 | npm install
57 | ```
58 |
59 | By default, I already deployed the API to run in Vercel environment. The live API can be accessed from [https://react-admin-ui-v1-api.vercel.app/](https://react-admin-ui-v1-api.vercel.app/). However, in case you would like to configure the backend by yourself, you can run below.
60 |
61 | ```bash
62 | nodemon ./src/index.ts
63 | ```
64 |
65 | And the API can be accessed locally from [http://localhost:5000](http://localhost:5000).
66 |
67 |
68 |
69 |
70 | Frontend
71 |
72 | Install all dependencies listed in `package.json` inside frontend directory.
73 |
74 | ```bash
75 | cd frontend
76 | ```
77 |
78 | ```bash
79 | npm install
80 | ```
81 |
82 | ```bash
83 | npm run dev
84 | ```
85 |
86 | If you would like to change the default API endpoint, you can go to [ApiCollection.tsx](/frontend/src/api/ApiCollection.tsx).
87 |
88 | And then, the app can be accessed from [http://localhost:5173/](http://localhost:5173/).
89 |
90 |
91 |
92 | ## 📫 Have a question? Would like to chat? Ran into a problem?
93 |
94 | Obviously you can always **reach out to me directly** via a formal approach such as [Email](mailto:franswinata6@gmail.com) or [LinkedIn](https://www.linkedin.com/in/fransachmadhw/).
95 |
96 | ## 🤝 Found a bug? Suggesting a specific feature?
97 |
98 | Feel free to **file a new issue** with a respective title and description on the the [fransachmadhw/react_admin_ui_v1](https://github.com/fransachmadhw/react_admin_ui_v1/issues) repository. If you already found a solution to your problem, **we would love to review your pull request**!
99 |
100 | ## ✅ Requirements
101 |
102 | React Admin UI requires a **Node version higher or equal to 20.11.0 LTS**. Have a look at the `dependencies` and `devDependencies` section in the _package.json_ inside [backend](/backend/package.json) and [frontend](/frontend/package.json) to find the **current list of the requirements** of React Admin UI.
103 |
104 | ## 📘 License
105 |
106 | React Admin Dashboard UI Prototype is released under the terms of the [BSD-3-Clause](LICENSE).
107 |
--------------------------------------------------------------------------------
/backend/src/app.ts:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import morgan from 'morgan';
3 | import helmet from 'helmet';
4 | import cors from 'cors';
5 |
6 | import * as middlewares from './middlewares';
7 | import api from './api';
8 | // import MessageResponse from './interfaces/MessageResponse';
9 | import users from './data/users';
10 | import products from './data/products';
11 | import orders from './data/orders';
12 | import posts from './data/posts';
13 | import topDeals from './data/topDeals';
14 | import totalUsers from './data/totalUsers';
15 | import totalProducts from './data/totalProducts';
16 | import totalRatio from './data/totalRatio';
17 | import totalRevenue from './data/totalRevenue';
18 | import totalVisit from './data/totalVisit';
19 | import totalProfit from './data/totalProfit';
20 | import totalSource from './data/totalSource';
21 | import totalRevenueByProducts from './data/totalRevenueByProducts';
22 | import notes from './data/notes';
23 | import logs from './data/logs';
24 |
25 | require('dotenv').config();
26 |
27 | const app = express();
28 |
29 | app.use(morgan('dev'));
30 | app.use(helmet());
31 | app.use(cors());
32 | app.use(express.json());
33 |
34 | app.get('/', (req, res) => {
35 | const navigationLinks = {
36 | user: '/users',
37 | user1: '/users/:1',
38 | products: '/products',
39 | products1: '/products/1',
40 | orders: '/orders',
41 | posts: '/posts',
42 | notes: '/notes',
43 | logs: '/logs',
44 | };
45 |
46 | let htmlResponse =
47 | '
🦄🌈✨ React Dashboard Admin V1 API ✨🌈🦄 ';
48 | htmlResponse += '
';
49 | for (const [route, url] of Object.entries(navigationLinks)) {
50 | htmlResponse += `${route} `;
51 | }
52 | htmlResponse += ' ';
53 |
54 | // Send HTML response
55 | res.send(htmlResponse);
56 | });
57 |
58 | // GET USERS
59 | app.get('/users', (req, res) => {
60 | res.json(users);
61 | });
62 |
63 | // GET A USER
64 | app.get('/users/:id', (req, res) => {
65 | const user = users.find(
66 | // eslint-disable-next-line @typescript-eslint/comma-dangle
67 | (usera) => usera.id === parseInt(req.params.id)
68 | );
69 |
70 | res.json(user);
71 | });
72 |
73 | // GET PRODUCTS
74 | app.get('/products', (req, res) => {
75 | res.json(products);
76 | });
77 |
78 | // GET A PRODUCT
79 | app.get('/products/:id', (req, res) => {
80 | const product = products.find(
81 | // eslint-disable-next-line @typescript-eslint/comma-dangle
82 | (producta) => producta.id === parseInt(req.params.id)
83 | );
84 |
85 | res.json(product);
86 | });
87 |
88 | // GET ORDERS
89 | app.get('/orders', (req, res) => {
90 | res.json(orders);
91 | });
92 |
93 | // GET POSTS
94 | app.get('/posts', (req, res) => {
95 | res.json(posts);
96 | });
97 |
98 | // GET TOP DEAL USERS
99 | app.get('/topdeals', (req, res) => {
100 | res.json(topDeals);
101 | });
102 |
103 | // GET TOTAL USERS
104 | app.get('/totalusers', (req, res) => {
105 | res.json(totalUsers);
106 | });
107 |
108 | // GET TOTAL PRODUCTS
109 | app.get('/totalproducts', (req, res) => {
110 | res.json(totalProducts);
111 | });
112 |
113 | // GET TOTAL RATIO
114 | app.get('/totalratio', (req, res) => {
115 | res.json(totalRatio);
116 | });
117 |
118 | // GET TOTAL REVENUE
119 | app.get('/totalrevenue', (req, res) => {
120 | res.json(totalRevenue);
121 | });
122 |
123 | // GET TOTAL SOURCE
124 | app.get('/totalsource', (req, res) => {
125 | res.json(totalSource);
126 | });
127 |
128 | // GET TOTAL VISIT
129 | app.get('/totalvisit', (req, res) => {
130 | res.json(totalVisit);
131 | });
132 |
133 | // GET TOTAL PROFIT
134 | app.get('/totalprofit', (req, res) => {
135 | res.json(totalProfit);
136 | });
137 |
138 | // GET TOTAL REVENUE BY PRODUCTS
139 | app.get('/totalrevenue-by-product', (req, res) => {
140 | res.json(totalRevenueByProducts);
141 | });
142 |
143 | // GET NOTES
144 | app.get('/notes', (req, res) => {
145 | const { q } = req.query;
146 | const keys = ['title', 'body'];
147 |
148 | interface Note {
149 | id: number;
150 | title: string;
151 | body: string;
152 | date: string;
153 | author: string;
154 | }
155 |
156 | const search = (data: Note[]) => {
157 | return data.filter((item) =>
158 | keys.some((key) =>
159 | (item as any)[key].toLowerCase().includes(q as string)
160 | )
161 | );
162 | };
163 |
164 | q
165 | ? res.json(search(notes).slice(0, 10))
166 | : res.json(notes.slice(0, 10));
167 | });
168 |
169 | // GET LOGS
170 | app.get('/logs', (req, res) => {
171 | res.json(logs);
172 | });
173 |
174 | app.use('/api/v1', api);
175 |
176 | app.use(middlewares.notFound);
177 | app.use(middlewares.errorHandler);
178 |
179 | export default app;
180 |
--------------------------------------------------------------------------------
/backend/src/data/users.ts:
--------------------------------------------------------------------------------
1 | let users = [
2 | {
3 | id: 1,
4 | img: 'https://images.pexels.com/photos/8405873/pexels-photo-8405873.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load',
5 | lastName: 'Hubbard',
6 | firstName: 'Eula',
7 | email: 'hubbard@gmail.com',
8 | phone: '423 452 729',
9 | createdAt: '05.07.2023',
10 | verified: true,
11 | },
12 | {
13 | id: 2,
14 | img: 'https://images.pexels.com/photos/1181519/pexels-photo-1181519.jpeg?auto=compress&cs=tinysrgb&w=1600',
15 | lastName: 'Manning',
16 | firstName: 'Stella',
17 | email: 'manning@gmail.com',
18 | phone: '422 426 715',
19 | createdAt: '03.07.2023',
20 | verified: true,
21 | },
22 | {
23 | id: 3,
24 | img: 'https://images.pexels.com/photos/1587009/pexels-photo-1587009.jpeg?auto=compress&cs=tinysrgb&w=1600',
25 | lastName: 'Greer',
26 | firstName: 'Mary',
27 | email: 'greer@hottmail.com',
28 | phone: '563 632 325',
29 | createdAt: '02.07.2023',
30 | verified: true,
31 | },
32 | {
33 | id: 4,
34 | img: 'https://images.pexels.com/photos/871495/pexels-photo-871495.jpeg?auto=compress&cs=tinysrgb&w=1600',
35 | lastName: 'Williamson',
36 | firstName: 'Mildred',
37 | email: 'williamson@gmail.com',
38 | phone: '534 522 125',
39 | createdAt: '12.06.2023',
40 | verified: true,
41 | },
42 | {
43 | id: 5,
44 | img: 'https://images.pexels.com/photos/1758144/pexels-photo-1758144.jpeg?auto=compress&cs=tinysrgb&w=1600',
45 | lastName: 'Gross',
46 | firstName: 'Jose',
47 | email: 'gobtagbes@yahoo.com',
48 | phone: '462 252 624',
49 | createdAt: '10.06.2023',
50 | },
51 | {
52 | id: 6,
53 | img: 'https://images.pexels.com/photos/769745/pexels-photo-769745.jpeg?auto=compress&cs=tinysrgb&w=1600',
54 | lastName: 'Sharp',
55 | firstName: 'Jeremy',
56 | email: 'vulca.eder@mail.com',
57 | phone: '735 523 563',
58 | createdAt: '11.05.2023',
59 | verified: true,
60 | },
61 | {
62 | id: 7,
63 | img: 'https://images.pexels.com/photos/1043474/pexels-photo-1043474.jpeg?auto=compress&cs=tinysrgb&w=1600',
64 | lastName: 'Lowe',
65 | firstName: 'Christina',
66 | email: 'reso.bilic@gmail.com',
67 | phone: '235 734 574',
68 | createdAt: '04.05.2023',
69 | },
70 | {
71 | id: 8,
72 | img: 'https://images.pexels.com/photos/428364/pexels-photo-428364.jpeg?auto=compress&cs=tinysrgb&w=1600',
73 | lastName: 'Dean',
74 | firstName: 'Garrett',
75 | email: 'codaic@mail.com',
76 | phone: '377 346 834',
77 | createdAt: '08.04.2023',
78 | verified: true,
79 | },
80 | {
81 | id: 9,
82 | img: 'https://images.pexels.com/photos/1181686/pexels-photo-1181686.jpeg?auto=compress&cs=tinysrgb&w=1600',
83 | lastName: 'Parsons',
84 | firstName: 'Leah',
85 | email: 'parsons@gmail.com',
86 | phone: '745 677 345',
87 | createdAt: '04.04.2023',
88 | },
89 | {
90 | id: 10,
91 | img: 'https://images.pexels.com/photos/775358/pexels-photo-775358.jpeg?auto=compress&cs=tinysrgb&w=1600',
92 | lastName: 'Reid',
93 | firstName: 'Elnora',
94 | email: 'elnora@gmail.com',
95 | phone: '763 345 346',
96 | createdAt: '01.04.2023',
97 | verified: true,
98 | },
99 | {
100 | id: 11,
101 | img: 'https://images.pexels.com/photos/762020/pexels-photo-762020.jpeg?auto=compress&cs=tinysrgb&w=1600',
102 | lastName: 'Dunn',
103 | firstName: 'Gertrude',
104 | email: 'gertrude@gmail.com',
105 | phone: '124 456 789',
106 | createdAt: '05.04.2023',
107 | verified: true,
108 | },
109 | {
110 | id: 12,
111 | img: 'https://images.pexels.com/photos/774095/pexels-photo-774095.jpeg?auto=compress&cs=tinysrgb&w=1600',
112 | lastName: 'Williams',
113 | firstName: 'Mark',
114 | email: 'williams@hotmail.com',
115 | phone: '626 235 345',
116 | createdAt: '01.03.2023',
117 | },
118 | {
119 | id: 13,
120 | img: 'https://images.pexels.com/photos/761977/pexels-photo-761977.jpeg?auto=compress&cs=tinysrgb&w=1600',
121 | lastName: 'Cruz',
122 | firstName: 'Charlotte',
123 | email: 'charlotte@gmail.com',
124 | phone: '673 547 343',
125 | createdAt: '03.02.2023',
126 | },
127 | {
128 | id: 14,
129 | img: 'https://images.pexels.com/photos/927022/pexels-photo-927022.jpeg?auto=compress&cs=tinysrgb&w=1600',
130 | lastName: 'Harper',
131 | firstName: 'Sara',
132 | email: 'harper@hotmail.com',
133 | phone: '734 234 234',
134 | createdAt: '01.02.2023',
135 | },
136 | {
137 | id: 15,
138 | img: 'https://images.pexels.com/photos/8405873/pexels-photo-8405873.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load',
139 | lastName: 'Griffin',
140 | firstName: 'Eric',
141 | email: 'griffin@gmail.com',
142 | phone: '763 234 235',
143 | createdAt: '01.01.2023',
144 | },
145 | ];
146 |
147 | export default users;
148 |
--------------------------------------------------------------------------------
/frontend/src/pages/Products.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { GridColDef } from '@mui/x-data-grid';
3 | import DataTable from '../components/DataTable';
4 | import { fetchProducts } from '../api/ApiCollection';
5 | import { useQuery } from '@tanstack/react-query';
6 | import toast from 'react-hot-toast';
7 | import AddData from '../components/AddData';
8 |
9 | const Products = () => {
10 | const [isOpen, setIsOpen] = React.useState(false);
11 | const { isLoading, isError, isSuccess, data } = useQuery({
12 | queryKey: ['allproducts'],
13 | queryFn: fetchProducts,
14 | });
15 |
16 | const columns: GridColDef[] = [
17 | { field: 'id', headerName: 'ID', width: 90 },
18 | {
19 | field: 'img',
20 | headerName: 'Product',
21 | minWidth: 300,
22 | flex: 1,
23 | renderCell: (params) => {
24 | return (
25 |
26 |
27 |
32 |
33 |
34 | {params.row.title}
35 |
36 |
37 | );
38 | },
39 | },
40 | // {
41 | // field: 'title',
42 | // type: 'string',
43 | // headerName: 'Title',
44 | // width: 250,
45 | // },
46 | {
47 | field: 'color',
48 | type: 'string',
49 | headerName: 'Color',
50 | minWidth: 100,
51 | flex: 1,
52 | },
53 | {
54 | field: 'price',
55 | type: 'string',
56 | headerName: 'Price',
57 | minWidth: 100,
58 | flex: 1,
59 | },
60 | {
61 | field: 'producer',
62 | headerName: 'Producer',
63 | type: 'string',
64 | minWidth: 100,
65 | flex: 1,
66 | },
67 | {
68 | field: 'createdAt',
69 | headerName: 'Created At',
70 | minWidth: 100,
71 | type: 'string',
72 | flex: 1,
73 | },
74 | {
75 | field: 'inStock',
76 | headerName: 'In Stock',
77 | minWidth: 80,
78 | type: 'boolean',
79 | flex: 1,
80 | },
81 | ];
82 |
83 | React.useEffect(() => {
84 | if (isLoading) {
85 | toast.loading('Loading...', { id: 'promiseProducts' });
86 | }
87 | if (isError) {
88 | toast.error('Error while getting the data!', {
89 | id: 'promiseProducts',
90 | });
91 | }
92 | if (isSuccess) {
93 | toast.success('Got the data successfully!', {
94 | id: 'promiseProducts',
95 | });
96 | }
97 | }, [isError, isLoading, isSuccess]);
98 |
99 | return (
100 |
101 |
102 |
103 |
104 |
105 | Products
106 |
107 | {data && data.length > 0 && (
108 |
109 | {data.length} Products Found
110 |
111 | )}
112 |
113 |
setIsOpen(true)}
115 | className={`btn ${
116 | isLoading ? 'btn-disabled' : 'btn-primary'
117 | }`}
118 | >
119 | Add New Product +
120 |
121 |
122 |
123 | {isLoading ? (
124 |
130 | ) : isSuccess ? (
131 |
137 | ) : (
138 | <>
139 |
145 |
146 | Error while getting the data!
147 |
148 | >
149 | )}
150 |
151 | {isOpen && (
152 |
157 | )}
158 |
159 |
160 | );
161 | };
162 |
163 | export default Products;
164 |
--------------------------------------------------------------------------------
/frontend/src/pages/Users.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { GridColDef } from '@mui/x-data-grid';
3 | import DataTable from '../components/DataTable';
4 | import { fetchUsers } from '../api/ApiCollection';
5 | import { useQuery } from '@tanstack/react-query';
6 | import toast from 'react-hot-toast';
7 | import AddData from '../components/AddData';
8 |
9 | const Users = () => {
10 | const [isOpen, setIsOpen] = React.useState(false);
11 | const { isLoading, isError, isSuccess, data } = useQuery({
12 | queryKey: ['allusers'],
13 | queryFn: fetchUsers,
14 | });
15 |
16 | const columns: GridColDef[] = [
17 | { field: 'id', headerName: 'ID', width: 90 },
18 | {
19 | field: 'firstName',
20 | headerName: 'Name',
21 | minWidth: 220,
22 | flex: 1,
23 | renderCell: (params) => {
24 | return (
25 |
26 |
27 |
28 |
32 |
33 |
34 |
35 | {params.row.firstName} {params.row.lastName}
36 |
37 |
38 | );
39 | },
40 | },
41 | {
42 | field: 'email',
43 | type: 'string',
44 | headerName: 'Email',
45 | minWidth: 200,
46 | flex: 1,
47 | },
48 | {
49 | field: 'phone',
50 | type: 'string',
51 | headerName: 'Phone',
52 | minWidth: 120,
53 | flex: 1,
54 | },
55 | {
56 | field: 'createdAt',
57 | headerName: 'Created At',
58 | minWidth: 100,
59 | type: 'string',
60 | flex: 1,
61 | },
62 | // {
63 | // field: 'fullName',
64 | // headerName: 'Full name',
65 | // description:
66 | // 'This column has a value getter and is not sortable.',
67 | // sortable: false,
68 | // width: 160,
69 | // valueGetter: (params: GridValueGetterParams) =>
70 | // `${params.row.firstName || ''} ${params.row.lastName || ''}`,
71 | // },
72 | {
73 | field: 'verified',
74 | headerName: 'Verified',
75 | width: 80,
76 | type: 'boolean',
77 | flex: 1,
78 | },
79 | ];
80 |
81 | React.useEffect(() => {
82 | if (isLoading) {
83 | toast.loading('Loading...', { id: 'promiseUsers' });
84 | }
85 | if (isError) {
86 | toast.error('Error while getting the data!', {
87 | id: 'promiseUsers',
88 | });
89 | }
90 | if (isSuccess) {
91 | toast.success('Got the data successfully!', {
92 | id: 'promiseUsers',
93 | });
94 | }
95 | }, [isError, isLoading, isSuccess]);
96 |
97 | return (
98 |
99 |
100 |
101 |
102 |
103 | Users
104 |
105 | {data && data.length > 0 && (
106 |
107 | {data.length} Users Found
108 |
109 | )}
110 |
111 |
setIsOpen(true)}
113 | className={`btn ${
114 | isLoading ? 'btn-disabled' : 'btn-primary'
115 | }`}
116 | >
117 | Add New User +
118 |
119 |
120 | {isLoading ? (
121 |
127 | ) : isSuccess ? (
128 |
134 | ) : (
135 | <>
136 |
142 |
143 | Error while getting the data!
144 |
145 | >
146 | )}
147 |
148 | {isOpen && (
149 |
154 | )}
155 |
156 |
157 | );
158 | };
159 |
160 | export default Users;
161 |
--------------------------------------------------------------------------------
/frontend/src/components/charts/data.ts:
--------------------------------------------------------------------------------
1 | import {
2 | MdGroup,
3 | MdInventory2,
4 | MdAssessment,
5 | MdSwapHorizontalCircle,
6 | } from 'react-icons/md';
7 |
8 | export const totalUsers = {
9 | color: '#8884d8',
10 | IconBox: MdGroup,
11 | title: 'Total Users',
12 | number: '11.238',
13 | dataKey: 'users',
14 | percentage: 45,
15 | chartData: [
16 | { name: 'Sun', users: 400 },
17 | { name: 'Mon', users: 600 },
18 | { name: 'Tue', users: 500 },
19 | { name: 'Wed', users: 700 },
20 | { name: 'Thu', users: 400 },
21 | { name: 'Fri', users: 500 },
22 | { name: 'Sat', users: 450 },
23 | ],
24 | };
25 |
26 | export const totalProducts = {
27 | color: 'skyblue',
28 | IconBox: MdInventory2,
29 | title: 'Total Products',
30 | number: '238',
31 | dataKey: 'products',
32 | percentage: 21,
33 | chartData: [
34 | { name: 'Sun', products: 400 },
35 | { name: 'Mon', products: 600 },
36 | { name: 'Tue', products: 500 },
37 | { name: 'Wed', products: 700 },
38 | { name: 'Thu', products: 400 },
39 | { name: 'Fri', products: 500 },
40 | { name: 'Sat', products: 450 },
41 | ],
42 | };
43 | export const totalRevenue = {
44 | color: 'teal',
45 | IconBox: MdAssessment,
46 | title: 'Total Revenue',
47 | number: '$56.432',
48 | dataKey: 'revenue',
49 | percentage: -12,
50 | chartData: [
51 | { name: 'Sun', revenue: 400 },
52 | { name: 'Mon', revenue: 600 },
53 | { name: 'Tue', revenue: 500 },
54 | { name: 'Wed', revenue: 700 },
55 | { name: 'Thu', revenue: 400 },
56 | { name: 'Fri', revenue: 500 },
57 | { name: 'Sat', revenue: 450 },
58 | ],
59 | };
60 | export const totalRatio = {
61 | color: 'gold',
62 | IconBox: MdSwapHorizontalCircle,
63 | title: 'Total Ratio',
64 | number: '2.6',
65 | dataKey: 'ratio',
66 | percentage: 12,
67 | chartData: [
68 | { name: 'Sun', ratio: 400 },
69 | { name: 'Mon', ratio: 600 },
70 | { name: 'Tue', ratio: 500 },
71 | { name: 'Wed', ratio: 700 },
72 | { name: 'Thu', ratio: 400 },
73 | { name: 'Fri', ratio: 500 },
74 | { name: 'Sat', ratio: 450 },
75 | ],
76 | };
77 |
78 | export const totalVisit = {
79 | title: 'Total Visit',
80 | color: '#FF8042',
81 | dataKey: 'visit',
82 | chartData: [
83 | {
84 | name: 'Sun',
85 | visit: 4000,
86 | },
87 | {
88 | name: 'Mon',
89 | visit: 3000,
90 | },
91 | {
92 | name: 'Tue',
93 | visit: 2000,
94 | },
95 | {
96 | name: 'Wed',
97 | visit: 2780,
98 | },
99 | {
100 | name: 'Thu',
101 | visit: 1890,
102 | },
103 | {
104 | name: 'Fri',
105 | visit: 2390,
106 | },
107 | {
108 | name: 'Sat',
109 | visit: 3490,
110 | },
111 | ],
112 | };
113 |
114 | export const totalProfit = {
115 | title: 'Profit Earned',
116 | color: '#8884d8',
117 | dataKey: 'profit',
118 | chartData: [
119 | {
120 | name: 'Sun',
121 | profit: 4000,
122 | },
123 | {
124 | name: 'Mon',
125 | profit: 3000,
126 | },
127 | {
128 | name: 'Tue',
129 | profit: 2000,
130 | },
131 | {
132 | name: 'Wed',
133 | profit: 2780,
134 | },
135 | {
136 | name: 'Thu',
137 | profit: 1890,
138 | },
139 | {
140 | name: 'Fri',
141 | profit: 2390,
142 | },
143 | {
144 | name: 'Sat',
145 | profit: 3490,
146 | },
147 | ],
148 | };
149 |
150 | export const totalSource = {
151 | title: 'Leads by Source',
152 | // color: '#8884d8',
153 | dataKey: 'value',
154 | chartPieData: [
155 | { name: 'Mobile', value: 350, color: '#0088FE' },
156 | { name: 'Desktop', value: 250, color: '#00C49F' },
157 | { name: 'Laptop', value: 325, color: '#FFBB28' },
158 | { name: 'Tablet', value: 75, color: '#FF8042' },
159 | ],
160 | };
161 |
162 | export const totalRevenueByProducts = {
163 | title: 'Revenue by Products',
164 | // color: '#8884d8',
165 | dataKey: 'name',
166 | chartAreaData: [
167 | {
168 | name: 'Sun',
169 | smartphones: 4000,
170 | consoles: 2400,
171 | laptops: 2400,
172 | others: 1000,
173 | },
174 | {
175 | name: 'Mon',
176 | smartphones: 3000,
177 | consoles: 1398,
178 | laptops: 2210,
179 | others: 700,
180 | },
181 | {
182 | name: 'Tue',
183 | smartphones: 2000,
184 | consoles: 9800,
185 | laptops: 2290,
186 | others: 675,
187 | },
188 | {
189 | name: 'Wed',
190 | smartphones: 2780,
191 | consoles: 3908,
192 | laptops: 2000,
193 | others: 685,
194 | },
195 | {
196 | name: 'Thu',
197 | smartphones: 1890,
198 | consoles: 4800,
199 | laptops: 2181,
200 | others: 675,
201 | },
202 | {
203 | name: 'Fri',
204 | smartphones: 2390,
205 | consoles: 3800,
206 | laptops: 2500,
207 | others: 650,
208 | },
209 | {
210 | name: 'Sat',
211 | smartphones: 3490,
212 | consoles: 4300,
213 | laptops: 2100,
214 | others: 1075,
215 | },
216 | ],
217 | };
218 |
--------------------------------------------------------------------------------
/backend/src/data/orders.ts:
--------------------------------------------------------------------------------
1 | const orders = [
2 | {
3 | id: 1,
4 | product:
5 | 'https://venturebeat.com/wp-content/uploads/2015/07/As_AO1-131_gray_nonglare_win10_03.png?fit=1338%2C1055&strip=all',
6 | title: 'Acer Laptop 16 KL-4804',
7 | profile:
8 | 'https://images.pexels.com/photos/3785079/pexels-photo-3785079.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
9 | recipient: 'Teddy Firgantoro',
10 | address: 'Jln. Gatot Subroto No. 620, Palu',
11 | date: '01.02.2024',
12 | total: '$599.99',
13 | status: 'Pending',
14 | },
15 | {
16 | id: 2,
17 | product:
18 | 'https://img.productz.com/review_image/102489/preview_sony-kdl-50w800b-50-inch-hdtv-review-superb-picture-102489.png',
19 | title: 'Sony Bravia KDL-47W805A',
20 | profile:
21 | 'https://images.pexels.com/photos/761977/pexels-photo-761977.jpeg?auto=compress&cs=tinysrgb&w=1600',
22 | recipient: 'Laila Tari Puspasari',
23 | address: 'Jln. Jakarta No. 468, Malang',
24 | date: '25.01.2024',
25 | total: '$879.99',
26 | status: 'Dispatch',
27 | },
28 | {
29 | id: 3,
30 | product:
31 | 'https://www.smartworld.it/wp-content/uploads/2019/09/High_Resolution_PNG-MX-Master-3-LEFT-GRAPHITE.png',
32 | title: 'Logitech MX Master 3',
33 | profile:
34 | 'https://images.pexels.com/photos/1547971/pexels-photo-1547971.jpeg',
35 | recipient: 'Kezia Yulia',
36 | address: 'Jr. Qrisdoren No. 731, Semarang',
37 | date: '03.02.2024',
38 | total: '$879.99',
39 | status: 'Cancelled',
40 | },
41 | {
42 | id: 4,
43 | product:
44 | 'https://www.signify.com/b-dam/signify/en-aa/about/news/2020/20200903-movie-night-essentials-popcorn-ice-cream-and-the-new-philips-hue-play-gradient-lightstrip/packaging-lighstrip.png',
45 | title: 'Philips Hue Play Gradient',
46 | profile:
47 | 'https://images.pexels.com/photos/428364/pexels-photo-428364.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
48 | recipient: 'Bambang Najmudin',
49 | address: 'Jr. Supono No. 770, DI Yogyakarta',
50 | date: '01.02.2024',
51 | total: '$45.99',
52 | status: 'Completed',
53 | },
54 | {
55 | id: 5,
56 | product:
57 | 'http://images.samsung.com/is/image/samsung/uk-led-tv-hg40ed670ck-hg40ed670ckxxu-001-front',
58 | title: 'Samsung TV 4K SmartTV',
59 | profile:
60 | 'https://images.pexels.com/photos/681644/pexels-photo-681644.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
61 | recipient: 'Wulan Pratiwi',
62 | address: 'Psr. Surapati No. 982, Denpasar',
63 | date: '01.02.2024',
64 | total: '$549.99',
65 | status: 'Dispatch',
66 | },
67 | {
68 | id: 6,
69 | product:
70 | 'https://store.sony.com.au/on/demandware.static/-/Sites-sony-master-catalog/default/dw1b537bbb/images/PLAYSTATION5W/PLAYSTATION5W.png',
71 | title: 'Playstation 5 Digital Edition',
72 | profile:
73 | 'https://images.pexels.com/photos/623305/pexels-photo-623305.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
74 | recipient: 'Darimin Permadi',
75 | address: 'Jln. Ekonomi No. 524, Palangka Raya',
76 | date: '15.01.2024',
77 | total: '$759.99',
78 | status: 'Completed',
79 | },
80 | {
81 | id: 7,
82 | product:
83 | 'https://www.pngmart.com/files/6/Dell-Laptop-PNG-Image.png',
84 | title: 'Dell Laptop KR211822',
85 | profile:
86 | 'https://images.pexels.com/photos/3361154/pexels-photo-3361154.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
87 | recipient: 'Salsabila Mutia Novitasari',
88 | address: 'Jln. Pasteur No. 981, Jakarta Barat',
89 | date: '28.01.2024',
90 | total: '$499.99',
91 | status: 'Completed',
92 | },
93 | {
94 | id: 8,
95 | product: 'https://raylo.imgix.net/iphone-14-blue.png',
96 | title: 'Apple Iphone 14 Pro Max',
97 | profile:
98 | 'https://images.pexels.com/photos/460031/pexels-photo-460031.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
99 | recipient: 'Lasmanto Putra',
100 | address: 'Ki. Bakti No. 754, Semarang',
101 | date: '08.02.2024',
102 | total: '$799.49',
103 | status: 'Pending',
104 | },
105 | {
106 | id: 9,
107 | product:
108 | 'https://www.smartworld.it/wp-content/uploads/2019/09/High_Resolution_PNG-MX-Master-3-LEFT-GRAPHITE.png',
109 | title: 'Logitech MX Master 3',
110 | profile:
111 | 'https://images.pexels.com/photos/1083855/pexels-photo-1083855.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
112 | recipient: 'Cawisadi Hardiansyah',
113 | address: 'Ds. Sutarto No. 418, Surakarta',
114 | date: '10.02.2024',
115 | total: '$79.49',
116 | status: 'Cancelled',
117 | },
118 | {
119 | id: 10,
120 | product:
121 | 'http://images.samsung.com/is/image/samsung/uk-led-tv-hg40ed670ck-hg40ed670ckxxu-001-front',
122 | title: 'Samsung TV 4K SmartTV',
123 | profile:
124 | 'https://images.pexels.com/photos/1832326/pexels-photo-1832326.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
125 | recipient: 'Amelia Utami',
126 | address: 'Jr. Bank Dagang Negara No. 893, Denpasar',
127 | date: '14.02.2024',
128 | total: '$679.49',
129 | status: 'Dispatch',
130 | },
131 | ];
132 |
133 | export default orders;
134 |
--------------------------------------------------------------------------------
/frontend/src/pages/Home.tsx:
--------------------------------------------------------------------------------
1 | // import React from 'react';
2 | import TopDealsBox from '../components/topDealsBox/TopDealsBox';
3 | import ChartBox from '../components/charts/ChartBox';
4 | import { useQuery } from '@tanstack/react-query';
5 | import {
6 | MdGroup,
7 | MdInventory2,
8 | MdAssessment,
9 | MdSwapHorizontalCircle,
10 | } from 'react-icons/md';
11 | import {
12 | fetchTotalProducts,
13 | fetchTotalProfit,
14 | fetchTotalRatio,
15 | fetchTotalRevenue,
16 | fetchTotalRevenueByProducts,
17 | fetchTotalSource,
18 | fetchTotalUsers,
19 | fetchTotalVisit,
20 | } from '../api/ApiCollection';
21 |
22 | const Home = () => {
23 | const queryGetTotalUsers = useQuery({
24 | queryKey: ['totalusers'],
25 | queryFn: fetchTotalUsers,
26 | });
27 |
28 | const queryGetTotalProducts = useQuery({
29 | queryKey: ['totalproducts'],
30 | queryFn: fetchTotalProducts,
31 | });
32 |
33 | const queryGetTotalRatio = useQuery({
34 | queryKey: ['totalratio'],
35 | queryFn: fetchTotalRatio,
36 | });
37 |
38 | const queryGetTotalRevenue = useQuery({
39 | queryKey: ['totalrevenue'],
40 | queryFn: fetchTotalRevenue,
41 | });
42 |
43 | const queryGetTotalSource = useQuery({
44 | queryKey: ['totalsource'],
45 | queryFn: fetchTotalSource,
46 | });
47 |
48 | const queryGetTotalRevenueByProducts = useQuery({
49 | queryKey: ['totalrevenue-by-products'],
50 | queryFn: fetchTotalRevenueByProducts,
51 | });
52 |
53 | const queryGetTotalVisit = useQuery({
54 | queryKey: ['totalvisit'],
55 | queryFn: fetchTotalVisit,
56 | });
57 |
58 | const queryGetTotalProfit = useQuery({
59 | queryKey: ['totalprofit'],
60 | queryFn: fetchTotalProfit,
61 | });
62 |
63 | return (
64 | // screen
65 |
66 | {/* grid */}
67 |
68 |
69 |
70 |
71 |
72 |
80 |
81 |
82 |
90 |
91 |
92 |
99 |
100 |
101 |
109 |
110 |
111 |
119 |
120 |
121 |
128 |
129 |
130 |
137 |
138 |
139 |
146 |
147 |
148 |
149 | );
150 | };
151 |
152 | export default Home;
153 |
--------------------------------------------------------------------------------
/frontend/src/pages/Login.tsx:
--------------------------------------------------------------------------------
1 | // import React from 'react';
2 | import ChangeThemes from '../components/ChangesThemes';
3 | import { DiReact } from 'react-icons/di';
4 | import { useNavigate } from 'react-router-dom';
5 |
6 | const Login = () => {
7 | const navigate = useNavigate();
8 | return (
9 | // screen
10 |
11 | {/* container */}
12 |
13 | {/* theme */}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | React Dashboard
22 |
23 |
24 |
25 | Hello, 👋 Welcome Back!
26 |
27 |
28 |
29 |
35 |
36 |
37 |
38 |
43 |
44 |
45 |
51 |
56 |
57 |
62 |
63 |
83 |
navigate('/')}
85 | className="btn btn-block btn-primary"
86 | >
87 | Log In
88 |
89 |
OR
90 |
91 |
92 |
97 |
98 |
99 |
104 |
105 |
106 |
111 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | );
123 | };
124 |
125 | export default Login;
126 |
--------------------------------------------------------------------------------
/frontend/src/components/calendar/CalendarBox.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | EventApi,
4 | DateSelectArg,
5 | EventClickArg,
6 | EventContentArg,
7 | formatDate,
8 | } from '@fullcalendar/core';
9 | import FullCalendar from '@fullcalendar/react';
10 | import dayGridPlugin from '@fullcalendar/daygrid';
11 | import timeGridPlugin from '@fullcalendar/timegrid';
12 | import interactionPlugin from '@fullcalendar/interaction';
13 | // import listPlugin from '@fullcalendar/list';
14 | import { INITIAL_EVENTS, createEventId } from './CalendarUtils';
15 |
16 | // interface CalendarBoxProps {
17 | // weekendsVisible: boolean;
18 | // currentEvents: EventApi[];
19 | // }
20 |
21 | const CalendarBox: React.FC = () => {
22 | const [currentEvents, setCurrentEvents] = React.useState<
23 | EventApi[]
24 | >([]);
25 |
26 | const handleDateSelect = (selectInfo: DateSelectArg) => {
27 | const title = prompt('Please enter a new title for your event');
28 | const calendarApi = selectInfo.view.calendar;
29 |
30 | calendarApi.unselect(); // clear date selection
31 |
32 | if (title) {
33 | calendarApi.addEvent({
34 | id: createEventId(),
35 | title,
36 | start: selectInfo.startStr,
37 | end: selectInfo.endStr,
38 | allDay: selectInfo.allDay,
39 | });
40 | }
41 | };
42 |
43 | const handleEventClick = (clickInfo: EventClickArg) => {
44 | if (
45 | confirm(
46 | `Are you sure you want to delete the event '${clickInfo.event.title}'`
47 | )
48 | ) {
49 | clickInfo.event.remove();
50 | }
51 | };
52 |
53 | const handleEvents = (events: EventApi[]) => {
54 | setCurrentEvents(events);
55 | };
56 |
57 | const renderEventContent = (eventContent: EventContentArg) => {
58 | return (
59 |
60 | {eventContent.timeText}
61 | {eventContent.event.title}
62 |
63 | );
64 | };
65 |
66 | const renderSidebarEvent = (event: EventApi) => {
67 | return (
68 |
72 |
73 |
74 | {formatDate(event.start!, {
75 | year: 'numeric',
76 | month: 'short',
77 | day: 'numeric',
78 | })}
79 |
80 | {event.end && (
81 |
until
82 | )}
83 |
84 | {formatDate(event.end!, {
85 | year: 'numeric',
86 | month: 'short',
87 | day: 'numeric',
88 | })}
89 |
90 |
91 |
{event.title}
92 |
93 | );
94 | };
95 |
96 | return (
97 |
98 | {/* sidebar */}
99 |
100 | {/* heading */}
101 |
Events
102 |
103 | {currentEvents.map(renderSidebarEvent)}
104 |
105 |
106 | {currentEvents.map((event) => (
107 |
111 |
112 |
113 | {formatDate(event.start!, {
114 | year: 'numeric',
115 | month: 'short',
116 | day: 'numeric',
117 | })}
118 |
119 | {event.end && (
120 |
until
121 | )}
122 |
123 | {formatDate(event.end!, {
124 | year: 'numeric',
125 | month: 'short',
126 | day: 'numeric',
127 | })}
128 |
129 |
130 |
{event.title}
131 |
132 | ))}
133 |
134 |
135 |
136 | {/* calendar */}
137 |
138 |
162 |
163 |
164 | );
165 | };
166 |
167 | export default CalendarBox;
168 |
--------------------------------------------------------------------------------
/frontend/src/components/Navbar.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, useNavigate } from 'react-router-dom';
3 | import { HiBars3CenterLeft } from 'react-icons/hi2';
4 | import { DiReact } from 'react-icons/di';
5 | import { HiSearch, HiOutlineBell } from 'react-icons/hi';
6 | import { RxEnterFullScreen, RxExitFullScreen } from 'react-icons/rx';
7 | import ChangeThemes from './ChangesThemes';
8 | import toast from 'react-hot-toast';
9 | import { menu } from './menu/data';
10 | import MenuItem from './menu/MenuItem';
11 |
12 | const Navbar = () => {
13 | const [isFullScreen, setIsFullScreen] = React.useState(true);
14 | const element = document.getElementById('root');
15 |
16 | const [isDrawerOpen, setDrawerOpen] = React.useState(false);
17 | const toggleDrawer = () => setDrawerOpen(!isDrawerOpen);
18 |
19 | const toggleFullScreen = () => {
20 | setIsFullScreen((prev) => !prev);
21 | };
22 |
23 | const navigate = useNavigate();
24 |
25 | React.useEffect(() => {
26 | if (isFullScreen) {
27 | document.exitFullscreen();
28 | } else {
29 | element?.requestFullscreen({ navigationUI: 'auto' });
30 | }
31 | }, [element, isFullScreen]);
32 |
33 | return (
34 | // navbar screen
35 |
36 | {/* container */}
37 |
38 | {/* for mobile */}
39 |
40 |
47 |
48 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
66 |
67 |
68 | React Dashboard
69 |
70 |
71 | {menu.map((item, index) => (
72 |
78 | ))}
79 |
80 |
81 |
82 |
83 | {/* navbar logo */}
84 |
85 |
86 |
87 | React Dashboard
88 |
89 |
90 |
91 |
92 | {/* navbar items to right */}
93 |
94 | {/* search */}
95 |
97 | toast('Gaboleh cari!', {
98 | icon: '😠',
99 | })
100 | }
101 | className="hidden sm:inline-flex btn btn-circle btn-ghost"
102 | >
103 |
104 |
105 |
106 | {/* fullscreen */}
107 |
111 | {isFullScreen ? (
112 |
113 | ) : (
114 |
115 | )}
116 |
117 |
118 | {/* notification */}
119 |
121 | toast('Gaada notif!', {
122 | icon: '😠',
123 | })
124 | }
125 | className="px-0 xl:px-auto btn btn-circle btn-ghost"
126 | >
127 |
128 |
129 |
130 | {/* theme */}
131 |
132 |
133 |
134 |
135 | {/* avatar dropdown */}
136 |
137 |
142 |
143 |
147 |
148 |
149 |
153 |
154 |
155 | My Profile
156 |
157 |
158 | navigate('/login')}>
159 | Log Out
160 |
161 |
162 |
163 |
164 |
165 | );
166 | };
167 |
168 | export default Navbar;
169 |
--------------------------------------------------------------------------------
/frontend/src/pages/Charts.tsx:
--------------------------------------------------------------------------------
1 | // import React from 'react';
2 | import {
3 | LineChart,
4 | Line,
5 | PieChart,
6 | Pie,
7 | Cell,
8 | AreaChart,
9 | Area,
10 | BarChart,
11 | Bar,
12 | // XAxis,
13 | // YAxis,
14 | // CartesianGrid,
15 | ResponsiveContainer,
16 | } from 'recharts';
17 |
18 | const Charts = () => {
19 | const dataLine = [
20 | {
21 | name: 'Page A',
22 | uv: 4000,
23 | pv: 2400,
24 | amt: 2400,
25 | },
26 | {
27 | name: 'Page B',
28 | uv: 3000,
29 | pv: 1398,
30 | amt: 2210,
31 | },
32 | {
33 | name: 'Page C',
34 | uv: 2000,
35 | pv: 9800,
36 | amt: 2290,
37 | },
38 | {
39 | name: 'Page D',
40 | uv: 2780,
41 | pv: 3908,
42 | amt: 2000,
43 | },
44 | {
45 | name: 'Page E',
46 | uv: 1890,
47 | pv: 4800,
48 | amt: 2181,
49 | },
50 | {
51 | name: 'Page F',
52 | uv: 2390,
53 | pv: 3800,
54 | amt: 2500,
55 | },
56 | {
57 | name: 'Page G',
58 | uv: 3490,
59 | pv: 4300,
60 | amt: 2100,
61 | },
62 | ];
63 |
64 | const dataPie = [
65 | { name: 'Group A', value: 400 },
66 | { name: 'Group B', value: 300 },
67 | { name: 'Group C', value: 300 },
68 | { name: 'Group D', value: 200 },
69 | ];
70 | const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042'];
71 |
72 | const dataArea = [
73 | {
74 | name: 'Page A',
75 | uv: 4000,
76 | pv: 2400,
77 | amt: 2400,
78 | },
79 | {
80 | name: 'Page B',
81 | uv: 3000,
82 | pv: 1398,
83 | amt: 2210,
84 | },
85 | {
86 | name: 'Page C',
87 | uv: 2000,
88 | pv: 9800,
89 | amt: 2290,
90 | },
91 | {
92 | name: 'Page D',
93 | uv: 2780,
94 | pv: 3908,
95 | amt: 2000,
96 | },
97 | {
98 | name: 'Page E',
99 | uv: 1890,
100 | pv: 4800,
101 | amt: 2181,
102 | },
103 | {
104 | name: 'Page F',
105 | uv: 2390,
106 | pv: 3800,
107 | amt: 2500,
108 | },
109 | {
110 | name: 'Page G',
111 | uv: 3490,
112 | pv: 4300,
113 | amt: 2100,
114 | },
115 | ];
116 |
117 | const dataBar = [
118 | {
119 | name: 'Page A',
120 | uv: 4000,
121 | pv: 2400,
122 | amt: 2400,
123 | },
124 | {
125 | name: 'Page B',
126 | uv: 3000,
127 | pv: 1398,
128 | amt: 2210,
129 | },
130 | {
131 | name: 'Page C',
132 | uv: 2000,
133 | pv: 9800,
134 | amt: 2290,
135 | },
136 | {
137 | name: 'Page D',
138 | uv: 2780,
139 | pv: 3908,
140 | amt: 2000,
141 | },
142 | {
143 | name: 'Page E',
144 | uv: 1890,
145 | pv: 4800,
146 | amt: 2181,
147 | },
148 | {
149 | name: 'Page F',
150 | uv: 2390,
151 | pv: 3800,
152 | amt: 2500,
153 | },
154 | {
155 | name: 'Page G',
156 | uv: 3490,
157 | pv: 4300,
158 | amt: 2100,
159 | },
160 | ];
161 |
162 | return (
163 | // screen
164 |
165 | {/* container */}
166 |
167 | {/* heading */}
168 |
169 | Charts
170 |
171 | {/* grid */}
172 |
173 |
174 |
175 |
176 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
196 | {dataPie.map((_entry, index) => (
197 | |
201 | ))}
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 | {/* */}
210 | {/* */}
211 | {/* */}
212 | {/* */}
213 |
220 |
227 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 | );
248 | };
249 |
250 | export default Charts;
251 |
--------------------------------------------------------------------------------
/frontend/src/api/ApiCollection.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | // GET TOP DEALS
4 | export const fetchTopDeals = async () => {
5 | const response = await axios
6 | .get('https://react-admin-ui-v1-api.vercel.app/topdeals')
7 | .then((res) => {
8 | console.log('axios get:', res.data);
9 | return res.data;
10 | })
11 | .catch((err) => {
12 | console.log(err);
13 | throw err;
14 | });
15 |
16 | return response;
17 | };
18 |
19 | // GET TOTAL USERS
20 | export const fetchTotalUsers = async () => {
21 | const response = await axios
22 | .get('https://react-admin-ui-v1-api.vercel.app/totalusers')
23 | .then((res) => {
24 | console.log('axios get:', res.data);
25 | return res.data;
26 | })
27 | .catch((err) => {
28 | console.log(err);
29 | throw err;
30 | });
31 |
32 | return response;
33 | };
34 |
35 | // GET TOTAL PRODUCTS
36 | export const fetchTotalProducts = async () => {
37 | const response = await axios
38 | .get('https://react-admin-ui-v1-api.vercel.app/totalproducts')
39 | .then((res) => {
40 | console.log('axios get:', res.data);
41 | return res.data;
42 | })
43 | .catch((err) => {
44 | console.log(err);
45 | throw err;
46 | });
47 |
48 | return response;
49 | };
50 |
51 | // GET TOTAL RATIO
52 | export const fetchTotalRatio = async () => {
53 | const response = await axios
54 | .get('https://react-admin-ui-v1-api.vercel.app/totalratio')
55 | .then((res) => {
56 | console.log('axios get:', res.data);
57 | return res.data;
58 | })
59 | .catch((err) => {
60 | console.log(err);
61 | throw err;
62 | });
63 |
64 | return response;
65 | };
66 |
67 | // GET TOTAL REVENUE
68 | export const fetchTotalRevenue = async () => {
69 | const response = await axios
70 | .get('https://react-admin-ui-v1-api.vercel.app/totalrevenue')
71 | .then((res) => {
72 | console.log('axios get:', res.data);
73 | return res.data;
74 | })
75 | .catch((err) => {
76 | console.log(err);
77 | throw err;
78 | });
79 |
80 | return response;
81 | };
82 |
83 | // GET TOTAL SOURCE
84 | export const fetchTotalSource = async () => {
85 | const response = await axios
86 | .get('https://react-admin-ui-v1-api.vercel.app/totalsource')
87 | .then((res) => {
88 | console.log('axios get:', res.data);
89 | return res.data;
90 | })
91 | .catch((err) => {
92 | console.log(err);
93 | throw err;
94 | });
95 |
96 | return response;
97 | };
98 |
99 | // GET TOTAL VISIT
100 | export const fetchTotalVisit = async () => {
101 | const response = await axios
102 | .get('https://react-admin-ui-v1-api.vercel.app/totalvisit')
103 | .then((res) => {
104 | console.log('axios get:', res.data);
105 | return res.data;
106 | })
107 | .catch((err) => {
108 | console.log(err);
109 | throw err;
110 | });
111 |
112 | return response;
113 | };
114 |
115 | // GET TOTAL REVENUE BY PRODUCTS
116 | export const fetchTotalRevenueByProducts = async () => {
117 | const response = await axios
118 | .get(
119 | 'https://react-admin-ui-v1-api.vercel.app/totalrevenue-by-product'
120 | )
121 | .then((res) => {
122 | console.log('axios get:', res.data);
123 | return res.data;
124 | })
125 | .catch((err) => {
126 | console.log(err);
127 | throw err;
128 | });
129 |
130 | return response;
131 | };
132 |
133 | // GET TOTAL PROFIT
134 | export const fetchTotalProfit = async () => {
135 | const response = await axios
136 | .get('https://react-admin-ui-v1-api.vercel.app/totalprofit')
137 | .then((res) => {
138 | console.log('axios get:', res.data);
139 | return res.data;
140 | })
141 | .catch((err) => {
142 | console.log(err);
143 | throw err;
144 | });
145 |
146 | return response;
147 | };
148 |
149 | // GET ALL USERS
150 | export const fetchUsers = async () => {
151 | const response = await axios
152 | .get('https://react-admin-ui-v1-api.vercel.app/users')
153 | .then((res) => {
154 | console.log('axios get:', res.data);
155 | return res.data;
156 | })
157 | .catch((err) => {
158 | console.log(err);
159 | throw err;
160 | });
161 |
162 | return response;
163 | };
164 |
165 | // GET SINGLE USER
166 | export const fetchSingleUser = async (id: string) => {
167 | const response = await axios
168 | .get(`https://react-admin-ui-v1-api.vercel.app/users/${id}`)
169 | .then((res) => {
170 | console.log('axios get:', res.data);
171 | return res.data;
172 | })
173 | .catch((err) => {
174 | console.log(err);
175 | throw err;
176 | });
177 |
178 | return response;
179 | };
180 |
181 | // GET ALL PRODUCTS
182 | export const fetchProducts = async () => {
183 | const response = await axios
184 | .get('https://react-admin-ui-v1-api.vercel.app/products')
185 | .then((res) => {
186 | console.log('axios get:', res.data);
187 | return res.data;
188 | })
189 | .catch((err) => {
190 | console.log(err);
191 | throw err;
192 | });
193 |
194 | return response;
195 | };
196 |
197 | // GET SINGLE PRODUCT
198 | export const fetchSingleProduct = async (id: string) => {
199 | const response = await axios
200 | .get(`https://react-admin-ui-v1-api.vercel.app/products/${id}`)
201 | .then((res) => {
202 | console.log('axios get:', res.data);
203 | return res.data;
204 | })
205 | .catch((err) => {
206 | console.log(err);
207 | throw err;
208 | });
209 |
210 | return response;
211 | };
212 |
213 | // GET ALL ORDERS
214 | export const fetchOrders = async () => {
215 | const response = await axios
216 | .get('https://react-admin-ui-v1-api.vercel.app/orders')
217 | .then((res) => {
218 | console.log('axios get:', res.data);
219 | return res.data;
220 | })
221 | .catch((err) => {
222 | console.log(err);
223 | throw err;
224 | });
225 |
226 | return response;
227 | };
228 |
229 | // GET ALL POSTS
230 | export const fetchPosts = async () => {
231 | const response = await axios
232 | .get('https://react-admin-ui-v1-api.vercel.app/posts')
233 | .then((res) => {
234 | console.log('axios get:', res.data);
235 | return res.data;
236 | })
237 | .catch((err) => {
238 | console.log(err);
239 | throw err;
240 | });
241 |
242 | return response;
243 | };
244 |
245 | // GET ALL NOTES
246 | export const fetchNotes = async () => {
247 | const response = await axios
248 | .get(`https://react-admin-ui-v1-api.vercel.app/notes?q=`)
249 | .then((res) => {
250 | console.log('axios get:', res.data);
251 | return res.data;
252 | })
253 | .catch((err) => {
254 | console.log(err);
255 | throw err;
256 | });
257 |
258 | return response;
259 | };
260 |
261 | // GET ALL LOGS
262 | export const fetchLogs = async () => {
263 | const response = await axios
264 | .get(`https://react-admin-ui-v1-api.vercel.app/logs`)
265 | .then((res) => {
266 | console.log('axios get:', res.data);
267 | return res.data;
268 | })
269 | .catch((err) => {
270 | console.log(err);
271 | throw err;
272 | });
273 |
274 | return response;
275 | };
276 |
--------------------------------------------------------------------------------
/frontend/src/pages/Orders.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { GridColDef } from '@mui/x-data-grid';
3 | import DataTable from '../components/DataTable';
4 | import { useQuery } from '@tanstack/react-query';
5 | import toast from 'react-hot-toast';
6 | // import AddData from '../components/AddData';
7 | import { fetchOrders } from '../api/ApiCollection';
8 |
9 | const Orders = () => {
10 | // const [isOpen, setIsOpen] = React.useState(false);
11 | const { isLoading, isError, isSuccess, data } = useQuery({
12 | queryKey: ['allorders'],
13 | queryFn: fetchOrders,
14 | });
15 |
16 | const columns: GridColDef[] = [
17 | { field: 'id', headerName: 'ID', width: 90 },
18 | {
19 | field: 'title',
20 | headerName: 'Product',
21 | minWidth: 300,
22 | flex: 1,
23 | renderCell: (params) => {
24 | return (
25 |
26 |
27 |
32 |
33 |
34 | {params.row.title}
35 |
36 |
37 | );
38 | },
39 | },
40 | {
41 | field: 'address',
42 | type: 'string',
43 | headerName: 'Address',
44 | minWidth: 320,
45 | flex: 1,
46 | },
47 | {
48 | field: 'recipient',
49 | headerName: 'Recipient',
50 | minWidth: 250,
51 | flex: 1,
52 | renderCell: (params) => {
53 | return (
54 |
55 |
56 |
57 |
63 |
64 |
65 |
66 | {params.row.recipient}
67 |
68 |
69 | );
70 | },
71 | },
72 | {
73 | field: 'date',
74 | headerName: 'Date',
75 | minWidth: 100,
76 | type: 'string',
77 | flex: 1,
78 | },
79 | {
80 | field: 'total',
81 | headerName: 'Total',
82 | minWidth: 100,
83 | type: 'string',
84 | flex: 1,
85 | },
86 | {
87 | field: 'status',
88 | headerName: 'Status',
89 | minWidth: 120,
90 | flex: 1,
91 | renderCell: (params) => {
92 | if (params.row.status == 'Pending') {
93 | return (
94 |
95 |
96 |
97 | {params.row.status}
98 |
99 |
100 | );
101 | } else if (params.row.status == 'Dispatch') {
102 | return (
103 |
104 |
105 |
106 | {params.row.status}
107 |
108 |
109 | );
110 | } else if (params.row.status == 'Cancelled') {
111 | return (
112 |
113 |
114 |
115 | {params.row.status}
116 |
117 |
118 | );
119 | } else if (params.row.status == 'Completed') {
120 | return (
121 |
122 |
123 |
124 | {params.row.status}
125 |
126 |
127 | );
128 | } else {
129 | return (
130 |
131 |
132 |
133 | Unknown
134 |
135 |
136 | );
137 | }
138 | },
139 | },
140 | ];
141 |
142 | React.useEffect(() => {
143 | if (isLoading) {
144 | toast.loading('Loading...', { id: 'promiseOrders' });
145 | }
146 | if (isError) {
147 | toast.error('Error while getting the data!', {
148 | id: 'promiseOrders',
149 | });
150 | }
151 | if (isSuccess) {
152 | toast.success('Got the data successfully!', {
153 | id: 'promiseOrders',
154 | });
155 | }
156 | }, [isError, isLoading, isSuccess]);
157 |
158 | return (
159 |
160 |
161 |
162 |
163 |
164 | Orders
165 |
166 | {data && data.length > 0 && (
167 |
168 | {data.length} Orders Found
169 |
170 | )}
171 |
172 | {/*
setIsOpen(true)}
174 | className={`btn ${
175 | isLoading ? 'btn-disabled' : 'btn-primary'
176 | }`}
177 | >
178 | Add New Order +
179 | */}
180 |
181 | {isLoading ? (
182 |
188 | ) : isSuccess ? (
189 |
195 | ) : (
196 | <>
197 |
203 |
204 | Error while getting the data!
205 |
206 | >
207 | )}
208 |
209 | {/* {isOpen && (
210 |
215 | )} */}
216 |
217 |
218 | );
219 | };
220 |
221 | export default Orders;
222 |
--------------------------------------------------------------------------------
/frontend/src/pages/Posts.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { GridColDef } from '@mui/x-data-grid';
3 | import DataTable from '../components/DataTable';
4 | import { useQuery } from '@tanstack/react-query';
5 | import toast from 'react-hot-toast';
6 | // import AddData from '../components/AddData';
7 | import { fetchPosts } from '../api/ApiCollection';
8 | import {
9 | HiOutlineGlobeAmericas,
10 | HiOutlineLockClosed,
11 | } from 'react-icons/hi2';
12 |
13 | const Posts = () => {
14 | // const [isOpen, setIsOpen] = React.useState(false);
15 | const { isLoading, isError, isSuccess, data } = useQuery({
16 | queryKey: ['allorders'],
17 | queryFn: fetchPosts,
18 | });
19 |
20 | const columns: GridColDef[] = [
21 | { field: 'id', headerName: 'ID', minWidth: 90 },
22 | {
23 | field: 'title',
24 | headerName: 'Title',
25 | minWidth: 500,
26 | flex: 1,
27 | renderCell: (params) => {
28 | return (
29 |
30 |
31 |
39 |
40 |
41 |
42 |
43 | {params.row.title}
44 |
45 |
46 |
47 |
48 | {params.row.desc}
49 |
50 |
51 |
52 |
53 | );
54 | },
55 | },
56 | {
57 | field: 'tags',
58 | // type: 'string',
59 | headerName: 'Tags',
60 | minWidth: 250,
61 | flex: 1,
62 | renderCell: (params) => {
63 | return (
64 |
65 | {params.row.tags &&
66 | params.row.tags.map((tag: string, index: number) => (
67 |
71 | {tag}
72 |
73 | ))}
74 |
75 | );
76 | },
77 | },
78 | {
79 | field: 'author',
80 | headerName: 'Author',
81 | minWidth: 220,
82 | flex: 1,
83 | renderCell: (params) => {
84 | return (
85 |
86 |
87 |
88 |
94 |
95 |
96 |
97 | {params.row.author}
98 |
99 |
100 | );
101 | },
102 | },
103 | {
104 | field: 'visibility',
105 | headerName: 'Visibility',
106 | minWidth: 100,
107 | flex: 1,
108 | renderCell: (params) => {
109 | if (params.row.visibility == 'Public') {
110 | return (
111 |
112 |
113 |
114 | {params.row.visibility}
115 |
116 |
117 | );
118 | } else if (params.row.visibility == 'Private') {
119 | return (
120 |
121 |
122 |
123 | {params.row.visibility}
124 |
125 |
126 | );
127 | } else {
128 | return
Unknown ;
129 | }
130 | },
131 | },
132 | {
133 | field: 'date',
134 | type: 'string',
135 | headerName: 'Date',
136 | minWidth: 100,
137 | },
138 | {
139 | field: 'views',
140 | type: 'number',
141 | headerName: 'Views',
142 | minWidth: 120,
143 | },
144 | {
145 | field: 'comments',
146 | type: 'number',
147 | headerName: 'Comments',
148 | minWidth: 120,
149 | renderCell: (params) => {
150 | return (
151 |
152 | {params.row.comments && params.row.comments.length}
153 |
154 | );
155 | },
156 | },
157 | {
158 | field: 'likes',
159 | type: 'number',
160 | headerName: 'Likes',
161 | minWidth: 80,
162 | },
163 | ];
164 |
165 | React.useEffect(() => {
166 | if (isLoading) {
167 | toast.loading('Loading...', { id: 'promisePosts' });
168 | }
169 | if (isError) {
170 | toast.error('Error while getting the data!', {
171 | id: 'promisePosts',
172 | });
173 | }
174 | if (isSuccess) {
175 | toast.success('Got the data successfully!', {
176 | id: 'promisePosts',
177 | });
178 | }
179 | }, [isError, isLoading, isSuccess]);
180 |
181 | return (
182 |
183 |
184 |
185 |
186 |
187 | Posts
188 |
189 | {data && data.length > 0 && (
190 |
191 | {data.length} Posts Found
192 |
193 | )}
194 |
195 | {/*
setIsOpen(true)}
197 | className={`btn ${
198 | isLoading ? 'btn-disabled' : 'btn-primary'
199 | }`}
200 | >
201 | Add New Order +
202 | */}
203 |
204 | {isLoading ? (
205 |
211 | ) : isSuccess ? (
212 |
218 | ) : (
219 | <>
220 |
226 |
227 | Error while getting the data!
228 |
229 | >
230 | )}
231 |
232 | {/* {isOpen && (
233 |
238 | )} */}
239 |
240 |
241 | );
242 | };
243 |
244 | export default Posts;
245 |
--------------------------------------------------------------------------------
/frontend/src/pages/Product.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useParams } from 'react-router-dom';
3 | import toast from 'react-hot-toast';
4 | import { useQuery } from '@tanstack/react-query';
5 | import { fetchSingleProduct } from '../api/ApiCollection';
6 | import {
7 | LineChart,
8 | Line,
9 | XAxis,
10 | YAxis,
11 | Tooltip,
12 | Legend,
13 | ResponsiveContainer,
14 | } from 'recharts';
15 |
16 | const Product = () => {
17 | const tempEntries: number[] = [1, 2, 3, 4, 5];
18 | const dataLine = [
19 | {
20 | name: 'Jan',
21 | purchased: 4000,
22 | wishlisted: 2400,
23 | amt: 2400,
24 | },
25 | {
26 | name: 'Feb',
27 | purchased: 3000,
28 | wishlisted: 1398,
29 | amt: 2210,
30 | },
31 | {
32 | name: 'Mar',
33 | purchased: 2000,
34 | wishlisted: 9800,
35 | amt: 2290,
36 | },
37 | {
38 | name: 'Apr',
39 | purchased: 2780,
40 | wishlisted: 3908,
41 | amt: 2000,
42 | },
43 | {
44 | name: 'May',
45 | purchased: 1890,
46 | wishlisted: 4800,
47 | amt: 2181,
48 | },
49 | {
50 | name: 'Jun',
51 | purchased: 2390,
52 | wishlisted: 3800,
53 | amt: 2500,
54 | },
55 | {
56 | name: 'Jul',
57 | purchased: 3490,
58 | wishlisted: 4300,
59 | amt: 2100,
60 | },
61 | ];
62 |
63 | // const [user, setUser] = React.useState();
64 | const { id } = useParams();
65 | // const navigate = useNavigate();
66 |
67 | const { isLoading, isError, data, isSuccess } = useQuery({
68 | queryKey: ['user', id],
69 | queryFn: () => fetchSingleProduct(id || ''),
70 | });
71 |
72 | React.useEffect(() => {
73 | if (isLoading) {
74 | toast.loading('Loading...', { id: 'promiseRead' });
75 | }
76 | if (isError) {
77 | toast.error('Error while getting the data!', {
78 | id: 'promiseRead',
79 | });
80 | }
81 | if (isSuccess) {
82 | toast.success('Read the data successfully!', {
83 | id: 'promiseRead',
84 | });
85 | }
86 | }, [isError, isLoading, isSuccess]);
87 |
88 | return (
89 | // screen
90 |
91 | {/* container */}
92 |
93 | {/* column 1 */}
94 |
95 | {/* product block */}
96 |
97 | {/* photo block */}
98 |
99 |
100 |
101 | {isLoading ? (
102 |
103 | ) : isSuccess ? (
104 |
105 |
110 |
111 | ) : (
112 | ''
113 | )}
114 |
115 |
116 | {isLoading ? (
117 |
118 | ) : isSuccess ? (
119 |
120 | {data.title}
121 |
122 | ) : (
123 |
124 | )}
125 |
126 | Exclusive
127 |
128 |
129 |
130 |
131 | {/* detail block */}
132 |
133 | {isLoading ? (
134 |
135 | ) : isSuccess ? (
136 |
137 | {/* column 1 */}
138 |
139 | Product ID
140 | Color
141 | Price
142 | Producer
143 | Status
144 |
145 | {/* column 2 */}
146 |
147 | {data.id}
148 |
149 | {data.color}
150 |
151 |
152 | {data.price}
153 |
154 |
155 | {data.producer}
156 |
157 |
158 | {data.inStock ? 'In stock' : 'Out of stock'}
159 |
160 |
161 |
162 | ) : (
163 |
164 | )}
165 |
166 |
167 | {/* divider */}
168 |
169 | {/* chart */}
170 | {isLoading ? (
171 |
172 | ) : isSuccess ? (
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
185 |
190 |
191 |
192 |
193 | ) : (
194 |
195 | )}
196 |
197 | {/* column 2 */}
198 |
202 |
203 | Latest Activities
204 |
205 | {isLoading &&
206 | tempEntries.map((index: number) => (
207 |
211 | ))}
212 | {isSuccess && (
213 |
214 |
215 |
216 | Frans AHW purchased {data.title}
217 | 3 days ago
218 |
219 |
220 |
221 |
222 |
223 | Kurt Cobain added {data.title} into wishlist
224 |
225 | 1 week ago
226 |
227 |
228 |
229 |
230 | Mary Jane purchased {data.title}
231 | 2 weeks ago
232 |
233 |
234 |
235 |
236 |
237 | Jose Rose added {data.title} into wishlist
238 |
239 | 3 weeks ago
240 |
241 |
242 |
243 |
244 | James Deane purchased {data.title}
245 | 1 month ago
246 |
247 |
248 |
249 | )}
250 | {isError &&
251 | tempEntries.map((index: number) => (
252 |
256 | ))}
257 |
258 |
259 |
260 | );
261 | };
262 |
263 | export default Product;
264 |
--------------------------------------------------------------------------------
/frontend/src/pages/Profile.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import toast from 'react-hot-toast';
3 | import { HiOutlinePencil, HiOutlineTrash } from 'react-icons/hi2';
4 | import { useNavigate } from 'react-router-dom';
5 |
6 | const Profile = () => {
7 | const modalDelete = React.useRef
(null);
8 | const navigate = useNavigate();
9 |
10 | return (
11 | // screen
12 |
13 | {/* container */}
14 |
15 | {/* block 1 */}
16 |
17 |
18 | My Profile
19 |
20 | navigate('/profile/edit')}
22 | className="btn text-xs xl:text-sm dark:btn-neutral"
23 | >
24 | Edit My Profile
25 |
26 |
27 | {/* block 2 */}
28 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 | Frans AHW
40 |
41 | Supervisor
42 |
43 |
44 | {/* block 3 */}
45 |
46 | {/* heading */}
47 |
48 |
49 | Basic Information
50 |
51 |
52 |
53 | {/* grid */}
54 |
55 | {/* column 1 */}
56 |
57 | {/* column 1 label */}
58 |
59 | First Name*
60 | Last Name*
61 | Nickname
62 |
63 | {/* column 1 text */}
64 |
65 | Frans
66 | AHW
67 | Frans
68 |
69 |
70 | {/* column 2 */}
71 |
72 | {/* column 2 label */}
73 |
74 | Email*
75 | Phone
76 | Address
77 |
78 | {/* column 2 text */}
79 |
80 |
81 | franswinata6@gmail.com
82 |
83 | 081-234-5678
84 |
85 | Suite 948 Jl. Gajahmada No. 91, Malang, SM 74810
86 |
87 |
88 |
89 | {/* column 3 */}
90 |
91 | {/* column 3 label */}
92 |
93 | Password
94 |
95 | {/* column 3 text */}
96 |
97 |
98 | Change Password
99 |
100 |
101 |
102 |
103 |
104 | {/* block 4 */}
105 |
106 | {/* heading */}
107 |
108 |
109 |
110 | Account Integrations
111 |
112 |
113 |
114 |
115 | Authorize faster and easier with your external service
116 | account.
117 |
118 |
119 | {/* services block */}
120 |
121 | {/* column 1 */}
122 |
123 |
125 | toast('Gaboleh', {
126 | icon: '😠',
127 | })
128 | }
129 | className="btn btn-block flex-nowrap justify-start dark:btn-neutral"
130 | >
131 |
136 |
137 | Connect with Microsoft
138 |
139 |
140 |
141 |
146 |
147 | Connected with Google
148 |
149 |
150 |
152 | toast('Gaboleh', {
153 | icon: '😠',
154 | })
155 | }
156 | className="btn btn-block justify-start dark:btn-neutral"
157 | >
158 |
163 |
168 |
169 | Connect with Apple
170 |
171 |
172 |
173 | {/* column 2 */}
174 |
175 |
176 |
178 | toast('Gaboleh', {
179 | icon: '😠',
180 | })
181 | }
182 | className="btn btn-ghost text-error text-xs xl:text-sm"
183 | >
184 | Disconnect
185 |
186 |
187 |
188 |
189 |
190 | {/* block 5 */}
191 |
192 |
modalDelete.current?.showModal()}
195 | >
196 |
197 | Delete My Account
198 |
199 |
204 |
205 |
206 | Action Confirmation!
207 |
208 |
209 | Do you want to delete your account?
210 |
211 |
212 |
214 | toast('Lancang kamu ya!', {
215 | icon: '😠',
216 | })
217 | }
218 | className="btn btn-error btn-block text-base-100 dark:text-white"
219 | >
220 | Yes, I want to delete my account
221 |
222 |
228 |
229 |
230 |
231 |
232 |
233 |
234 | );
235 | };
236 |
237 | export default Profile;
238 |
--------------------------------------------------------------------------------
/frontend/src/pages/User.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useParams } from 'react-router-dom';
3 | import toast from 'react-hot-toast';
4 | import { useQuery } from '@tanstack/react-query';
5 | import { fetchSingleUser } from '../api/ApiCollection';
6 | import {
7 | LineChart,
8 | Line,
9 | XAxis,
10 | YAxis,
11 | Tooltip,
12 | Legend,
13 | ResponsiveContainer,
14 | } from 'recharts';
15 |
16 | const User = () => {
17 | const tempEntries: number[] = [1, 2, 3, 4, 5];
18 | const dataLine = [
19 | {
20 | name: 'Jan',
21 | purchased: 4000,
22 | wishlisted: 2400,
23 | amt: 2400,
24 | },
25 | {
26 | name: 'Feb',
27 | purchased: 3000,
28 | wishlisted: 1398,
29 | amt: 2210,
30 | },
31 | {
32 | name: 'Mar',
33 | purchased: 2000,
34 | wishlisted: 9800,
35 | amt: 2290,
36 | },
37 | {
38 | name: 'Apr',
39 | purchased: 2780,
40 | wishlisted: 3908,
41 | amt: 2000,
42 | },
43 | {
44 | name: 'May',
45 | purchased: 1890,
46 | wishlisted: 4800,
47 | amt: 2181,
48 | },
49 | {
50 | name: 'Jun',
51 | purchased: 2390,
52 | wishlisted: 3800,
53 | amt: 2500,
54 | },
55 | {
56 | name: 'Jul',
57 | purchased: 3490,
58 | wishlisted: 4300,
59 | amt: 2100,
60 | },
61 | ];
62 |
63 | // const [user, setUser] = React.useState();
64 | const { id } = useParams();
65 | // const navigate = useNavigate();
66 |
67 | const { isLoading, isError, data, isSuccess } = useQuery({
68 | queryKey: ['user', id],
69 | queryFn: () => fetchSingleUser(id || ''),
70 | });
71 |
72 | React.useEffect(() => {
73 | if (isLoading) {
74 | toast.loading('Loading...', { id: 'promiseRead' });
75 | }
76 | if (isError) {
77 | toast.error('Error while getting the data!', {
78 | id: 'promiseRead',
79 | });
80 | }
81 | if (isSuccess) {
82 | toast.success('Read the data successfully!', {
83 | id: 'promiseRead',
84 | });
85 | }
86 | }, [isError, isLoading, isSuccess]);
87 |
88 | return (
89 | // screen
90 |
91 | {/* container */}
92 |
93 | {/* column 1 */}
94 |
95 | {/* profile block */}
96 |
97 | {/* photo block */}
98 |
99 |
100 |
101 | {isLoading ? (
102 |
103 | ) : isSuccess ? (
104 |
105 |
106 |
107 | ) : (
108 | ''
109 | )}
110 |
111 |
112 | {isLoading ? (
113 |
114 | ) : isSuccess ? (
115 |
116 | {data.firstName} {data.lastName}
117 |
118 | ) : (
119 |
120 | )}
121 |
122 | Member
123 |
124 |
125 |
126 |
127 | {/* detail block */}
128 |
129 | {isLoading ? (
130 |
131 | ) : isSuccess ? (
132 |
133 | {/* column 1 */}
134 |
135 | First Name
136 | Last Name
137 | Email
138 | Phone
139 | Status
140 |
141 | {/* column 2 */}
142 |
143 |
144 | {data.firstName}
145 |
146 |
147 | {data.lastName}
148 |
149 |
150 | {data.email}
151 |
152 |
153 | {data.phone}
154 |
155 |
156 | {data.verified ? 'Verified' : 'Not Verified'}
157 |
158 |
159 |
160 | ) : (
161 |
162 | )}
163 |
164 |
165 | {/* divider */}
166 |
167 | {/* chart */}
168 | {isLoading ? (
169 |
170 | ) : isSuccess ? (
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
183 |
188 |
189 |
190 |
191 | ) : (
192 |
193 | )}
194 |
195 | {/* column 2 */}
196 |
200 |
201 | Latest Activities
202 |
203 | {isLoading &&
204 | tempEntries.map((index: number) => (
205 |
209 | ))}
210 | {isSuccess && (
211 |
212 |
213 |
214 |
215 | {data.firstName} {data.lastName} purchased
216 | Playstation 5 Digital Edition
217 |
218 | 3 days ago
219 |
220 |
221 |
222 |
223 |
224 | {data.firstName} {data.lastName} added 3 items
225 | into wishlist
226 |
227 | 1 week ago
228 |
229 |
230 |
231 |
232 |
233 | {data.firstName} {data.lastName} purchased Samsung
234 | 4K UHD SmartTV
235 |
236 | 2 weeks ago
237 |
238 |
239 |
240 |
241 |
242 | {data.firstName} {data.lastName} commented a post
243 |
244 | 3 weeks ago
245 |
246 |
247 |
248 |
249 |
250 | {data.firstName} {data.lastName} added 1 item into
251 | wishlist
252 |
253 | 1 month ago
254 |
255 |
256 |
257 | )}
258 | {isError &&
259 | tempEntries.map((index: number) => (
260 |
264 | ))}
265 |
266 |
267 |
268 | );
269 | };
270 |
271 | export default User;
272 |
--------------------------------------------------------------------------------
/frontend/src/pages/Notes.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import axios from 'axios';
3 | import { useQuery } from '@tanstack/react-query';
4 | import toast from 'react-hot-toast';
5 |
6 | import NoteCard from '../components/notes/NoteCard';
7 | import { fetchNotes } from '../api/ApiCollection';
8 | import { HiOutlineXMark } from 'react-icons/hi2';
9 | // import { allNotes } from '../components/notes/data';
10 |
11 | interface Note {
12 | id: number;
13 | title: string;
14 | body: string;
15 | date: string;
16 | author: string;
17 | topic: string;
18 | }
19 |
20 | const Notes = () => {
21 | const [allNotes, setAllNotes] = React.useState([]);
22 | const [searchQuery, setSearchQuery] = React.useState('');
23 |
24 | const [noteSelected, setNoteSelected] = React.useState(false);
25 | const [selectedCard, setSelectedCard] = React.useState({
26 | title: '',
27 | body: '',
28 | });
29 | const [titleSelected, setTitleSelected] = React.useState('');
30 | const [bodySelected, setBodySelected] = React.useState('');
31 | const [topicSelected, setTopicSelected] = React.useState('');
32 |
33 | const tempTotalEntries = [1, 2, 3, 4, 5, 6, 7];
34 |
35 | const { data, isLoading, isError, isSuccess } = useQuery({
36 | queryKey: ['notes'],
37 | queryFn: fetchNotes,
38 | });
39 |
40 | React.useEffect(() => {
41 | if (isLoading) {
42 | toast.loading('Loading...', { id: 'promiseNotes' });
43 | }
44 | if (isError) {
45 | toast.error('Error while getting the data!', {
46 | id: 'promiseNotes',
47 | });
48 | }
49 | if (isSuccess) {
50 | toast.success('Got the data successfully!', {
51 | id: 'promiseNotes',
52 | });
53 | }
54 | }, [isError, isLoading, isSuccess]);
55 |
56 | React.useEffect(() => {
57 | console.log('Ini datanya:', data);
58 | setAllNotes(data);
59 | // console.log('Ini notes nya:', allNotes);
60 | }, [data]);
61 |
62 | React.useEffect(() => {
63 | const fetchSpesificNote = async () => {
64 | const res = await axios.get(
65 | `https://react-admin-ui-v1-api.vercel.app/notes?q=${searchQuery}`
66 | );
67 | setAllNotes(res.data);
68 | };
69 |
70 | if (searchQuery.length === 0 || searchQuery.length > 2) {
71 | fetchSpesificNote();
72 | }
73 | }, [searchQuery]);
74 |
75 | return (
76 | // screen
77 |
78 | {/* container */}
79 |
80 | {/* grid */}
81 |
82 | {/* column 1 */}
83 |
84 | {/* heading */}
85 |
86 |
87 | Notes
88 |
89 | + Add Note
90 |
91 |
92 | {/* Search Box */}
93 |
94 |
99 | setSearchQuery(e.target.value.toLowerCase())
100 | }
101 | />
102 |
103 |
104 | {/* listed notes */}
105 | {isLoading ? (
106 | tempTotalEntries.map((index: number) => (
107 |
111 | ))
112 | ) : isSuccess ? (
113 | allNotes &&
114 | allNotes.map((note: Note, index: number) => (
115 |
129 | ))
130 | ) : (
131 |
132 | Error while getting the data!
133 |
134 | )}
135 |
136 |
137 | {/* column 2 */}
138 |
139 | {/* content */}
140 | {!noteSelected ? (
141 |
142 |
Please select one note
143 |
144 | ) : (
145 |
146 |
147 | {topicSelected == 'e-commerce' && (
148 |
153 | )}
154 | {topicSelected == 'marketing' && (
155 |
160 | )}
161 | {topicSelected == 'social-media' && (
162 |
167 | )}
168 | {topicSelected == 'SEO' && (
169 |
174 | )}
175 | {topicSelected == 'productivity' && (
176 |
181 | )}
182 | {topicSelected == 'communication' && (
183 |
188 | )}
189 | {topicSelected == '' && (
190 |
195 | )}
196 |
197 |
198 | {titleSelected}
199 |
200 |
201 | {bodySelected}
202 |
203 |
204 | )}
205 |
206 |
207 | {/* mobile only */}
208 |
{
210 | setNoteSelected(false);
211 | setSelectedCard({
212 | title: '',
213 | body: '',
214 | });
215 | }}
216 | className={`w-screen h-screen left-0 bottom-0 fixed z-[99] flex items-end transition-all duration-[2s] bg-black/50
217 | ${
218 | noteSelected
219 | ? 'opacity-100 inline-flex xl:hidden'
220 | : 'opacity-0 hidden'
221 | }`}
222 | >
223 |
227 |
{
229 | setNoteSelected(false);
230 | setSelectedCard({
231 | title: '',
232 | body: '',
233 | });
234 | }}
235 | className="btn btn-circle fixed top-5 right-5 z-[99]"
236 | >
237 |
238 |
239 |
240 | {topicSelected == 'e-commerce' && (
241 |
246 | )}
247 | {topicSelected == 'marketing' && (
248 |
253 | )}
254 | {topicSelected == 'social-media' && (
255 |
260 | )}
261 | {topicSelected == 'SEO' && (
262 |
267 | )}
268 | {topicSelected == 'productivity' && (
269 |
274 | )}
275 | {topicSelected == 'communication' && (
276 |
281 | )}
282 | {topicSelected == '' && (
283 |
288 | )}
289 |
290 |
291 | {titleSelected}
292 |
293 |
294 | {bodySelected}
295 |
296 |
297 |
298 |
299 |
300 | );
301 | };
302 |
303 | export default Notes;
304 |
--------------------------------------------------------------------------------