powerhouse-rp-toolkit
Version:
Renaissance Periodization Training Toolkit for PowerHouseATX
387 lines (325 loc) • 10.3 kB
JavaScript
/**
* Volume Card UI Handler
* Manages volume range setup and landmark editing
*/
import trainingState from "../core/trainingState.js";
import { updateChart } from "./chartManager.js";
import {
validateVolumeInput,
analyzeVolumeStatus,
} from "../calculators/unified.js";
/**
* Initialize volume card interactions
*/
export function initVolumeCard() {
const muscleSelect = document.getElementById("muscleSelect");
const currentSetsInput = document.getElementById("currentSets");
const mevInput = document.getElementById("mev");
const mrvInput = document.getElementById("mrv");
const mavInput = document.getElementById("mav");
const mvInput = document.getElementById("mv");
if (!muscleSelect || !currentSetsInput) return;
// Update displays when muscle changes
muscleSelect.addEventListener("change", updateVolumeDisplays);
// Inline editing for current sets
currentSetsInput.addEventListener("focus", enableInlineEdit);
currentSetsInput.addEventListener("blur", saveInlineEdit);
currentSetsInput.addEventListener("keypress", handleInlineEditKeypress);
// Landmark editing
if (mevInput) mevInput.addEventListener("change", updateLandmark);
if (mrvInput) mrvInput.addEventListener("change", updateLandmark);
if (mavInput) mavInput.addEventListener("change", updateLandmark);
if (mvInput) mvInput.addEventListener("change", updateLandmark);
// Initialize displays
updateVolumeDisplays();
}
/**
* Update volume displays for selected muscle
*/
function updateVolumeDisplays() {
const muscleSelect = document.getElementById("muscleSelect");
const muscle = muscleSelect?.value;
if (!muscle) return;
updateCurrentSetsDisplay(muscle);
updateLandmarkDisplays(muscle);
updateVolumeStatus(muscle);
}
/**
* Update current sets display
*/
function updateCurrentSetsDisplay(muscle) {
const currentSetsInput = document.getElementById("currentSets");
if (currentSetsInput) {
currentSetsInput.value = trainingState.currentWeekSets[muscle] || 0;
}
}
/**
* Update landmark input displays
*/
function updateLandmarkDisplays(muscle) {
const landmarks = trainingState.volumeLandmarks[muscle];
if (!landmarks) return;
const mevInput = document.getElementById("mev");
const mrvInput = document.getElementById("mrv");
const mavInput = document.getElementById("mav");
const mvInput = document.getElementById("mv");
if (mevInput) mevInput.value = landmarks.MEV;
if (mrvInput) mrvInput.value = landmarks.MRV;
if (mavInput && landmarks.MAV) mavInput.value = landmarks.MAV;
if (mvInput && landmarks.MV) mvInput.value = landmarks.MV;
}
/**
* Update volume status display
*/
function updateVolumeStatus(muscle) {
const statusElement = document.getElementById("volumeStatus");
if (!statusElement) return;
const analysis = analyzeVolumeStatus(muscle);
statusElement.innerHTML = `
<div class="volume-analysis">
<div class="status-indicator ${analysis.status}">
${analysis.status.toUpperCase()}
</div>
<div class="volume-details">
<p>${analysis.recommendation}</p>
<div class="landmarks-display">
<span>MV: ${analysis.landmarks.MV}</span>
<span>MEV: ${analysis.landmarks.MEV}</span>
<span>MAV: ${analysis.landmarks.MAV}</span>
<span>MRV: ${analysis.landmarks.MRV}</span>
</div>
</div>
</div>
`;
statusElement.className = `volume-status ${analysis.urgency}`;
}
/**
* Enable inline editing for current sets
*/
function enableInlineEdit(event) {
const input = event.target;
input.select();
input.dataset.originalValue = input.value;
}
/**
* Save inline edit changes
*/
function saveInlineEdit(event) {
const input = event.target;
const muscleSelect = document.getElementById("muscleSelect");
const muscle = muscleSelect?.value;
if (!muscle) return;
const newValue = parseInt(input.value, 10);
const originalValue = parseInt(input.dataset.originalValue, 10);
if (isNaN(newValue) || newValue === originalValue) {
input.value = originalValue;
return;
}
// Validate input
const validation = validateVolumeInput(muscle, newValue);
if (!validation.isValid) {
showValidationError(validation.warning);
input.value = originalValue;
return;
}
// Show warning but allow input
if (validation.warning) {
showValidationWarning(validation.warning);
}
// Update training state
trainingState.updateWeeklySets(muscle, newValue);
updateChart();
updateVolumeStatus(muscle);
showSaveConfirmation();
}
/**
* Handle keypress in inline edit
*/
function handleInlineEditKeypress(event) {
if (event.key === "Enter") {
event.target.blur();
} else if (event.key === "Escape") {
const input = event.target;
input.value = input.dataset.originalValue;
input.blur();
}
}
/**
* Update volume landmark
*/
function updateLandmark(event) {
const input = event.target;
const landmarkType = input.id.toUpperCase(); // mev -> MEV
const muscleSelect = document.getElementById("muscleSelect");
const muscle = muscleSelect?.value;
if (!muscle) return;
const newValue = parseInt(input.value, 10);
if (isNaN(newValue) || newValue < 0) {
input.value = trainingState.volumeLandmarks[muscle][landmarkType];
return;
}
// Validate landmark relationships
const landmarks = { ...trainingState.volumeLandmarks[muscle] };
landmarks[landmarkType] = newValue;
if (!validateLandmarkRelationships(landmarks)) {
showValidationError("Invalid landmark relationship (MV ≤ MEV ≤ MAV ≤ MRV)");
input.value = trainingState.volumeLandmarks[muscle][landmarkType];
return;
}
// Update training state
trainingState.updateVolumeLandmarks(muscle, { [landmarkType]: newValue });
updateChart();
updateVolumeStatus(muscle);
showSaveConfirmation();
}
/**
* Validate landmark relationships
*/
function validateLandmarkRelationships(landmarks) {
const { MV = 0, MEV, MAV, MRV } = landmarks;
return MV <= MEV && MEV <= MAV && MAV <= MRV;
}
/**
* Show validation error
*/
function showValidationError(message) {
showNotification(message, "error");
}
/**
* Show validation warning
*/
function showValidationWarning(message) {
showNotification(message, "warning");
}
/**
* Show save confirmation
*/
function showSaveConfirmation() {
showNotification("Saved", "success");
}
/**
* Show notification
*/
function showNotification(message, type = "info") {
// Create notification element
const notification = document.createElement("div");
notification.className = `notification ${type}`;
notification.textContent = message;
// Style notification
Object.assign(notification.style, {
position: "fixed",
top: "20px",
right: "20px",
padding: "12px 20px",
borderRadius: "8px",
color: "white",
fontSize: "14px",
fontWeight: "600",
zIndex: "10000",
transition: "all 0.3s ease",
transform: "translateX(100%)",
opacity: "0",
});
// Type-specific styling
const colors = {
success: "#4CAF50",
error: "#f44336",
warning: "#ff9800",
info: "#2196F3",
};
notification.style.backgroundColor = colors[type] || colors.info;
// Add to DOM
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.style.transform = "translateX(0)";
notification.style.opacity = "1";
}, 100);
// Remove after delay
setTimeout(() => {
notification.style.transform = "translateX(100%)";
notification.style.opacity = "0";
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
/**
* Create advanced volume editor
*/
export function createAdvancedVolumeEditor() {
const container = document.getElementById("volumeCard");
if (!container) return;
// Add advanced controls HTML
const advancedHTML = `
<div class="advanced-volume-controls" style="margin-top: 20px;">
<h3>Volume Landmarks</h3>
<div class="landmark-grid">
<div class="input-group">
<label for="mv">MV (Maintenance Volume):</label>
<input type="number" id="mv" min="0" value="0">
</div>
<div class="input-group">
<label for="mav">MAV (Maximum Adaptive Volume):</label>
<input type="number" id="mav" min="0" value="0">
</div>
</div>
<div id="volumeStatus" class="volume-status"></div>
</div>
`;
// Insert before the existing button
const button = container.querySelector("button");
if (button) {
button.insertAdjacentHTML("beforebegin", advancedHTML);
}
}
/**
* Initialize volume presets
*/
export function initVolumePresets() {
const presetContainer = document.createElement("div");
presetContainer.className = "volume-presets";
presetContainer.innerHTML = `
<h4>Quick Presets</h4>
<div class="preset-buttons">
<button type="button" onclick="applyVolumePreset('beginner')">Beginner</button>
<button type="button" onclick="applyVolumePreset('intermediate')">Intermediate</button>
<button type="button" onclick="applyVolumePreset('advanced')">Advanced</button>
</div>
`;
const volumeCard = document.getElementById("volumeCard");
if (volumeCard) {
volumeCard.appendChild(presetContainer);
}
}
/**
* Apply volume preset
*/
window.applyVolumePreset = function (level) {
const muscleSelect = document.getElementById("muscleSelect");
const muscle = muscleSelect?.value;
if (!muscle) return;
const presets = {
beginner: { multiplier: 0.8 },
intermediate: { multiplier: 1.0 },
advanced: { multiplier: 1.2 },
};
const preset = presets[level];
const baseLandmarks = trainingState.volumeLandmarks[muscle];
const newLandmarks = {
MV: Math.round(baseLandmarks.MV * preset.multiplier),
MEV: Math.round(baseLandmarks.MEV * preset.multiplier),
MAV: Math.round(baseLandmarks.MAV * preset.multiplier),
MRV: Math.round(baseLandmarks.MRV * preset.multiplier),
};
trainingState.updateVolumeLandmarks(muscle, newLandmarks);
updateVolumeDisplays();
updateChart();
showNotification(`Applied ${level} preset for ${muscle}`, "success");
};
export {
updateVolumeDisplays,
updateCurrentSetsDisplay,
updateLandmarkDisplays,
updateVolumeStatus,
};