Skip to content

Instantly share code, notes, and snippets.

@jshmllr
Created September 20, 2024 13:45
Show Gist options
  • Save jshmllr/5b10b4f3690885ec0fd24dc020981316 to your computer and use it in GitHub Desktop.
Save jshmllr/5b10b4f3690885ec0fd24dc020981316 to your computer and use it in GitHub Desktop.
Co-Pro II
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Co-Pro Settlement Table</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="controls">
<label for="costBasis">Reference</label>
<select id="costBasis">
<option value="internal">Internal</option>
<option value="artist">External</option>
</select>
</div>
<table id="coProTable">
<thead>
<tr>
<th>Item</th>
<th class="right-align">Prism Promoter</th>
<th class="right-align">Live Nation</th>
<th class="right-align disabled-column">Internal Expenses</th>
<th class="right-align disabled-column">Co-Pro Expenses</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="Item">Trey Kennedy</td>
<td class="right-align editable" data-label="Savelive" data-editable>$23,461.73</td>
<td class="right-align editable" data-label="Live Nation" data-editable>$0.00</td>
<td class="right-align disabled-column" data-label="Internal Expenses">
<div class="expense-wrapper">
<span class="expense-amount">$23,461.73</span>
<sup class="expense-difference"></sup>
</div>
</td>
<td class="right-align revenue-diff disabled-column" data-label="Co-Pro Revenue">
<div class="revenue-wrapper">
<span class="revenue-amount">$23,461.73</span>
</div>
</td>
</tr>
<tr>
<td data-label="Item">Backline Rental (keyboard only)</td>
<td class="right-align editable" data-label="Savelive" data-editable>$127.50</td>
<td class="right-align editable" data-label="Live Nation" data-editable>$0.00</td>
<td class="right-align disabled-column" data-label="Internal Expenses">
<div class="expense-wrapper">
<span class="expense-amount">$127.50</span>
<sup class="expense-difference"></sup>
</div>
</td>
<td class="right-align revenue-diff disabled-column" data-label="Co-Pro Revenue">
<div class="revenue-wrapper">
<span class="revenue-amount">$127.50</span>
</div>
</td>
</tr>
<tr>
<td data-label="Item">Stage Manager</td>
<td class="right-align editable" data-label="Savelive" data-editable>$250.00</td>
<td class="right-align editable" data-label="Live Nation" data-editable>$0.00</td>
<td class="right-align disabled-column" data-label="Internal Expenses">
<div class="expense-wrapper">
<span class="expense-amount">$250.00</span>
<sup class="expense-difference"></sup>
</div>
</td>
<td class="right-align revenue-diff disabled-column" data-label="Co-Pro Revenue">
<div class="revenue-wrapper">
<span class="revenue-amount">$250.00</span>
</div>
</td>
</tr>
<tr id="totalsRow">
<td data-label="Item"><strong>Totals</strong></td>
<td class="right-align" data-label="Savelive"></td>
<td class="right-align" data-label="Live Nation"></td>
<td class="right-align" data-label="Internal Expenses">
<div class="expense-wrapper">
<span class="expense-amount"></span>
<sup class="expense-difference"></sup>
</div>
</td>
<td class="right-align revenue-diff" data-label="Co-Pro Revenue">
<div class="revenue-wrapper">
<span class="revenue-amount"></span>
</div>
</td>
</tr>
</tbody>
</table>
</body>
document.addEventListener("DOMContentLoaded", (event) => {
console.log("DOM fully loaded and parsed");
const editableCells = document.querySelectorAll("[data-editable]");
const costBasisDropdown = document.getElementById("costBasis");
const coProTable = document.getElementById("coProTable");
const expensesColumnHeader = coProTable.querySelector("thead th:nth-child(4)");
console.log(`Found ${editableCells.length} editable cells`);
// Mock data for artist settlement costs (external expenses)
const artistSettlementCosts = {
"Trey Kennedy": "$24,000.00",
"Backline Rental (keyboard only)": "$200.00",
"Stage Manager": "$550.00"
};
// Store original internal costs
const originalInternalCosts = {};
coProTable.querySelectorAll("tbody tr").forEach((row) => {
const itemName = row.cells[0].textContent;
originalInternalCosts[itemName] = row.cells[3].querySelector(".expense-amount").textContent;
});
function updateTotals() {
const rows = coProTable.querySelectorAll("tbody tr:not(#totalsRow)");
const totalsRow = document.getElementById("totalsRow");
let totalSavelive = 0;
let totalLiveNation = 0;
let totalInternalExpenses = 0;
let totalCoProExpenses = 0;
rows.forEach((row) => {
totalSavelive += parseFloat(row.cells[1].textContent.replace(/[^\d.-]/g, ""));
totalLiveNation += parseFloat(row.cells[2].textContent.replace(/[^\d.-]/g, ""));
totalInternalExpenses += parseFloat(row.cells[3].querySelector(".expense-amount").textContent.replace(/[^\d.-]/g, ""));
totalCoProExpenses += parseFloat(row.cells[4].querySelector(".revenue-amount").textContent.replace(/[^\d.-]/g, ""));
});
totalsRow.cells[1].innerHTML = `<strong>$${formatNumber(totalSavelive)}</strong>`;
totalsRow.cells[2].innerHTML = `<strong>$${formatNumber(totalLiveNation)}</strong>`;
const internalExpenseDifference = totalCoProExpenses - totalInternalExpenses;
let differenceHtml = "";
if (Math.abs(internalExpenseDifference) > 0.01) {
differenceHtml = `
<sup class="expense-difference ${internalExpenseDifference >= 0 ? "positive" : "negative"}">
(${internalExpenseDifference >= 0 ? "+" : ""}$${formatNumber(Math.abs(internalExpenseDifference))})
</sup>
`;
}
totalsRow.cells[3].innerHTML = `
<div class="expense-wrapper">
<strong class="expense-amount">$${formatNumber(totalInternalExpenses)}</strong>
${differenceHtml}
</div>
`;
totalsRow.cells[4].innerHTML = `
<div class="revenue-wrapper">
<strong class="revenue-amount">$${formatNumber(totalCoProExpenses)}</strong>
</div>
`;
}
// Helper function to format number with commas
function formatNumber(num) {
return num.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, "$&,");
}
editableCells.forEach((cell) => {
cell.addEventListener("click", function (e) {
console.log("Cell clicked", this);
e.stopPropagation();
if (this.querySelector("input")) return;
const currentValue = this.textContent.replace(/,/g, "");
const input = document.createElement("input");
input.type = "text";
input.value = currentValue;
input.style.width = "100%";
input.style.boxSizing = "border-box";
input.style.textAlign = "right";
this.textContent = "";
this.appendChild(input);
input.focus();
const blurHandler = function () {
const newValue = parseFloat(this.value.replace(/[^\d.-]/g, ""));
cell.textContent = isNaN(newValue) ? "$0.00" : "$" + formatNumber(newValue);
updateRow(cell);
};
input.addEventListener("blur", blurHandler);
input.addEventListener("keydown", function (e) {
if (e.key === "Enter") {
this.blur();
}
});
document.addEventListener("click", function clickOutside(e) {
if (!cell.contains(e.target)) {
blurHandler.call(input);
input.removeEventListener("blur", blurHandler);
document.removeEventListener("click", clickOutside);
}
});
});
});
function updateRow(cell) {
console.log("Updating row", cell);
const row = cell.closest("tr");
const internalExpenses = parseFloat(row.cells[3].querySelector(".expense-amount").textContent.replace(/[^\d.-]/g, ""));
const savelive = parseFloat(row.cells[1].textContent.replace(/[^\d.-]/g, ""));
const liveNation = parseFloat(row.cells[2].textContent.replace(/[^\d.-]/g, ""));
const coProRevenue = savelive + liveNation;
const difference = coProRevenue - internalExpenses;
const internalExpenseCell = row.cells[3];
const revenueCell = row.cells[4];
let differenceHtml = "";
if (Math.abs(difference) > 0.01) {
differenceHtml = `
<sup class="expense-difference ${difference >= 0 ? "positive" : "negative"}">
(${difference >= 0 ? "+" : ""}$${formatNumber(Math.abs(difference))})
</sup>
`;
}
internalExpenseCell.innerHTML = `
<div class="expense-wrapper">
<span class="expense-amount">$${formatNumber(internalExpenses)}</span>
${differenceHtml}
</div>
`;
revenueCell.innerHTML = `
<div class="revenue-wrapper">
<span class="revenue-amount">$${formatNumber(coProRevenue)}</span>
</div>
`;
updateTotals();
}
// Implement cost basis change logic
costBasisDropdown.addEventListener("change", function () {
console.log("Cost basis changed", this.value);
const rows = coProTable.querySelectorAll("tbody tr");
if (this.value === "artist") {
expensesColumnHeader.textContent = "External Expenses";
} else {
expensesColumnHeader.textContent = "Internal Expenses";
}
rows.forEach((row) => {
const itemName = row.cells[0].textContent;
const expensesCell = row.cells[3].querySelector(".expense-amount");
if (this.value === "artist" && artistSettlementCosts[itemName]) {
expensesCell.textContent = artistSettlementCosts[itemName];
} else if (this.value === "internal") {
expensesCell.textContent = originalInternalCosts[itemName];
}
updateRow(row.cells[1]); // Update calculations based on new costs
});
});
// Call updateTotals initially to display totals on first load
updateTotals();
})
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
margin: 20px;
line-height: 1.5;
color: #333;
}
.controls {
margin-bottom: 20px;
display: flex;
align-items: center;
}
.controls label {
margin-right: 10px;
font-weight: 600;
color: #374151;
}
.controls select {
flex-grow: 1;
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 4px;
background-color: #f9fafb;
color: #111827;
font-size: 14px;
cursor: pointer;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
th {
background-color: #f9fafb;
font-weight: 600;
color: #374151;
text-transform: uppercase;
font-size: 12px;
}
.right-align {
text-align: right;
}
.editable {
background-color: #ffffff;
cursor: pointer;
}
.disabled-column {
background-color: #f3f4f6;
color: #6b7280;
}
.revenue-wrapper, .expense-wrapper {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.revenue-amount, .expense-amount {
font-size: 1em;
line-height: 1.2;
}
.expense-difference {
font-size: 0.75em;
line-height: 1;
margin-top: 2px;
}
.expense-difference.positive {
color: #10b981;
}
.expense-difference.negative {
color: #ef4444;
}
@media screen and (max-width: 768px) {
.controls {
flex-direction: column;
align-items: flex-start;
}
.controls label {
margin-bottom: 5px;
}
.controls select {
width: 100%;
}
table {
border: 0;
}
table caption {
font-size: 1.3em;
}
table thead {
border: none;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
table tr {
border-bottom: 3px solid #ddd;
display: block;
margin-bottom: 0.625em;
}
table td {
border-bottom: 1px solid #ddd;
display: block;
font-size: 0.8em;
text-align: right;
padding: 0.625em;
}
table td::before {
content: attr(data-label);
float: left;
font-weight: bold;
text-transform: uppercase;
}
table td:last-child {
border-bottom: 0;
}
.revenue-wrapper, .expense-wrapper {
display: inline-flex;
flex-direction: column;
align-items: flex-end;
justify-content: flex-end;
}
.revenue-amount, .expense-amount {
margin-right: 5px;
}
.expense-difference {
font-size: 0.7em;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment