Skip to content

Commit

Permalink
✨ カテゴリーを再分類できる機能を追加
Browse files Browse the repository at this point in the history
  • Loading branch information
Irori235 committed Jun 1, 2023
1 parent 79f89be commit aaaf07b
Show file tree
Hide file tree
Showing 6 changed files with 280 additions and 60 deletions.
69 changes: 26 additions & 43 deletions src/components/AddItemForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Input from "./foundation/Input/Input";
import Button from "./foundation/Button/Button";
import Toast from "./foundation/Toast/Toast";
import { Transition } from "@headlessui/react";
import Modal from "./foundation/Modal/Modal";

interface AddItemFormProps {
budget: Budget | null;
Expand Down Expand Up @@ -95,50 +96,32 @@ const AddItemForm: React.FC<AddItemFormProps> = ({
</svg>
</Button>

<Transition
show={isFormVisible}
enter="transition-opacity duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed top-0 left-0 w-full h-full flex items-center justify-center">
<div className="bg-black bg-opacity-50 w-full h-full absolute"></div>
<div className=" bg-white w-2/3 md:max-w-md mx-auto rounded shadow-lg z-50 overflow-y-auto relative">
<div className="flex justify-end items-center">
<button className="w-6" onClick={() => setIsFormVisible(false)}>
x
</button>
</div>
<Toast show={showToast} message="already exists" type="error" />
<div className="flex flex-col gap-4 p-4">
<label className="flex justify-between">
<span>Category</span>
<Input
value={categoryName}
onChange={(e) => setCategoryName(e.target.value)}
/>
</label>
<label className="flex justify-between">
<span>Name</span>
<Input
value={itemName}
onChange={(e) => setItemName(e.target.value)}
/>
</label>
<label className="flex justify-between">
<span>Cost</span>
<Input value={cost} onChange={(e) => setCost(e.target.value)} />
</label>
<Button onClick={onClickAdd} color="black">
Add
</Button>
</div>
</div>
<Modal isOpen={isFormVisible} setIsOpen={setIsFormVisible}>
<Toast show={showToast} message="already exists" type="error" />
<div className="flex flex-col gap-4 p-4">
<label className="flex justify-between">
<span>Category</span>
<Input
value={categoryName}
onChange={(e) => setCategoryName(e.target.value)}
/>
</label>
<label className="flex justify-between">
<span>Name</span>
<Input
value={itemName}
onChange={(e) => setItemName(e.target.value)}
/>
</label>
<label className="flex justify-between">
<span>Cost</span>
<Input value={cost} onChange={(e) => setCost(e.target.value)} />
</label>
<Button onClick={onClickAdd} color="black">
Add
</Button>
</div>
</Transition>
</Modal>
</div>
);
};
Expand Down
44 changes: 44 additions & 0 deletions src/components/EditAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { useState } from "react";
import Dropdown from "./foundation/Dropdown/Dropdown";
import Modal from "./foundation/Modal/Modal";
import Input from "./foundation/Input/Input";
import Button from "./foundation/Button/Button";
import Toast from "./foundation/Toast/Toast";

import RenameCategory from "./RenameCategory";

interface EditActionProps {
handleRenameCategory: (newCategoryName: string) => void;
handleDeleteItems: () => void;
}

const EditAction: React.FC<EditActionProps> = ({
handleRenameCategory,
handleDeleteItems,
}) => {
const [isModalOpen, setIsModalOpen] = useState(false);

const dropdownOptions = [
{
label: "Rename",
action: () => setIsModalOpen(true),
},
{
label: "Delete",
action: handleDeleteItems,
},
];

return (
<>
<Dropdown options={dropdownOptions} />
<RenameCategory
isOpen={isModalOpen}
setIsOpen={setIsModalOpen}
handleRenameCategory={handleRenameCategory}
/>
</>
);
};

export default EditAction;
58 changes: 58 additions & 0 deletions src/components/RenameCategory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState } from "react";
import Dropdown from "./foundation/Dropdown/Dropdown";
import Modal from "./foundation/Modal/Modal";
import Input from "./foundation/Input/Input";
import Button from "./foundation/Button/Button";
import Toast from "./foundation/Toast/Toast";

interface RenameCategoryProps {
isOpen: boolean;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
handleRenameCategory: (newCategoryName: string) => void;
}

const RenameCategory: React.FC<RenameCategoryProps> = ({
isOpen,
setIsOpen,
handleRenameCategory,
}) => {
const [categoryName, setCategoryName] = useState("");
const [showToast, setShowToast] = useState<boolean>(false);

const triggerToast = () => {
setShowToast(true);
setTimeout(() => {
setShowToast(false);
}, 3000);
};

const onClickUpdateCategory = () => {
if (categoryName) {
handleRenameCategory(categoryName);
setCategoryName("");
setIsOpen(false);
} else {
triggerToast();
}
};

return (
<Modal isOpen={isOpen} setIsOpen={setIsOpen}>
<Toast show={showToast} message="Invalid name" type="error" />
<div className="flex flex-col gap-4 p-4">
<label className="flex justify-between">
<span>NewCategory</span>
<Input
value={categoryName}
onChange={(e) => setCategoryName(e.target.value)}
/>
</label>
<Button onClick={onClickUpdateCategory} color="black">
Update
</Button>
</div>
</Modal>
);
};

export default RenameCategory;
56 changes: 56 additions & 0 deletions src/components/foundation/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from "react";
import { Menu } from "@headlessui/react";

interface DropdownProps {
options: { label: string; action: () => void }[];
}

const Dropdown: React.FC<DropdownProps> = ({ options }) => {
return (
<div className="shadow-sm">
<Menu>
{({ open }) => (
<>
<Menu.Button className="px-3 py-3 rounded-lg shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 bg-white hover:bg-gray-200 border-black text-black">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-4 h-4"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
/>
</svg>
</Menu.Button>
{open && (
<Menu.Items className="absolute w-24 py-1 mt-1 overflow-auto text-base bg-white border border-gray-300 rounded-md shadow-lg max-h-60 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{options.map((option, index) => (
<Menu.Item key={index}>
{({ active }) => (
<a
href="#"
className={`${
active ? "bg-blue-600 text-white" : "text-gray-900"
} block px-4 py-2 text-sm`}
onClick={option.action}
>
{option.label}
</a>
)}
</Menu.Item>
))}
</Menu.Items>
)}
</>
)}
</Menu>
</div>
);
};

export default Dropdown;
66 changes: 66 additions & 0 deletions src/components/foundation/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from "react";
import { Dialog, Transition } from "@headlessui/react";

interface ModalProps {
isOpen: boolean;
setIsOpen: (value: boolean) => void;
children: React.ReactNode;
}

const Modal: React.FC<ModalProps> = ({ isOpen, setIsOpen, children }) => {
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog
as="div"
static
className="fixed z-10 inset-0 overflow-y-auto"
open={isOpen}
onClose={setIsOpen}
>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-50 transition-opacity" />
</Transition.Child>

<span
className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true"
>
&#8203;
</span>

<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
<div>
<div className="flex justify-end items-center">
<button className="w-6" onClick={() => setIsOpen(false)}>
x
</button>
</div>
{children}
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
);
};

export default Modal;
47 changes: 30 additions & 17 deletions src/pages/BudgetPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Button from "../components/foundation/Button/Button";
import { PureComponent as ExitIcon } from "react";
import EditMonthBudget from "../components/EditMonthBudget";
import Spiner from "../components/foundation/Spiner/Spiner";
import EditAction from "../components/EditAction";

const BudgetPage: React.FC = () => {
const [user, loading] = useAuthState(auth);
Expand Down Expand Up @@ -72,7 +73,31 @@ const BudgetPage: React.FC = () => {
}
};

const onClickDelete = () => {
const handleRenameCategory = (newCategoryName: string) => {
if (budget) {
const newBudget: Budget = {
...budget,
categories: budget.categories.map((c) => {
return {
...c,
items: c.items.filter((i) => !selectedItems.includes(i)),
};
}),
};

const newCategories: Category = {
name: newCategoryName,
items: selectedItems,
};

newBudget.categories.push(newCategories);

onUpdateBudget(newBudget);
setSelectedItems([]);
}
};

const handleDeleteItems = () => {
if (budget) {
const newBudget: Budget = {
...budget,
Expand Down Expand Up @@ -133,22 +158,10 @@ const BudgetPage: React.FC = () => {
</div>
<div className="flex gap-2 justify-end">
<AddItemForm budget={budget} onUpdateBudget={onUpdateBudget} />
<Button onClick={onClickDelete} color="red">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-4 h-4"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
</Button>
<EditAction
handleRenameCategory={handleRenameCategory}
handleDeleteItems={handleDeleteItems}
/>
</div>
</div>

Expand Down

1 comment on commit aaaf07b

@vercel
Copy link

@vercel vercel bot commented on aaaf07b Jun 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

cashly – ./

cashly-irori235.vercel.app
cashly-git-main-irori235.vercel.app
cashly.vercel.app

Please sign in to comment.