added CategoriesModal EmojiPickerButton CategorySign AddCategoryModal
updated NavigationBar AnalyticsModal SettingsModal
This commit is contained in:
1 parent
5aa0caae48
commit
e12607cfe5
10 files changed
+346
-4
No files matched your search
@@ -9,6 +9,8 @@
|
|||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"bootstrap": "^5.3.7",
|
"bootstrap": "^5.3.7",
|
||||||
"chart.js": "^4.5.0",
|
"chart.js": "^4.5.0",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
|
"emoji-picker-react": "^4.13.2",
|
||||||
"faker": "^6.6.6",
|
"faker": "^6.6.6",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-bootstrap": "^2.10.10",
|
"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>➞</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 = () => {
|
const NavigationBar = () => {
|
||||||
return (
|
return (
|
||||||
<Navbar
|
<div
|
||||||
expand="lg"
|
expand="lg"
|
||||||
className="justify-content-between"
|
className="d-flex align-items-center justify-content-between"
|
||||||
style={{ padding: "1rem 1rem 0 1rem" }}
|
style={{ padding: "1rem 1rem 0 1rem" }}
|
||||||
>
|
>
|
||||||
<Navbar.Brand href="/">Wallet</Navbar.Brand>
|
<Navbar.Brand href="/">Wallet</Navbar.Brand>
|
||||||
<SettingsModal />
|
<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",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</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;
|
||||||
@@ -145,7 +145,7 @@ const AnalyticsModal = () => {
|
|||||||
<spacer />
|
<spacer />
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body style={{ paddingLeft: "0", paddingRight: "0" }}>
|
<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="Incomes" title="Incomes"></Tab>
|
||||||
<Tab eventKey="Expenses" title="Expenses" style={{ padding: "0" }}>
|
<Tab eventKey="Expenses" title="Expenses" style={{ padding: "0" }}>
|
||||||
<div className="d-flex mt-2 justify-content-center">
|
<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>© Categories</div>
|
||||||
|
<div>❯</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)" }}>❯</div>
|
||||||
|
<div>Settings</div>
|
||||||
|
</Button>
|
||||||
|
<Modal.Title>Categories</Modal.Title>
|
||||||
|
<spacer />
|
||||||
|
<DropdownButton title="☰" 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)" }}>☌</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="😀"
|
||||||
|
title="Cash Loan"
|
||||||
|
subcategories={[]}
|
||||||
|
/>
|
||||||
|
<CategorySign
|
||||||
|
icon="😀"
|
||||||
|
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 { useTheme } from "../../contexts/ThemeContext";
|
||||||
import { ListGroup, Modal, Button } from "react-bootstrap";
|
import { ListGroup, Modal, Button } from "react-bootstrap";
|
||||||
import ThemeToggle from "../../components/ThemeToggle";
|
import ThemeToggle from "../../components/ThemeToggle";
|
||||||
|
import CategoriesModal from "../../modals/CategoriesModal/CategoriesModal";
|
||||||
|
|
||||||
const SettingsModal = () => {
|
const SettingsModal = () => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
@@ -39,6 +40,17 @@ const SettingsModal = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<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>
|
||||||
<ListGroup.Item>
|
<ListGroup.Item>
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
|
|||||||
Reference in new issue
Block a user