From 9a00a73bbcec50fa66952bbe72c790b5e867a5ac Mon Sep 17 00:00:00 2001 From: rxshellg Date: Wed, 11 Feb 2026 23:50:55 -0500 Subject: [PATCH 1/2] Added assignment logic --- src/components/AssignItemModal.module.css | 99 +++++++++ src/components/AssignItemModal.tsx | 142 +++++++++++++ .../PeopleManager/AddPersonModal.module.css | 4 + .../PeopleManager/AddPersonModal.tsx | 6 +- src/components/PeopleManager/usePeople.ts | 29 +-- src/pages/Split.tsx | 190 +++++++++++++----- src/types/index.ts | 1 - 7 files changed, 393 insertions(+), 78 deletions(-) create mode 100644 src/components/AssignItemModal.module.css create mode 100644 src/components/AssignItemModal.tsx create mode 100644 src/components/PeopleManager/AddPersonModal.module.css diff --git a/src/components/AssignItemModal.module.css b/src/components/AssignItemModal.module.css new file mode 100644 index 0000000..0d481be --- /dev/null +++ b/src/components/AssignItemModal.module.css @@ -0,0 +1,99 @@ +/* Modal Title*/ +.modalTitle { + font-weight: bold; +} + +/* People Selection */ +.emptyState { + color: #999; + font-style: italic; + padding: 1rem; + text-align: center; +} + +.peopleList { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-top: 2%; +} + +.personOption { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + border-radius: 8px; + cursor: pointer; + border: 2px solid #ddd; + transition: all 0.2s ease; + background-color: white; +} + +.personOption:hover { + border-color: var(--primary-pink); + background-color: #fef5f9; +} + +.personOption.selected { + border-color: var(--primary-pink); + background-color: rgba(219, 25, 90, 0.08); +} + +.checkbox { + cursor: pointer; + width: 18px; + height: 18px; +} + +.colorDot { + width: 14px; + height: 14px; + border-radius: 50%; + flex-shrink: 0; +} + +.personName { + flex: 1; +} + +/* Split Preview */ +.splitPreview { + margin-top: 1.5rem; + padding: 1.25rem; + background: linear-gradient(135deg, #fef5f9 0%, #f8f9fa 100%); + border-radius: 12px; + text-align: center; + border: 1px solid #ffe6f0; +} + +.splitLabel { + font-size: 0.875rem; + color: #666; + margin-bottom: 0.5rem; + font-weight: 500; +} + +.splitAmount { + font-size: 1.75rem; + font-weight: bold; + color: var(--primary-pink); + margin: 0; +} + +/* Warning State */ +.warning { + margin-top: 1.5rem; + padding: 1rem; + background-color: #fff3cd; + border-radius: 8px; + text-align: center; + color: #856404; + border: 1px solid #ffeeba; + font-size: 0.9rem; +} + +.warning::before { + content: "⚠️ "; + margin-right: 0.5rem; +} \ No newline at end of file diff --git a/src/components/AssignItemModal.tsx b/src/components/AssignItemModal.tsx new file mode 100644 index 0000000..21469f8 --- /dev/null +++ b/src/components/AssignItemModal.tsx @@ -0,0 +1,142 @@ +import Modal from "react-bootstrap/Modal"; +import { useState, useEffect } from "react"; +import type { Item, Person } from "../types"; +import styles from "./AssignItemModal.module.css"; + +interface Props { + show: boolean; + item: Item | null; + people: Person[]; + currentAssignees: string[]; // Array of person IDs currently assigned + onClose: () => void; + onSave: (itemId: string, personIds: string[]) => void; +} + +export default function AssignItemModal({ + show, + item, + people, + currentAssignees, + onClose, + onSave, +}: Props) { + const [selectedPeople, setSelectedPeople] = useState([]); + + useEffect(() => { + setSelectedPeople(currentAssignees); + }, [currentAssignees, show]); + + const togglePerson = (personId: string) => { + setSelectedPeople((prev) => { + if (prev.includes(personId)) { + return prev.filter((id) => id !== personId); + } else { + return [...prev, personId]; + } + }); + }; + + const calculateSplitAmount = () => { + if (!item || selectedPeople.length === 0) return 0; + const itemTotal = item.price * item.quantity; + return itemTotal / selectedPeople.length; + }; + + const handleSave = () => { + if (!item) return; + onSave(item.id, selectedPeople); + onClose(); + }; + + const handleCancel = () => { + // Reset to original assignments + setSelectedPeople(currentAssignees); + onClose(); + }; + + if (!item) return null; + + const splitAmount = calculateSplitAmount(); + + return ( + + + + Assign {item.name} to... + + + + + {/* People selection */} +
+ {people.length === 0 ? ( +

+ No people added yet. Add people first to assign items. +

+ ) : ( +
+ {people.map((person) => { + const isSelected = selectedPeople.includes(person.id); + return ( + + ); + })} +
+ )} +
+ + {/* Split preview */} + {selectedPeople.length > 0 && ( +
+
+ Split between {selectedPeople.length}{" "} + {selectedPeople.length === 1 ? "person" : "people"} +
+
+ ${splitAmount.toFixed(2)} each +
+
+ )} + + {selectedPeople.length === 0 && people.length > 0 && ( +
+ Select at least one person to assign this item to +
+ )} +
+ + + + +
+ ); +} diff --git a/src/components/PeopleManager/AddPersonModal.module.css b/src/components/PeopleManager/AddPersonModal.module.css new file mode 100644 index 0000000..40a0887 --- /dev/null +++ b/src/components/PeopleManager/AddPersonModal.module.css @@ -0,0 +1,4 @@ +/* Modal Title*/ +.modalTitle { + font-weight: bold; +} \ No newline at end of file diff --git a/src/components/PeopleManager/AddPersonModal.tsx b/src/components/PeopleManager/AddPersonModal.tsx index 31df103..49c0410 100644 --- a/src/components/PeopleManager/AddPersonModal.tsx +++ b/src/components/PeopleManager/AddPersonModal.tsx @@ -1,6 +1,7 @@ import Modal from "react-bootstrap/Modal"; import { useState } from "react"; import { DEFAULT_COLORS } from "./usePeople"; +import styles from './AddPersonModal.module.css'; interface Props { show: boolean; @@ -23,8 +24,8 @@ export default function AddPersonModal({ show, onClose, onSubmit }: Props) { return ( - - Add person + + Add person @@ -32,7 +33,6 @@ export default function AddPersonModal({ show, onClose, onSubmit }: Props) { setName(e.target.value)} - placeholder="Enter name" style={{ width: "100%", marginBottom: 16 }} /> diff --git a/src/components/PeopleManager/usePeople.ts b/src/components/PeopleManager/usePeople.ts index 420b9b8..eec915f 100644 --- a/src/components/PeopleManager/usePeople.ts +++ b/src/components/PeopleManager/usePeople.ts @@ -1,11 +1,5 @@ import { useState } from "react"; - -export type Person = { - id: string; - name: string; - color: string; - total: number; -}; +import type { Person } from "../../types"; export const DEFAULT_COLORS = [ "#C6005A", @@ -17,7 +11,10 @@ export const DEFAULT_COLORS = [ "#6A4C93", "#6F1D1B", "#D4A373", - "#6B7280" + "#6B7280", + "#4B5320", + "#3A86FF", + "#d80606d7" ]; export function usePeople(initial?: Person[]) { @@ -29,28 +26,16 @@ export function usePeople(initial?: Person[]) { ? crypto.randomUUID() : Date.now().toString(); - setPeople(prev => [...prev, { id, name, color, total: 0 }]); + setPeople(prev => [...prev, { id, name, color, items: [] }]); }; const removePerson = (id: string) => { setPeople(prev => prev.filter(p => p.id !== id)); }; - const updateTotal = (id: string, amount: number) => { - setPeople(prev => - prev.map(p => - p.id === id ? { ...p, total: amount } : p - ) - ); - }; - - const grandTotal = people.reduce((sum, p) => sum + p.total, 0); - return { people, addPerson, - removePerson, - updateTotal, - grandTotal + removePerson }; } \ No newline at end of file diff --git a/src/pages/Split.tsx b/src/pages/Split.tsx index 463d641..f72e868 100644 --- a/src/pages/Split.tsx +++ b/src/pages/Split.tsx @@ -1,58 +1,144 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useLocation } from "react-router-dom"; +import AssignItemModal from "../components/AssignItemModal"; import type { Item } from "../types"; import useIsMobile from "../hooks/useIsMobile"; -import { usePeople, PeopleChips, AddPersonModal } from "../components/PeopleManager"; +import { + usePeople, + PeopleChips, + AddPersonModal, +} from "../components/PeopleManager"; -import styles from './Split.module.css' +import styles from "./Split.module.css"; const Split = () => { - const isMobile = useIsMobile(); - const { state } = useLocation() as { state?: { items?: Item[] } }; - const { people, addPerson, removePerson, grandTotal } = usePeople([]); - const [showModal, setShowModal] = useState(false); - const items = state?.items || []; - - return ( - <> -
-
- setShowModal(true)} - onRemovePerson={removePerson} - /> - - {items.map((item, i) => ( - - - - - - - ))} -
{item.quantity}{item.name}${item.price.toFixed(2)}
- setShowModal(false)} - onSubmit={(name, color) => addPerson(name, color)} - /> -
-
-
Results
- - {people.map((p) => ( - - - - - ))} -
{p.name}:${p.total.toFixed(2)}
-
Total: ${grandTotal.toFixed(2)}
-
-
- - ); -} - -export default Split; \ No newline at end of file + const isMobile = useIsMobile(); + const [showAddPersonModal, setShowAddPersonModal] = useState(false); + const { people, addPerson, removePerson } = usePeople([]); + const { state } = useLocation() as { state?: { items?: Item[] } }; + const items = state?.items || []; + const [itemBeingAssigned, setItemBeingAssigned] = useState(null); + const [itemAssignments, setItemAssignments] = useState< + Record + >({}); + + // Sync assignment state with items on load + useEffect(() => { + const initialAssignments: Record = {}; + items.forEach((item) => { + initialAssignments[item.id] = item.assignedTo || []; + }); + setItemAssignments(initialAssignments); + }, [items]); + + const calculatePersonTotals = () => { + const totals: Record = {}; + + people.forEach((p) => { + totals[p.id] = 0; + }); + + items.forEach((item) => { + const assignedPeople = itemAssignments[item.id] || []; + if (assignedPeople.length === 0) return; + + const itemTotal = item.price * item.quantity; + const amountPerPerson = itemTotal / assignedPeople.length; + + assignedPeople.forEach((personId) => { + totals[personId] = (totals[personId] || 0) + amountPerPerson; + }); + }); + + return totals; + }; + + const personTotals = calculatePersonTotals(); + const grandTotal = Object.values(personTotals).reduce( + (sum, total) => sum + total, + 0, + ); + + const openAssignModal = (item: Item) => setItemBeingAssigned(item); + const closeAssignModal = () => setItemBeingAssigned(null); + + const saveAssignment = (itemId: string, personIds: string[]) => { + // Keep existing assignments, update this item + setItemAssignments((currentAssignments) => ({ + ...currentAssignments, + [itemId]: personIds, + })); + closeAssignModal(); + }; + + return ( + <> +
+ {/* Items & Assignment */} +
+ setShowAddPersonModal(true)} + onRemovePerson={removePerson} + /> + + {/* Items table */} + + {items.map((item) => ( + + + + + + + ))} +
{item.quantity}{item.name}${item.price.toFixed(2)} + +
+ + {/* Modals */} + setShowAddPersonModal(false)} + onSubmit={(name, color) => addPerson(name, color)} + /> + +
+ + {/* Results */} +
+
Results
+ + {people.map((person) => ( + + + + + ))} +
{person.name}:${personTotals[person.id]?.toFixed(2) || "0.00"}
+
Total: ${grandTotal.toFixed(2)}
+
+
+ + ); +}; + +export default Split; diff --git a/src/types/index.ts b/src/types/index.ts index afbab60..4b8e473 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -12,7 +12,6 @@ export type Person = { id: string; name: string; color: string; - total: number; items: string[]; }; From 4db488ef3cd3188e362ebd4ed4159c731b4213cf Mon Sep 17 00:00:00 2001 From: rxshellg Date: Wed, 11 Feb 2026 23:54:13 -0500 Subject: [PATCH 2/2] Updated type import --- src/components/PeopleManager/PeopleChips.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PeopleManager/PeopleChips.tsx b/src/components/PeopleManager/PeopleChips.tsx index d3b28b7..098080a 100644 --- a/src/components/PeopleManager/PeopleChips.tsx +++ b/src/components/PeopleManager/PeopleChips.tsx @@ -1,5 +1,5 @@ import styles from "./styles.module.css"; -import type { Person } from "./usePeople"; +import type { Person } from "../../types"; interface Props { people: Person[];