added CategoriesModal EmojiPickerButton CategorySign AddCategoryModal

updated NavigationBar AnalyticsModal SettingsModal
This commit is contained in:
wt
2025-07-26 23:23:55 +07:00
parent 5aa0caae48
commit e12607cfe5
10 changed files with 346 additions and 4 deletions
+2
View File
@@ -9,6 +9,8 @@
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.3.7",
"chart.js": "^4.5.0",
"classnames": "^2.5.1",
"emoji-picker-react": "^4.13.2",
"faker": "^6.6.6",
"react": "^19.1.0",
"react-bootstrap": "^2.10.10",
@@ -0,0 +1,69 @@
import { useTheme } from "../../contexts/ThemeContext";
import { Button } from "react-bootstrap";
import styles from "./CategorySign.module.css";
import classNames from "classnames";
const CategorySign = ({ icon, title, subcategories }) => {
const { theme } = useTheme();
return (
<Button variant={theme} className="w-100 mt-3">
<div>
<div className="d-flex flex-row gap-3 p-2">
<div
className="p-2 justify-content-start align-items-start d-flex flex-column"
style={{ fontSize: "30px" }}
>
<div>{icon}</div>
</div>
<div className="d-flex flex-column p-0 justify-content-center align-items-start w-100">
<h3
className="mb-0 fw-bolder"
style={{ fontSize: "1.1rem", fontWeight: 700 }}
>
{title}
</h3>
<div
className="text-muted small mb-1"
style={{ fontSize: "0.8rem" }}
>
{subcategories.length === 0
? "No subcategories"
: `Subcategories: ${subcategories.length}`}
</div>
</div>
</div>
{subcategories.length === 0 ? (
""
) : (
<div
className="d-flex align-items-center gap-3 p-0"
style={{ margin: "0 20px" }}
>
<div
className="p-2 justify-content-start align-items-start d-flex flex-column"
// style={{ fontSize: "30px" }}
>
<div>&#10142;</div>
</div>
<div
className={classNames("d-flex gap-3", styles.hideScrollbar)}
style={{ width: "100%", overflowX: "auto" }}
>
{subcategories.map((subcategory) => (
<Button
variant="secondary"
style={{ borderRadius: "25px", padding: "0 15px" }}
key={subcategory.id}
>
{subcategory.name}
</Button>
))}
</div>
</div>
)}
</div>
</Button>
);
};
export default CategorySign;
@@ -0,0 +1,7 @@
.hideScrollbar {
scrollbar-width: none;
-ms-overflow-style: none;
}
.hideScrollbar::-webkit-scrollbar {
display: none;
}
@@ -0,0 +1,58 @@
import React, { useState, useRef } from "react";
import { Button, Overlay, Popover, InputGroup } from "react-bootstrap";
import EmojiPicker from "emoji-picker-react";
import { useTheme } from "../../contexts/ThemeContext";
const EmojiPickerButton = () => {
const { theme } = useTheme();
const [showPicker, setShowPicker] = useState(false);
const [inputValue, setInputValue] = useState("😀");
const target = useRef(null);
const handleEmojiSelect = (emoji) => {
setInputValue(emoji.emoji);
setShowPicker(false);
};
return (
<div className="p-3">
<InputGroup>
<Button
ref={target}
variant="secondary"
style={{
borderRadius: "50%",
width: "100px",
height: "100px",
fontSize: "45px",
}}
onClick={() => setShowPicker(!showPicker)}
>
{inputValue}
</Button>
<Overlay
show={showPicker}
target={target.current}
placement="bottom"
rootClose
onHide={() => setShowPicker(false)}
>
<Popover id="emoji-popover">
<Popover.Body className="p-0">
<EmojiPicker
onEmojiClick={handleEmojiSelect}
height={350}
width="100%"
previewConfig={{ showPreview: false }}
theme={theme}
/>
</Popover.Body>
</Popover>
</Overlay>
</InputGroup>
</div>
);
};
export default EmojiPickerButton;
@@ -4,14 +4,14 @@ import SettingsModal from "../../modals/SettingsModal/SettingsModal";
const NavigationBar = () => {
return (
<Navbar
<div
expand="lg"
className="justify-content-between"
className="d-flex align-items-center justify-content-between"
style={{ padding: "1rem 1rem 0 1rem" }}
>
<Navbar.Brand href="/">Wallet</Navbar.Brand>
<SettingsModal />
</Navbar>
</div>
);
};
@@ -0,0 +1,70 @@
import { useState } from "react";
import { useTheme } from "../../contexts/ThemeContext";
import { Modal, Button, Card, Form } from "react-bootstrap";
import EmojiPickerButton from "../../components/EmojiPickerButton/EmojiPickerButton";
const AddCategoryModal = ({ variant, ...props }) => {
const { theme } = useTheme();
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const [title, setTitle] = useState("");
return (
<>
<Button variant={theme} onClick={handleShow} className="m-0" {...props}>
New Category
</Button>
<Modal show={show} onHide={handleClose} fullscreen={true}>
<Modal.Header className="d-flex align-items-center justify-content-between">
<spacer />
<Modal.Title>New Category</Modal.Title>
<Button
variant={theme}
type="button"
onClick={handleClose}
style={{
borderRadius: "50%",
width: "30px",
height: "30px",
padding: "0",
}}
>
&#10005;
</Button>
</Modal.Header>
<Modal.Body className="p-3">
<div className="d-flex align-items-center justify-content-center">
<EmojiPickerButton />
</div>
<Form.Control
type="text"
placeholder="Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
className="mb-3"
/>
<Card>
<Card.Body>
<div
className="text-muted small mb-1"
style={{ fontSize: "0.8rem" }}
>
SUBCATEGORIES
</div>
<Button
variant={"secondary"}
onClick={() => {}}
className="w-100 text-start mb-3"
>
Add subcategory
</Button>
<spacer />
<Button className="w-100">Add</Button>
</Card.Body>
</Card>
</Modal.Body>
</Modal>
</>
);
};
export default AddCategoryModal;
+1 -1
View File
@@ -145,7 +145,7 @@ const AnalyticsModal = () => {
<spacer />
</Modal.Header>
<Modal.Body style={{ paddingLeft: "0", paddingRight: "0" }}>
<Tabs defaultActiveKey="Expenses" id="history-tabs">
<Tabs defaultActiveKey="Expenses" id="analytics-tabs">
<Tab eventKey="Incomes" title="Incomes"></Tab>
<Tab eventKey="Expenses" title="Expenses" style={{ padding: "0" }}>
<div className="d-flex mt-2 justify-content-center">
@@ -0,0 +1,113 @@
import { useState } from "react";
import { useTheme } from "../../contexts/ThemeContext";
import {
Modal,
Button,
Form,
InputGroup,
Tabs,
Tab,
Dropdown,
DropdownButton,
} from "react-bootstrap";
import styles from "./CategoriesModal.module.css";
import CategorySign from "../../components/CategorySign/CategorySign";
import AddCategoryModal from "../../modals/AddCategoryModal/AddCategoryModal";
const CategoriesModal = ({ variant }) => {
const { theme } = useTheme();
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Button
variant={theme}
onClick={handleShow}
className="w-100 d-flex justify-content-between"
>
<div>&copy; Categories</div>
<div>&#10095;</div>
</Button>
<Modal show={show} onHide={handleClose} fullscreen={true}>
<Modal.Header>
<div className="d-flex flex-column w-100 gap-2">
<div className="d-flex align-items-center justify-content-between">
<Button
variant={theme}
onClick={handleClose}
className="d-flex justify-content-between gap-2"
>
<div style={{ transform: "rotate(180deg)" }}>&#10095;</div>
<div>Settings</div>
</Button>
<Modal.Title>Categories</Modal.Title>
<spacer />
<DropdownButton title="&#9776;" variant={theme} drop="start">
<Dropdown.Item className="p-0">
<AddCategoryModal
className="w-100"
style={{ paddingLeft: "0" }}
/>
</Dropdown.Item>
<Dropdown.Item>Reorder</Dropdown.Item>
</DropdownButton>
</div>
<InputGroup className="mb-3">
<InputGroup.Text id="basic-addon1">
<div style={{ transform: "rotate(90deg)" }}>&#9740;</div>
</InputGroup.Text>
<Form.Control
placeholder="Search"
aria-label="Search"
aria-describedby="basic-addon1"
/>
</InputGroup>
</div>
</Modal.Header>
<Modal.Body className="p-0">
<Tabs
defaultActiveKey="Expenses"
id="categories-tabs"
className="nav nav-tabs"
>
<Tab eventKey="Incomes" title="Incomes">
asdf
</Tab>
<Tab eventKey="Expenses" title="Expenses">
<div
className={styles.scrollableContent}
style={{ padding: "10px" }}
>
<CategorySign
icon="&#128512;"
title="Cash Loan"
subcategories={[]}
/>
<CategorySign
icon="&#128512;"
title="Cash Loan"
subcategories={[
{ id: 0, name: "Tbank" },
{ id: 1, name: "Sber" },
{ id: 2, name: "VTB" },
{ id: 0, name: "Tbank" },
{ id: 1, name: "Sber" },
{ id: 2, name: "VTB" },
{ id: 0, name: "Tbank" },
{ id: 1, name: "Sber" },
{ id: 2, name: "VTB" },
{ id: 0, name: "Tbank" },
{ id: 1, name: "Sber" },
{ id: 2, name: "VTB" },
]}
/>
</div>
</Tab>
</Tabs>
</Modal.Body>
</Modal>
</>
);
};
export default CategoriesModal;
@@ -0,0 +1,11 @@
.scrollableContent {
height: calc(100vh - 220px);
bottom: 0;
overflow-y: auto;
}
@media (max-width: 768px) {
.scrollableContent {
height: calc(100vh - 280px);
}
}
@@ -2,6 +2,7 @@ import { useState } from "react";
import { useTheme } from "../../contexts/ThemeContext";
import { ListGroup, Modal, Button } from "react-bootstrap";
import ThemeToggle from "../../components/ThemeToggle";
import CategoriesModal from "../../modals/CategoriesModal/CategoriesModal";
const SettingsModal = () => {
const { theme } = useTheme();
@@ -39,6 +40,17 @@ const SettingsModal = () => {
</Button>
</Modal.Header>
<Modal.Body>
<h3 className="text-muted small m-1" style={{ fontSize: "0.8rem" }}>
DATA
</h3>
<ListGroup>
<ListGroup.Item className="p-0">
<CategoriesModal />
</ListGroup.Item>
</ListGroup>
<h3 className="text-muted small m-1" style={{ fontSize: "0.8rem" }}>
APPLICATION
</h3>
<ListGroup>
<ListGroup.Item>
<ThemeToggle />