Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions src/pages/Split.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
gap: 2rem;
}

.test {
.content {
display: flex;
align-items: flex-start;
width: 100%;
Expand Down Expand Up @@ -48,7 +48,7 @@
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 1.5rem;
gap: 1rem;
padding: 5%;

@media (min-width: 768px) {
Expand All @@ -64,7 +64,8 @@
padding-bottom: 0.5rem;
}

.total {
.divider {
width: 100%;
margin-top: 0.5rem;
border-top: 2px solid rgba(255, 255, 255, 0.2);
padding-top: 0.5rem;
Expand Down Expand Up @@ -94,3 +95,33 @@
align-items: center;
justify-content: space-between;
}

.adjustments {
margin-top: 1.5rem;
display: flex;
gap: 1rem;
flex-wrap: nowrap;
}

.inputGroup {
flex: 1 1 0;
min-width: 200px;
}

.inputGroup label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}

.inputGroup input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 8px;
}

.spacerRow td {
padding: 1rem;
border: 0;
}
105 changes: 103 additions & 2 deletions src/pages/Split.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
const [showAddPersonModal, setShowAddPersonModal] = useState(false);
const { people, addPerson, removePerson } = usePeople([]);
const { state } = useLocation() as { state?: { items?: Item[] } };
const items = state?.items || [];

Check warning on line 19 in src/pages/Split.tsx

View workflow job for this annotation

GitHub Actions / test

The 'items' logical expression could make the dependencies of useEffect Hook (at line 34) change on every render. To fix this, wrap the initialization of 'items' in its own useMemo() Hook
const [itemBeingAssigned, setItemBeingAssigned] = useState<Item | null>(null);
const [itemAssignments, setItemAssignments] = useState<
Record<string, string[]>
>({});
const [taxPercent, setTaxPercent] = useState<number>(0);
const [tipPercent, setTipPercent] = useState<number>(0);

// Sync assignment state with items on load
useEffect(() => {
Expand All @@ -38,18 +40,37 @@
totals[p.id] = 0;
});

let subtotal = 0;

// Add up assigned items
items.forEach((item) => {
const assignedPeople = itemAssignments[item.id] || [];
if (assignedPeople.length === 0) return;

const itemTotal = item.price * item.quantity;
subtotal += itemTotal;

const amountPerPerson = itemTotal / assignedPeople.length;

assignedPeople.forEach((personId) => {
totals[personId] = (totals[personId] || 0) + amountPerPerson;
});
});

if (subtotal > 0) {
const taxAmount = (subtotal * taxPercent) / 100;
const tipAmount = (subtotal * tipPercent) / 100;

// Split tax/tip proportionally based on each person's share
people.forEach((p) => {
const personSubtotal = totals[p.id] || 0;
const personProportion = personSubtotal / subtotal;
const personTaxTip = (taxAmount + tipAmount) * personProportion;

totals[p.id] = personSubtotal + personTaxTip;
});
}

return totals;
};

Expand All @@ -59,6 +80,15 @@
0,
);

// Calculate breakdown for display
const subtotal = items.reduce((sum, item) => {
const assignedPeople = itemAssignments[item.id] || [];
if (assignedPeople.length === 0) return sum;
return sum + item.price * item.quantity;
}, 0);
const taxAmount = (subtotal * taxPercent) / 100;
const tipAmount = (subtotal * tipPercent) / 100;

const openAssignModal = (item: Item) => setItemBeingAssigned(item);
const closeAssignModal = () => setItemBeingAssigned(null);

Expand All @@ -75,7 +105,7 @@
<>
<div className={isMobile ? styles.mobilePage : styles.desktopPage}>
{/* Items & Assignment */}
<div className={styles.test}>
<div className={styles.content}>
<div className={styles.itemsContainer}>
<PeopleChips
people={people}
Expand Down Expand Up @@ -109,6 +139,35 @@
))}
</table>

{/* Tax & Tip Inputs */}
<div className={styles.adjustments}>
<div className={styles.inputGroup}>
<label>Tax (%)</label>
<input
type="number"
value={taxPercent}
onChange={(e) => setTaxPercent(Number(e.target.value) || 0)}
min="0"
max="100"
step="0.1"
placeholder="0"
/>
</div>

<div className={styles.inputGroup}>
<label>Tip (%)</label>
<input
type="number"
value={tipPercent}
onChange={(e) => setTipPercent(Number(e.target.value) || 0)}
min="0"
max="100"
step="1"
placeholder="0"
/>
</div>
</div>

{/* Modals */}
<AddPersonModal
show={showAddPersonModal}
Expand All @@ -132,15 +191,57 @@
{/* Results */}
<div className={styles.resultsContainer}>
<div className={styles.resultsHeader}>Results</div>

<table>
{/* Breakdown */}
{(taxPercent > 0 || tipPercent > 0) && (
<>
<tr>
<td>Subtotal:</td>
<td>${subtotal.toFixed(2)}</td>
</tr>

{taxPercent > 0 && (
<tr>
<td>Tax ({taxPercent}%):</td>
<td>${taxAmount.toFixed(2)}</td>
</tr>
)}

{tipPercent > 0 && (
<tr>
<td>Tip ({tipPercent}%):</td>
<td>${tipAmount.toFixed(2)}</td>
</tr>
)}

<tr className={styles.spacerRow}>
<td colSpan={2} aria-hidden></td>
</tr>
</>
)}

{/* Each person's total */}
{people.map((person) => (
<tr key={person.id}>
<td>{person.name}:</td>
<td>${personTotals[person.id]?.toFixed(2) || "0.00"}</td>
</tr>
))}

{/* Grand total */}
{people.length > 0 && (
<tr>
<td colSpan={2}>
<div className={styles.divider}></div>
</td>
</tr>
)}
<tr>
<td>Total: </td>
<td>${grandTotal.toFixed(2)}</td>
</tr>
</table>
<div className={styles.total}>Total: ${grandTotal.toFixed(2)}</div>
</div>
</div>
</div>
Expand Down