diff --git a/.env.development b/.env.development index 58ed8bd..f60f11d 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,7 @@ NEXTAUTH_URL=http://localhost:3000 NEXTAUTH_SECRET=e01b7895a403fa7364061b2f01a650fc BACKEND_API_HOST=https://demo.duendesoftware.com +BACKEND_CUSTOM_API_HOST=https://new-dev.accelist.com:1234 OIDC_ISSUER=https://demo.duendesoftware.com OIDC_CLIENT_ID=interactive.public.short OIDC_SCOPE=openid profile email api offline_access diff --git a/appsettings.js b/appsettings.js index ebd662f..604afed 100644 --- a/appsettings.js +++ b/appsettings.js @@ -1,5 +1,6 @@ module.exports = { backendApiHost: process.env['BACKEND_API_HOST'] ?? '', + backendCustomApiHost: process.env['BACKEND_CUSTOM_API_HOST'] ?? '', oidcIssuer: process.env['OIDC_ISSUER'] ?? '', oidcClientId: process.env['OIDC_CLIENT_ID'] ?? '', oidcScope: process.env['OIDC_SCOPE'] ?? '', diff --git a/components/DefautLayout.tsx b/components/DefautLayout.tsx index 973c396..3f04e16 100644 --- a/components/DefautLayout.tsx +++ b/components/DefautLayout.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import Head from 'next/head'; import { Avatar, Button, ConfigProvider, Drawer, Layout, Menu, MenuProps } from "antd"; -import { faBars, faSignOut, faSignIn, faHome, faCubes, faUser, faUsers, faFlaskVial } from '@fortawesome/free-solid-svg-icons' +import { faBars, faSignOut, faSignIn, faHome, faCubes, faUser} from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useRouter } from "next/router"; import { useSession, signIn, signOut } from "next-auth/react"; @@ -29,7 +29,7 @@ const DefaultLayout: React.FC<{ menu.push({ key: '/', - label: 'Home', + label: 'Main Menu', icon: , onClick: () => router.push('/') }); @@ -37,69 +37,9 @@ const DefaultLayout: React.FC<{ menu.push( { key: '#menu-1', - label: 'Menu 1', + label: 'Post', icon: , - children: [ - { - key: '/dashboard', - label: 'Dashboard', - onClick: () => router.push('/dashboard') - }, - { - key: '/sub-menu-b', - label: 'Sub Menu B', - onClick: () => router.push('/') - }, - { - key: '/sub-menu-c', - label: 'Sub Menu C', - onClick: () => router.push('/') - } - ] - }, - { - key: '#menu-2', - label: 'Menu 2', - icon: , - children: [ - { - key: '/sub-menu-d', - label: 'Sub Menu D', - onClick: () => router.push('/') - }, - { - key: '/sub-menu-e', - label: 'Sub Menu E', - onClick: () => router.push('/') - }, - { - key: '/sub-menu-f', - label: 'Sub Menu F', - onClick: () => router.push('/') - } - ] - }, - { - key: '#menu-3', - label: 'Menu 3', - icon: , - children: [ - { - key: '/sub-menu-g', - label: 'Sub Menu G', - onClick: () => router.push('/') - }, - { - key: '/sub-menu-h', - label: 'Sub Menu H', - onClick: () => router.push('/') - }, - { - key: '/sub-menu-i', - label: 'Sub Menu I', - onClick: () => router.push('/') - } - ] + onClick: () => router.push('/post') } ); diff --git a/components/DeleteConfirmation.tsx b/components/DeleteConfirmation.tsx new file mode 100644 index 0000000..f51a73d --- /dev/null +++ b/components/DeleteConfirmation.tsx @@ -0,0 +1,29 @@ +// components/ConfirmationModal.tsx +import React from 'react'; + +interface DeleteConfirmation { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; +} + +const DeleteConfirmation: React.FC = ({ isOpen, onClose, onConfirm }) => { + return ( + <> + {isOpen && ( +
+
+
+

Are you sure you want to delete this order?

+
+ + +
+
+
+ )} + + ); +}; + +export default DeleteConfirmation; diff --git a/package-lock.json b/package-lock.json index 9344003..af1b903 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "nprogress": "0.2.0", "openid-client": "5.4.0", "react": "18.2.0", + "react-datepicker": "^6.9.0", "react-dom": "18.2.0", "react-hook-form": "7.43.9", "swr": "2.1.2", @@ -197,6 +198,54 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dependencies": { + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.12", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.12.tgz", + "integrity": "sha512-D09o62HrWdIkstF2kGekIKAC0/N/Dl6wo3CQsnLcOmO3LkW6Ik8uIb3kw8JYkwxNCcg+uJ2bpWUiIijTBep05w==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@floating-ui/utils": "^0.2.0", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "dependencies": { + "@floating-ui/dom": "^1.6.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", @@ -1471,6 +1520,14 @@ "node": ">=12" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4969,6 +5026,31 @@ "node": ">=0.10.0" } }, + "node_modules/react-datepicker": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-6.9.0.tgz", + "integrity": "sha512-QTxuzeem7BUfVFWv+g5WuvzT0c5BPo+XTCNbMTZKSZQLU+cMMwSUHwspaxuIcDlwNcOH0tiJ+bh1fJ2yxOGYWA==", + "dependencies": { + "@floating-ui/react": "^0.26.2", + "clsx": "^2.1.0", + "date-fns": "^3.3.1", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.13.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" + } + }, + "node_modules/react-datepicker/node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -5001,6 +5083,19 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-onclickoutside": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", + "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==", + "funding": { + "type": "individual", + "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" + }, + "peerDependencies": { + "react": "^15.5.x || ^16.x || ^17.x || ^18.x", + "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -5538,6 +5633,11 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, "node_modules/tailwindcss": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.1.tgz", diff --git a/package.json b/package.json index 3d75a9a..342a427 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "nprogress": "0.2.0", "openid-client": "5.4.0", "react": "18.2.0", + "react-datepicker": "^6.9.0", "react-dom": "18.2.0", "react-hook-form": "7.43.9", "swr": "2.1.2", diff --git a/pages/api/be/[...apiGateway].ts b/pages/api/be/[...apiGateway].ts index 3dea920..f72f420 100644 --- a/pages/api/be/[...apiGateway].ts +++ b/pages/api/be/[...apiGateway].ts @@ -5,7 +5,7 @@ import { AppSettings } from '../../../functions/AppSettings'; // Great way to avoid using CORS and making API calls from HTTPS pages to back-end HTTP servers // Recommendation for projects in Kubernetes cluster: set target to Service DNS name instead of public DNS name const server = Proxy.createProxyServer({ - target: AppSettings.current.backendApiHost, + target: AppSettings.current.backendCustomApiHost, // changeOrigin to support name-based virtual hosting changeOrigin: true, xfwd: true, @@ -23,7 +23,7 @@ server.on('proxyReq', (proxyReq, req) => { } proxyReq.removeHeader('cookie'); // console.log(JSON.stringify(proxyReq.getHeaders(), null, 4)); - console.log('API Proxy:', req.url, '-->', AppSettings.current.backendApiHost + urlRewrite); + console.log('API Proxy:', req.url, '-->', AppSettings.current.backendCustomApiHost + urlRewrite); }); const apiGateway = async (req: NextApiRequest, res: NextApiResponse) => { diff --git a/pages/index.tsx b/pages/index.tsx index 6c0943a..977e6f7 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,15 +1,99 @@ import { WithDefaultLayout } from '../components/DefautLayout'; import { Title } from '../components/Title'; import { Page } from '../types/Page'; +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/router'; +import DeleteConfirmation from '../components/DeleteConfirmation'; -const IndexPage: Page = () => { - return ( -
- Home - Hello World! -
- ); -} - -IndexPage.layout = WithDefaultLayout; -export default IndexPage; +const MainMenu: Page = () => { + const router = useRouter(); + const [showModal, setShowModal] = useState(false); + const [selectedOrderId, setSelectedOrderId] = useState(null); + + // Berikut hanyalah sampel yang ditampilkan. + const [orders, setOrders] = useState([ + { id: 1, name: 'Order 1', from: 'Location A', to: 'Location B', orderedAt: '2024-04-25', quantity: 5 }, + { id: 2, name: 'Order 2', from: 'Location C', to: 'Location D', orderedAt: '2024-04-24', quantity: 3 }, + ]); + + useEffect(() => { + const { newOrder } = router.query; + if (newOrder) { + const parsedNewOrder = JSON.parse(newOrder as string); + const orderExists = orders.some(order => order.id === parsedNewOrder.id); + if (!orderExists) { + setOrders(prevOrders => [...prevOrders, parsedNewOrder]); + } + } +}, [router.query, orders]); + + // Handle untuk navigate ketika button diklik. + const handleView = (id: number) => { + router.push(`/orderdetailmenu?id=${id}`); + }; + + const handleUpdate = (id: number) => { + router.push(`/update?id=${id}`); + }; + + const handleDelete = (id: number) => { + setSelectedOrderId(id); + setShowModal(true); + }; + + const handleCloseModal = () => { + setShowModal(false); + setSelectedOrderId(null); + }; + + const handleConfirmDelete = () => { + const updatedOrders = orders.filter(order => order.id !== selectedOrderId); + setOrders(updatedOrders); + handleCloseModal(); + }; + + return ( +
+ Main Menu +

Main Menu

+ + + + + + + + + + + + + + {orders.map((order, index) => ( + + + + + + + + + + ))} + +
#NameOrder FromOrder ToOrdered AtQuantityAction
{index + 1}{order.name}{order.from}{order.to}{order.orderedAt}{order.quantity} + + + +
+ +
+ ); +}; + +MainMenu.layout = WithDefaultLayout; +export default MainMenu; diff --git a/pages/login.tsx b/pages/login.tsx new file mode 100644 index 0000000..2a75b2b --- /dev/null +++ b/pages/login.tsx @@ -0,0 +1,25 @@ +import { useState } from 'react'; + +const Login: React.FC = () => { + const [emailOrUsername, setEmailOrUsername] = useState(''); + const [password, setPassword] = useState(''); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + }; + + return ( +
+
+

Login

+
+ setEmailOrUsername(e.target.value)} className="input-field mb-4" required /> + setPassword(e.target.value)} className="input-field mb-4" required /> + +
+
+
+ ); +}; + +export default Login; diff --git a/pages/orderdetailmenu.tsx b/pages/orderdetailmenu.tsx new file mode 100644 index 0000000..80e8e26 --- /dev/null +++ b/pages/orderdetailmenu.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { useRouter } from 'next/router'; + +interface OrderDetailProps { + orderId: number; + description: string; + orderFrom: string; + orderTo: string; + orderedAt: string; + quantity: number; +} + +const OrderDetail: React.FC = ({ orderId, description, orderFrom, orderTo, orderedAt, quantity }) => { + const router = useRouter(); + const { id } = router.query; + return ( +
+

Order Detail {id}

+
+
+

Order ID:

+

{orderId}

+
+
+

Description:

+

{description}

+
+
+

Order From:

+

{orderFrom}

+
+
+

Order To:

+

{orderTo}

+
+
+

Ordered At:

+

{orderedAt}

+
+
+

Quantity:

+

{quantity}

+
+
+
+ ); +}; + +export default OrderDetail; diff --git a/pages/post.tsx b/pages/post.tsx new file mode 100644 index 0000000..110d3bf --- /dev/null +++ b/pages/post.tsx @@ -0,0 +1,85 @@ +import { useState } from 'react'; +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; +import { useRouter } from 'next/router'; + +const Post: React.FC = () => { + const router = useRouter(); + const [description, setDescription] = useState(''); + const [orderFrom, setOrderFrom] = useState(''); + const [orderTo, setOrderTo] = useState(''); + const [orderedAt, setOrderedAt] = useState(''); + const [quantity, setQuantity] = useState(undefined); + const [error, setError] = useState(''); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!description || !orderFrom || !orderTo || !orderedAt || !quantity) { + setError('Every field must be inputted!'); + return; + } + if (description.length > 100) { + setError('Description max 100 characters!'); + return; + } + if (quantity < 1 || quantity > 99) { + setError('Item must be between 1 - 99'); + return; + } + const newOrder = { + id: Math.floor(Math.random() * 1000) + 1, + name: `Order ${Math.floor(Math.random() * 1000) + 1}`, + from: orderFrom, + to: orderTo, + orderedAt: orderedAt, + quantity: quantity + }; + + router.push({ + pathname: '/', + query: { newOrder: JSON.stringify(newOrder) } + }); + }; + + + return ( +
+
+

Post Order

+
+
+ +