A Pen by Josh Miller on CodePen.
Created
September 20, 2024 13:45
-
-
Save jshmllr/5b10b4f3690885ec0fd24dc020981316 to your computer and use it in GitHub Desktop.
Co-Pro II
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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