diff --git a/src/components/BarChart.js b/src/components/BarChart.js index 86ec90e..264cdf7 100644 --- a/src/components/BarChart.js +++ b/src/components/BarChart.js @@ -98,32 +98,46 @@ const SimpleBarChart = ({ Data }) => { const month = formattedDate.toLocaleString('en-US', { month: 'short' }); // Get the abbreviated month (e.g., "Nov") const year = formattedDate.getFullYear().toString().slice(2); // Get the last two digits of the year (e.g., "24") - // Extract the month and year from the date - const currentMonth = formattedDate.toLocaleDateString('en-US', { month: 'short' }); - const currentYear = formattedDate.getFullYear(); + // Extract the year from the first data point + const firstYear = new Date(sortedData[0]?.date).getFullYear(); + + // Check if all dates have the same year + const allSameYear = sortedData.every((item) => new Date(item.date).getFullYear() === firstYear); + + // If all dates are from the same year, show only "day month" + if (allSameYear && formattedDate.toLocaleString('en-US', { month: 'short' }) !== lastMonthRef.current) { + lastMonthRef.current = formattedDate.toLocaleString('en-US', { month: 'short' }); + // Show just the day and month if all dates have the same year + return `${day} ${month}`; + } + // if (allSameYear) { + // // Show just the day and month if all dates have the same year + // return `${day} ${month}`; + // } // Check if it's the oldest date (first entry) if (date === oldestDate) { - lastMonthRef.current = currentMonth; // Update lastMonthRef for the first entry - lastYearRef.current = currentYear; // Update lastYearRef for the first entry + lastMonthRef.current = formattedDate.toLocaleDateString('en-US', { month: 'short' }); + lastYearRef.current = formattedDate.getFullYear(); return `${day} ${month} ${year}`; // Format as "day month year" (e.g., "4 Nov 24") } // If the year changes, show the full date with year - if (currentYear !== lastYearRef.current) { - lastYearRef.current = currentYear; // Update year reference + if (formattedDate.getFullYear() !== lastYearRef.current) { + lastYearRef.current = formattedDate.getFullYear(); return `${day} ${month} ${year}`; // Show full date: day month year } // If the month changes, show the full date with month and year - if (currentMonth !== lastMonthRef.current) { - lastMonthRef.current = currentMonth; // Update month reference - return `${day} ${month} ${year}`; // Show full date: day month year + if (formattedDate.toLocaleString('en-US', { month: 'short' }) !== lastMonthRef.current) { + lastMonthRef.current = formattedDate.toLocaleString('en-US', { month: 'short' }); + return `${day} ${month} `; // Show full date: day month year } // Only show the day if the month hasn't changed return `${day}`; // Show just the day if the month remains the same }; + // Function to get the next available color from the palette, starting from a random index const getNextColor = () => { @@ -158,8 +172,11 @@ const SimpleBarChart = ({ Data }) => { interval={0} // Set interval to 0 to display all dates in data ticks={uniqueDates} // Only use unique dates for the XAxis /> - - + {/* Dynamically create bars and labels for each unique material */} @@ -168,7 +185,12 @@ const SimpleBarChart = ({ Data }) => { - ))} + ))} diff --git a/src/components/Modal.js b/src/components/Modal.js index e566e0b..5434aab 100644 --- a/src/components/Modal.js +++ b/src/components/Modal.js @@ -1,6 +1,8 @@ import React from "react"; import styles from "./Modal.module.css"; import CreateClerk from "../pages/CreateClerk" +import CreateCafe from "../pages/CreateCafe" +import CreateTenant from "../pages/CreateTenant" import TablesPage from "./TablesPage.js"; import PaymentOptions from "./PaymentOptions.js"; import TableMaps from "../components/TableMaps"; @@ -43,6 +45,8 @@ const Modal = ({ shop, isOpen, onClose, modalContent, setModal }) => { {modalContent === "req_notification" && } {modalContent === "blocked_notification" && } {modalContent === "create_clerk" && } + {modalContent === "create_kedai" && } + {modalContent === "create_tenant" && } {modalContent === "edit_tables" && } {modalContent === "new_transaction" && ( diff --git a/src/pages/CreateCafe.js b/src/pages/CreateCafe.js new file mode 100644 index 0000000..361f2c2 --- /dev/null +++ b/src/pages/CreateCafe.js @@ -0,0 +1,113 @@ +import React, { useState } from 'react'; +import { createClerks } from '../helpers/userHelpers'; // Adjust the import path as needed + +const CreateClerk = ({ shopId }) => { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + + const handleSubmit = async (event) => { + event.preventDefault(); + setLoading(true); + setMessage(''); + + // Basic validation + if (!username || !password) { + setMessage('Username and password are required'); + setLoading(false); + return; + } + + try { + const create = await createClerks(shopId, username, password); + + if (create) setMessage('Clerk created successfully'); + else setMessage('Failed to create clerk'); + } catch (error) { + setMessage('Error creating clerk'); + } finally { + setLoading(false); + } + }; + + return ( +
+

Tambah Kedai

+
+ setUsername(e.target.value)} + style={styles.input} + /> + setPassword(e.target.value)} + style={styles.input} + /> + + {message && ( +

+ {message} +

+ )} +
+
+ ); +}; + +// Basic styling to make it mobile-friendly with a white background +const styles = { + container: { + backgroundColor: '#fff', + width: '100%', + maxWidth: '350px', + margin: '0 auto', + padding: '20px', + boxShadow: '0 4px 10px rgba(0, 0, 0, 0.1)', + borderRadius: '8px', + boxSizing: 'border-box', + }, + header: { + textAlign: 'center', + marginBottom: '20px', + fontSize: '20px', + color: '#333', + }, + form: { + display: 'flex', + flexDirection: 'column', + gap: '15px', + }, + input: { + padding: '12px', + fontSize: '16px', + borderRadius: '8px', + border: '1px solid #ccc', + width: '100%', + boxSizing: 'border-box', + backgroundColor: '#f9f9f9', + }, + button: { + padding: '12px', + fontSize: '16px', + borderRadius: '8px', + border: 'none', + backgroundColor: '#28a745', + color: 'white', + cursor: 'pointer', + width: '100%', + }, + message: { + textAlign: 'center', + marginTop: '10px', + }, +}; + +export default CreateClerk; diff --git a/src/pages/CreateClerk.js b/src/pages/CreateClerk.js index 1553a30..297da62 100644 --- a/src/pages/CreateClerk.js +++ b/src/pages/CreateClerk.js @@ -33,7 +33,7 @@ const CreateClerk = ({ shopId }) => { return (
-

Create Clerk

+

Tambah Kasir

{ + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(''); + + const handleSubmit = async (event) => { + event.preventDefault(); + setLoading(true); + setMessage(''); + + // Basic validation + if (!username || !password) { + setMessage('Username and password are required'); + setLoading(false); + return; + } + + try { + const create = await createClerks(shopId, username, password); + + if (create) setMessage('Clerk created successfully'); + else setMessage('Failed to create clerk'); + } catch (error) { + setMessage('Error creating clerk'); + } finally { + setLoading(false); + } + }; + + return ( +
+

Tambah Kedai

+ + setUsername(e.target.value)} + style={styles.input} + /> + setPassword(e.target.value)} + style={styles.input} + /> + + {message && ( +

+ {message} +

+ )} + +
+ ); +}; + +// Basic styling to make it mobile-friendly with a white background +const styles = { + container: { + backgroundColor: '#fff', + width: '100%', + maxWidth: '350px', + margin: '0 auto', + padding: '20px', + boxShadow: '0 4px 10px rgba(0, 0, 0, 0.1)', + borderRadius: '8px', + boxSizing: 'border-box', + }, + header: { + textAlign: 'center', + marginBottom: '20px', + fontSize: '20px', + color: '#333', + }, + form: { + display: 'flex', + flexDirection: 'column', + gap: '15px', + }, + input: { + padding: '12px', + fontSize: '16px', + borderRadius: '8px', + border: '1px solid #ccc', + width: '100%', + boxSizing: 'border-box', + backgroundColor: '#f9f9f9', + }, + button: { + padding: '12px', + fontSize: '16px', + borderRadius: '8px', + border: 'none', + backgroundColor: '#28a745', + color: 'white', + cursor: 'pointer', + width: '100%', + }, + message: { + textAlign: 'center', + marginTop: '10px', + }, +}; + +export default CreateClerk; diff --git a/src/pages/Dashboard.js b/src/pages/Dashboard.js index 7383f92..42ffc65 100644 --- a/src/pages/Dashboard.js +++ b/src/pages/Dashboard.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useLocation } from "react-router-dom"; import styles from './LinktreePage.module.css'; import { loginUser, getAnalytics, createCafeOwner } from "../helpers/userHelpers"; import { getOwnedCafes, createCafe, updateCafe } from "../helpers/cafeHelpers"; @@ -14,6 +14,9 @@ import BarChart from '../components/BarChart'; const LinktreePage = ({ user, setModal }) => { const navigate = useNavigate(); + const location = useLocation(); + const [lastModal, setLastModal] = useState(false); + const [inputtingPassword, setInputtingPassword] = useState(false); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); @@ -23,15 +26,22 @@ const LinktreePage = ({ user, setModal }) => { const [isCreating, setIsCreating] = useState(false); const [newItem, setNewItem] = useState({ name: "", type: "" }); const [isModalOpen, setIsModalOpen] = useState(false); - const [expanded, setIsExpand] = useState(false); - const [expandedCafeId, setExpandedCafeId] = useState(null); const [selectedItemId, setSelectedItemId] = useState(0); const [selectedSubItemId, setSelectedSubItemId] = useState(0); - // Handle expand/collapse of cafe details - const handleToggleExpand = (cafeId) => { - setExpandedCafeId(expandedCafeId === cafeId ? null : cafeId); - }; + + useEffect(() => { + const urlParams = new URLSearchParams(location.search); + const modalParam = urlParams.get('modal'); + if (lastModal && !modalParam) { + if (selectedItemId == -1) setSelectedItemId(0); + else if (selectedSubItemId == -1) setSelectedSubItemId(0); + } + if (modalParam) { + setLastModal(modalParam); + } + + }, [location]); // Handle user transactions const handleMyTransactions = async () => { @@ -136,268 +146,86 @@ const LinktreePage = ({ user, setModal }) => { if (amount != null) return amount.toString(); } }; + const colors = [ // Complementary (for contrast with olive green) "#FF6347", // Tomato red (complementary to olive green) "#FF4500", // Orange red (complementary to olive green) - + // Analogous to olive green "#D0E14F", // Light green-yellow "#A9C96E", // Muted olive green (your bg color itself) "#A5B24F", // Earthy olive green - - + + // Triadic (balanced and vibrant palette) "#FF00FF", // Magenta (triadic color) "#1E90FF", // Dodger blue (triadic color) "#32CD32", // Lime green (triadic color) - + // Neutral tones "#FFDAB9", // Peach (light neutral tone) "#4B0082", // Indigo (dark neutral tone) "#8B4513", // Saddle brown (earthy neutral) ]; - + const selectedItems = items.items?.find(item => (item.userId || item.cafeId) === selectedItemId); // If the selected tenant is found, extract the cafes const selectedSubItems = selectedItems?.subItems || []; // 1. Optionally combine all report items from cafes of the selected tenant - const allSelectedSubItems = selectedSubItems.flatMap(cafe => cafe.report?.items || selectedItems.report.items || []); + let allSelectedSubItems = null; + if (user.roleId == 1) + allSelectedSubItems = selectedSubItems?.flatMap(cafe => cafe.report?.items || selectedItems.report.items || []); // 2. Retrieve the specific cafe's report items if needed - const filteredItems = selectedSubItems.find(cafe => cafe.cafeId == selectedSubItemId) || { report: { items: [] } }; + const filteredItems = selectedSubItems?.find(cafe => cafe.cafeId == selectedSubItemId) || { report: { items: [] } }; // 3. Decide whether to use combined items or individual cafe items - const segments = (selectedItemId != 0 && selectedItemId != -1 && selectedSubItemId == 0 ? allSelectedSubItems : selectedItemId != 0 && selectedItemId != -1 ? filteredItems.report?.items || [] : items?.items || []).map((item, index) => ({ - percentage: item.percentage || (items.totalIncome / item.totalIncome || items.totalIncome / item.report.totalIncome) * 100, - value: item.username || item.name, - color: (colors && colors[index]) || "#cccccc", // Safe check for colors array - })) || []; // Ensure segments is an empty array if no items are availabled + let segments = []; + + if (user.roleId == 1 || user.roleId == 0 && selectedItemId == 0) + segments = (selectedItemId != 0 && selectedItemId != -1 && selectedSubItemId == 0 ? allSelectedSubItems : selectedItemId != 0 && selectedItemId != -1 ? filteredItems.report?.items || [] : items?.items || []).map((item, index) => ({ + percentage: item.percentage || (items.totalIncome / item.totalIncome || items.totalIncome / item.report.totalIncome) * 100, + value: item.username || item.name, + color: (colors && colors[index]) || "#cccccc", // Safe check for colors array + })) || []; // Ensure segments is an empty array if no items are availabled + // Function to combine items of all cafes for the selected tenant console.log(selectedItems) - function combineSelectedTenantCafeReports(selectedItems) { - return selectedItems.cafes.flatMap(cafe => cafe.report?.items || []); - } + console.log(selectedItemId) +// Check if items and items.items are defined before proceeding +const allMaterials = (items?.items || []).flatMap(item => item.report?.materialsPurchased || []); + +// Sort the merged array by date if it's not empty +const sortedMaterials = allMaterials.sort((a, b) => new Date(a.date) - new Date(b.date)); + + return ( <> {user && user.roleId < 2 ? ( <> -
+
-
-
+
+
-
setIsModalOpen(true)} - isLogout={handleLogout} - user={user} - showProfile={true} - setModal={setModal} - HeaderMargin='0px' - /> -
-
-
- {user.roleId == 0 ? ( - selectedItemId == 0 || selectedItemId == -1 ? ( -
-
- - - - - - - - - - -

Total pemasukan

-

{items?.totalIncome}

-
-
- - - - - - - - - - -

Total keuntungan

-

{items?.totalIncome * 0.02}

-
-
- ) : ( -
-
- - - - - - - - - - - -

{selectedItems?.totalIncome}

-

pemasukan

-
-
- - - - - - - - - - -

{selectedItems?.totalOutcome}

-

pengeluaran

-
-
- -

{filteredItems.name}

-
-
- ) - ) : ( - selectedItemId == 0 || selectedItemId == -1 ? - ( +
setIsModalOpen(true)} + isLogout={handleLogout} + user={user} + showProfile={true} + setModal={setModal} + HeaderMargin='0px' + /> +
+
+
+ {user.roleId == 0 ? ( + selectedItemId == 0 || selectedItemId == -1 ? (
{

Total pemasukan

-

{formatIncome(items?.totalIncome)}

+

{items?.totalIncome}

{ 1 -205 -24 -27 -69 -23 -89 7 -20 31 -17 172 5 198 17 22 59 22 83 0z"/> -

Total pengeluaran

-

{formatIncome(items?.totalOutcome)}

+

Total keuntungan

+

{items?.totalIncome * 0.02}

) : (
-
- - - - - - - - - - - -

{formatIncome(selectedItems?.report.totalIncome)}

-

pemasukan

-
+ @@ -576,115 +360,381 @@ const LinktreePage = ({ user, setModal }) => { 1 -205 -24 -27 -69 -23 -89 7 -20 31 -17 172 5 198 17 22 59 22 83 0z"/> -

{formatIncome(selectedItems?.report.currentOutcome)}

-

pengeluaran

+

{selectedItems?.totalIncome}

+

pemasukann

+
+
+ + + + + + + + + + +

{selectedItems?.totalIncome * 0.02}

+

tagihan

-

{selectedItems.name}

+

{filteredItems.name}

) - )} + ) : ( + selectedItemId == 0 || selectedItemId == -1 ? + ( +
+
+ -
-
+ + + + + + + + +

Total pemasukan

+

{formatIncome(items?.totalIncome)}

+
+
+ -
-
- + + + + + + + + +

Total pengeluaran

+

{formatIncome(items?.totalOutcome)}

+
+
+ -

terlakuu

-
-
- -
-
- {segments && segments.map((item, index) => ( -
-
- ★ -
-
{item.percentage == 'Infinity' || isNaN(item.percentage) ? 0 : item.percentage}%   {item.value}
-
- ))} - {segments.length < 1 && - <> -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ + + + + + + + +

Total tanggungan

+

{formatIncome(items?.totalIncome * 0.02)}

+
+
+ ) : ( +
+
- - } + + + + + + + + + + +

{formatIncome(selectedItems?.report.totalIncome)}

+

pemasukan

+
+
+ + + + + + + + + + +

{formatIncome(selectedItems?.report.currentOutcome)}

+

pengeluaran

+
+
+ +

{selectedItems?.name}

+
+
+ ) + )} + +
-

penambahan stok

- -
-
+
+ -
+

terlakuu

+
+
+ +
+
+ {segments && segments.map((item, index) => ( +
+
+ ★ +
+
{item.percentage == 'Infinity' || isNaN(item.percentage) ? 0 : item.percentage}%   {item.value}
+
+ ))} + {segments.length < 1 && + <> + + {Array.from({ length: 5 }).map((_, index) => ( +
+
+ - +
+
+ ))} + + + } +
+ +
+

penambahan stok

+ +
+
+ +
Semua {user.roleId < 1 ? 'penyewa' : 'kedai yang dikau miliki'} @@ -695,7 +745,10 @@ const LinktreePage = ({ user, setModal }) => { {user.roleId < 1 &&
{ setSelectedItemId(selectedItemId == -1 ? 0 : -1); setModal('add-tenant') }} + onClick={() => { + setSelectedItemId(selectedItemId === -1 ? 0 : -1); + setModal('create_tenant'); + }} > Tambah penyewa
@@ -746,9 +799,8 @@ const LinktreePage = ({ user, setModal }) => { {selectedItemId == item.cafeId &&
{ - }} - style={{}} + onClick={() => { setSelectedSubItemId(selectedSubItemId == -1 ? 0 : -1); setModal('create_clerk') }} + style={{ backgroundColor: selectedItemId == item.cafeId && selectedSubItemId == -1 ? 'rgb(69, 69, 69)' : 'rgb(114, 114, 114)' }} > tambah kasir
@@ -762,9 +814,8 @@ const LinktreePage = ({ user, setModal }) => { } {user.roleId > 0 &&
setSelectedItemId(selectedItemId == -1 ? 0 : -1)} + onClick={() => { setSelectedItemId(selectedItemId == -1 ? 0 : -1); setModal('create_kedai') }} > Tambah kedai
diff --git a/src/pages/MaterialList.js b/src/pages/MaterialList.js index 8b3a4b5..5081646 100644 --- a/src/pages/MaterialList.js +++ b/src/pages/MaterialList.js @@ -422,6 +422,7 @@ const MaterialList = ({ cafeId, handleClose }) => {
)}
Kembali
+
); }; @@ -433,7 +434,8 @@ const styles = { width: '100vw', top: 0, right: 0, - backgroundColor: 'rgb(207, 207, 207)' + backgroundColor: 'rgb(207, 207, 207)', + overflowY: 'auto' }, heading: { textAlign: "center",