Skip to content
Closed
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
14 changes: 10 additions & 4 deletions .github/workflows/lint-format-write.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ on:

jobs:
lint-format-write:
# Only run on manual trigger, not on main/master branches
if: github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main' && github.ref != 'refs/heads/master'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# Need to fetch all history for git operations
fetch-depth: 0
# Need write permissions to push commits
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Python
Expand Down Expand Up @@ -66,4 +66,10 @@ jobs:
run: |
git add -A
git commit -m "${{ github.event.inputs.commit_message }}"
git push origin ${{ github.ref }}
git push origin ${{ github.ref }} || {
echo "Failed to push changes. This might be due to:"
echo "1. Insufficient permissions on the token"
echo "2. Branch protection rules"
echo "3. Repository settings"
exit 1
}
63 changes: 47 additions & 16 deletions src/ui/distribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,35 @@

@distribute_page.route("/distribute", methods=["GET"])
def distribute_page_view():
item_index = request.args.get("item_index", type=int) or 0
person_index = request.args.get("person_index", type=int) or 0

items = get_current_items(session)
persons = get_current_persons(session)
item = items.items[item_index]
person = (
persons[person_index] if persons and 0 <= person_index < len(persons) else None
)

# Calculate per-item share for this person using map and list comprehension
def share_for_item(args):
idx, item = args
count = sum(1 for p in persons if idx in p.items)
share = (
item.price / count if person and idx in person.items and count > 0 else 0.0
)
return {
"name": item.name,
"price": item.price,
"share": share,
}

item_shares = list(map(share_for_item, enumerate(items.items)))

return render_template(
"distribute.html",
item=item,
persons=persons or [],
item_index=item_index or 0,
item_count=len(items.items),
items=item_shares,
person=person,
person_index=person_index,
person_count=len(persons),
)


Expand Down Expand Up @@ -80,20 +97,34 @@ def distribute_item():
)


@distribute_page.route("/get_persons", methods=["GET"])
def get_persons_api():
persons = get_current_persons(session)
return jsonify([p.model_dump() for p in persons]), 200


@distribute_page.route("/get_items", methods=["GET"])
def get_items_api():
items = get_current_items(session)
return jsonify([{"name": i.name, "price": i.price} for i in items.items]), 200


@distribute_page.route("/save_distribution", methods=["POST"])
def save_distribution():
data = request.get_json()
person_index = data.get("person_index")
item_index = data.get("item_index")
person_ids = data.get("person_ids", [])
add = data.get("add", True)

persons = get_current_persons(session)

for person_id, person in enumerate(persons):
if person_id in person_ids:
person.items = sorted(set(person.items + [item_index]))
elif item_index in person.items:
person.items.remove(item_index)

save_persons_file(persons, session)

if 0 <= person_index < len(persons):
person = persons[person_index]
if add:
if item_index not in person.items:
person.items.append(item_index)
person.items.sort()
else:
if item_index in person.items:
person.items.remove(item_index)
save_persons_file(persons, session)
return jsonify({"success": True}), 200
136 changes: 125 additions & 11 deletions src/ui/distribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ let currentItem: { name: string; price: number } | null = null;
let currentItemIndex: number = 0;
let totalItemCount: number = 0;

// Person-based distribution logic
let currentPersonIndex: number = 0;
let totalPersonCount: number = 0;
let persons: any[] = [];
let items: any[] = [];

const itemName = document.getElementById("item-name") as HTMLSpanElement;
const itemPrice = document.getElementById("item-price") as HTMLSpanElement;
const prevItemButton = document.getElementById(
Expand All @@ -21,13 +27,38 @@ const distributionResults = document.getElementById(
const resultsList = document.getElementById("results-list") as HTMLDivElement;
const loadingOverlay = document.getElementById("loading-overlay") as HTMLElement;

const prevPersonButton = document.getElementById("prev-person-button") as HTMLButtonElement;
const nextPersonButton = document.getElementById("next-person-button") as HTMLButtonElement;
const personName = document.getElementById("person-name") as HTMLSpanElement;

const itemNameElement = document.getElementById(
"item-name",
) as HTMLMetaElement;
const itemPriceElement = document.getElementById(
"item-price",
) as HTMLMetaElement;

const personElements = document.querySelectorAll(".person-box");

document.addEventListener("DOMContentLoaded", async () => {
prevItemButton.addEventListener("click", handlePrevItem);
nextItemButton.addEventListener("click", handleNextItem);
backButton.addEventListener("click", handleBack);
distributeButton.addEventListener("click", handleDone);

initializeItemData();
initializePersonData();
await fetchDistributeData();
renderItemsForPerson();
updateNavigationButtons();

prevPersonButton.addEventListener("click", handlePrevPerson);
nextPersonButton.addEventListener("click", handleNextPerson);

// Attach click handlers to item boxes
document.querySelectorAll(".item-box").forEach((itemBox) => {
itemBox.addEventListener("click", handleItemBoxClick);
});

setupPersonSelectionHandlers();

Expand All @@ -49,7 +80,6 @@ function setupPersonSelectionHandlers(): void {
] as const;
selectedPersons = [];

const personElements = document.querySelectorAll(".person-box");
personElements.forEach((personBox) => {
const personId = parseInt(personBox.getAttribute("data-person-id") || "0");

Expand Down Expand Up @@ -96,16 +126,6 @@ function initializeItemData(): void {
const itemIndexElement = document.getElementById(
"item-index",
) as HTMLMetaElement;
const itemNameElement = document.getElementById(
"item-name",
) as HTMLMetaElement;
const itemPriceElement = document.getElementById(
"item-price",
) as HTMLMetaElement;

totalItemCount = parseInt(itemCountElement.content || "0");
currentItemIndex = parseInt(itemIndexElement.content || "0");

const itemName = itemNameElement.content || "";
const itemPrice = parseFloat(itemPriceElement.content || "0");

Expand Down Expand Up @@ -261,3 +281,97 @@ function getItemIndexFromUrl(): number {
const urlParams = new URLSearchParams(window.location.search);
return parseInt(urlParams.get("item_index") || "0");
}

// Helper to fetch initial data from meta tags or window context
function initializePersonData(): void {
const personCountElement = document.getElementById("person-count") as HTMLMetaElement;
const personIndexElement = document.getElementById("person-index") as HTMLMetaElement;
totalPersonCount = parseInt(personCountElement.content || "0");
currentPersonIndex = parseInt(personIndexElement.content || "0");
}

// Fetch all persons and items from backend
async function fetchDistributeData() {
const [personsRes, itemsRes] = await Promise.all([
fetch("/get_persons"),
fetch("/get_items"),
]);
persons = await personsRes.json();
items = await itemsRes.json();
}

// Render all items for the current person
function renderItemsForPerson() {
const person = persons[currentPersonIndex];
personName.textContent = person.name;
const itemsList = document.querySelectorAll(".item-box");
itemsList.forEach((itemBox, idx) => {
const itemIndex = parseInt((itemBox as HTMLElement).getAttribute("data-item-index") || "0");
if (person.items.includes(itemIndex)) {
itemBox.classList.add("ring-4", "ring-green-400/40", "border-green-400");
} else {
itemBox.classList.remove("ring-4", "ring-green-400/40", "border-green-400");
}
// Update amount for this item for this person
const amountDiv = itemBox.querySelector(".text-blue-400.font-semibold") as HTMLElement;
const share = getPersonItemShare(itemIndex, person);
amountDiv.textContent = `$${share.toFixed(2)}`;
});
}

// Calculate share for a person for an item
function getPersonItemShare(itemIndex: number, person: any): number {
const item = items[itemIndex];
// Count how many persons have this item
const count = persons.filter(p => p.items.includes(itemIndex)).length;
if (person.items.includes(itemIndex) && count > 0) {
return item.price / count;
}
return 0;
}

// Toggle item for current person
async function handleItemBoxClick(e: Event) {
const itemBox = e.currentTarget as HTMLElement;
const itemIndex = parseInt(itemBox.getAttribute("data-item-index") || "0");
const person = persons[currentPersonIndex];
const hasItem = person.items.includes(itemIndex);
// Update backend
await fetch("/save_distribution", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
person_index: currentPersonIndex,
item_index: itemIndex,
add: !hasItem
})
});
// Update local state
if (hasItem) {
person.items = person.items.filter((idx: number) => idx !== itemIndex);
} else {
person.items.push(itemIndex);
}
renderItemsForPerson();
}

// Navigation
function updateNavigationButtons() {
prevPersonButton.disabled = currentPersonIndex <= 0;
nextPersonButton.disabled = currentPersonIndex >= totalPersonCount - 1;
}

async function handlePrevPerson() {
if (currentPersonIndex > 0) {
currentPersonIndex--;
renderItemsForPerson();
updateNavigationButtons();
}
}
async function handleNextPerson() {
if (currentPersonIndex < totalPersonCount - 1) {
currentPersonIndex++;
renderItemsForPerson();
updateNavigationButtons();
}
}
Loading