Last active
August 15, 2021 15:44
-
-
Save fatso83/6a399a9b720297d5c20386af68a38484 to your computer and use it in GitHub Desktop.
Pay bananas, get monkeys 🤯
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
/* | |
This is a typical example of a codebase I inherited 😠| |
I mean, where do I even start ... | |
- direct DOM manipulation | |
- toggling display: none instead of not rendering, ... | |
- unclear data paths and constant heavy lookups | |
Oh, are you surprised that this stinking pile of shit is full of bugs? Me neither. | |
#kilowott #ninestack | |
*/ | |
import React from 'react'; | |
import { Workouts, App } from 'app-interfaces'; | |
import { | |
useStateValue, | |
metricConverter, | |
isInt, | |
} from '../../bin/config/AppContext'; | |
import { makeStyles } from '@material-ui/core/styles'; | |
import { Button } from '@material-ui/core'; | |
import { ExerciseDetailsComponent as ExerciseDetails } from '../Exercises'; | |
import Table from './Table'; | |
import { useTranslation } from 'react-i18next'; | |
import uuid from 'uuid/v4'; | |
import { CatalogClass } from '../../bin/controllers'; | |
const useStyles = makeStyles((theme) => ({ | |
tablList: { | |
'& .step-indicator': { | |
width:"60%", | |
textAlign: 'center', | |
marginTop: 15, | |
// marginLeft: '31%', | |
right:"90px", | |
// position:"sticky", | |
position:"absolute", | |
[theme.breakpoints.down('md')]: { | |
marginTop: 15, | |
width:"100%", | |
marginLeft: '0%', | |
right:'unset' | |
}, | |
}, | |
'& .action-group': { | |
marginTop: -30, | |
[theme.breakpoints.down('md')]: { | |
marginTop: -35, | |
}, | |
'& .MuiButton-root': { | |
height: 40, | |
width: 95, | |
textTransform: 'capitalize', | |
color: '#ffffff', | |
fontSize: 14, | |
fontWeight: 600, | |
'&.prev': { | |
float: 'left', | |
backgroundColor: '#333333', | |
}, | |
'&.next': { | |
float: 'right', | |
backgroundColor: 'var(--text-primary)', | |
}, | |
}, | |
}, | |
}, | |
drawerTab:{ | |
// display:"flex", | |
// justifyContent:"space-between", | |
width:"25vw", | |
"& .tabDiv2":{ | |
width:"44vw", | |
marginTop:"1rem" | |
} | |
} | |
})); | |
export default function StepFormComponent(props: { | |
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void; | |
supersetList?: Workouts.IExercise[] | null; | |
addExercise?: Workouts.IExercise[] | null; | |
superset?: Workouts.IExerciseSuperSet | null; | |
children?: JSX.Element | JSX.Element[]; | |
}) { | |
const { metric } = useStateValue() as { metric: App.State['metric'] }; | |
const initialTab = 0; | |
const [superset, setSuperset] = React.useState(props.superset); | |
const [addExercise, setAddExercise] = React.useState(props.addExercise); | |
const [supersetList, setSupersetList] = React.useState(props.supersetList); | |
const [currentTab, dispatch] = React.useReducer(reduceCurrentTab, initialTab); | |
function handleChange(form: HTMLFormElement) { | |
const formData = new FormData(form); | |
const newExerciseList = formData.get('exercises'); | |
if (newExerciseList && typeof newExerciseList === 'string') { | |
if (supersetList) return setSupersetList(JSON.parse(newExerciseList)); | |
if (addExercise) return setAddExercise(JSON.parse(newExerciseList)); | |
if (superset) | |
return setSuperset({ | |
id: superset && superset.id ? superset.id : uuid(), | |
exercises: JSON.parse(newExerciseList), | |
}); | |
} | |
} | |
function reduceCurrentTab( | |
tab: number, | |
action: { type: string; payload?: any } | |
) { | |
const form = document.getElementById('step-form') as HTMLFormElement | null; | |
if (!form) return tab; | |
switch (action.type) { | |
case 'reset': | |
handleChange(form); | |
break; | |
case 'invalid': | |
return tab; | |
case 'prev': | |
if (tab === initialTab) return initialTab; | |
return tab - 1; | |
case 'next': | |
if (tab === initialTab) { | |
handleChange(form); | |
return tab + 1; | |
} | |
return tab + 1; | |
case 'submit': | |
handleChange(form); | |
form.dispatchEvent(new Event('submit')); | |
break; | |
default: | |
throw new Error(); | |
} | |
return initialTab; | |
} | |
// Handle Prev, Next / Submit button action: | |
const handleAction = (e: React.MouseEvent) => { | |
const action = e.currentTarget.id; | |
const form = document.getElementById('step-form') as HTMLFormElement | null; | |
const tabs = [...document.getElementsByClassName('tab')] as | |
| HTMLElement[] | |
| null; | |
if (!action || !form || !tabs || !tabs[currentTab]) return; | |
const formData = new FormData(form); | |
const exercises = formData.get('exercises'); | |
if (typeof exercises === 'string' && JSON.parse(exercises).length === 0) | |
return dispatch({ type: 'reset' }); | |
// Hide current tab: | |
tabs[currentTab].style.display = 'none'; | |
switch (action) { | |
case 'prevBtn': | |
if (currentTab === 1) { | |
const stepsIndicator=document.getElementById("setMarginTop") | |
if(stepsIndicator && window.innerWidth>700){ | |
stepsIndicator.style.top="unset" | |
} | |
} | |
return dispatch({ type: 'prev' }); | |
case 'nextBtn': | |
// If current tab is first tab then move to next tab: | |
if (currentTab === 0) { | |
return dispatch({ type: 'next' }); | |
} | |
// ...else format and update data input field: | |
else { | |
// Get current exercise id: | |
const exercise_id = | |
(supersetList && | |
supersetList[currentTab - 1] && | |
supersetList[currentTab - 1].exercise_id) || | |
(addExercise && | |
addExercise[currentTab - 1] && | |
addExercise[currentTab - 1].exercise_id) || | |
(superset && | |
superset.exercises[ | |
currentTab === 0 ? currentTab : currentTab - 1 | |
] && | |
superset.exercises[currentTab === 0 ? currentTab : currentTab - 1] | |
.exercise_id); | |
// Collect input fields: | |
const tabInputs = superset | |
? ([ | |
...tabs[ | |
currentTab === 0 ? currentTab : currentTab - 1 | |
].querySelectorAll('input.form-control'), | |
] as HTMLInputElement[] | null) | |
: ([...tabs[currentTab].querySelectorAll('input.form-control')] as | |
| HTMLInputElement[] | |
| null); | |
// Get target input field: | |
const dataInput = document.getElementById( | |
'data' | |
) as HTMLInputElement | null; | |
if (!exercise_id || !dataInput || !tabInputs) return; | |
const initialValues = { | |
name: tabInputs[0].name, | |
value: Number(tabInputs[0].value), | |
}; | |
const newEntry = { | |
id: uuid(), | |
exercise_id, | |
score_count: 0, | |
score_type: initialValues.name, | |
score_cols: [initialValues.name], | |
score_vals: [ | |
{ | |
type: initialValues.name, | |
value: [ | |
metricConverter( | |
initialValues.value, | |
`${initialValues.name}O`, | |
metric | |
), | |
], | |
}, | |
], | |
result_vals: [], | |
notes_sets: [], | |
exercise_notes: null, | |
}; | |
const reduceExerciseItem = ( | |
accum: Workouts.IExercise, | |
tabInput: HTMLInputElement | |
) => { | |
// if input name is same as score type, update score count and score vals: | |
if (tabInput.name === accum.score_type) { | |
if (isInt(tabInput.name) && tabInput.valueAsNumber % 1 !== 0) { | |
if (!tabInput.classList.contains('is-invalid')) | |
tabInput.classList.toggle('is-invalid'); | |
} else { | |
if (tabInput.classList.contains('is-invalid')) | |
tabInput.classList.toggle('is-invalid'); | |
} | |
accum.score_vals[0].value[accum.score_count] = metricConverter( | |
Number(tabInput.value), | |
`${tabInput.name}O`, | |
metric | |
); | |
accum.score_count = accum.score_count + 1; | |
return accum; | |
} | |
// else if input name is exercise_notes update exercise_notes: | |
else if (tabInput.name === 'exercise_notes') { | |
accum.exercise_notes = tabInput.value; | |
return accum; | |
} | |
// else if input name is notes_sets, update notes_sets: | |
else if (tabInput.name === 'notes_sets') { | |
const targetData = tabInput.parentElement as HTMLTableDataCellElement | null; | |
if (!targetData) return accum; | |
const setIndex = targetData.cellIndex - 1; | |
accum.notes_sets[setIndex] = tabInput.value; | |
return accum; | |
} | |
// else if input name exists in score cols update score values: | |
else if (accum.score_cols.find((el) => el === tabInput.name)) { | |
if (isInt(tabInput.name) && !tabInput.valueAsNumber) { | |
if (tabInput.classList.contains('is-invalid')) | |
tabInput.classList.toggle('is-invalid'); | |
} else if ( | |
isInt(tabInput.name) && | |
tabInput.valueAsNumber % 1 !== 0 | |
) { | |
if (!tabInput.classList.contains('is-invalid')) | |
tabInput.classList.toggle('is-invalid'); | |
} else { | |
if (tabInput.classList.contains('is-invalid')) | |
tabInput.classList.toggle('is-invalid'); | |
} | |
const targetData = tabInput.parentElement as HTMLTableDataCellElement | null; | |
if (!targetData) return accum; | |
const setIndex = targetData.cellIndex - 1; | |
accum.score_vals[ | |
accum.score_cols.findIndex((el) => el === tabInput.name) | |
].value[setIndex] = metricConverter( | |
Number(tabInput.value), | |
`${tabInput.name}O`, | |
metric | |
); | |
return accum; | |
} | |
// else input name doesn't exist, add score col and score val: | |
else { | |
if (isInt(tabInput.name) && !tabInput.valueAsNumber) { | |
if (tabInput.classList.contains('is-invalid')) | |
tabInput.classList.toggle('is-invalid'); | |
} else if ( | |
isInt(tabInput.name) && | |
tabInput.valueAsNumber % 1 !== 0 | |
) { | |
if (!tabInput.classList.contains('is-invalid')) | |
tabInput.classList.toggle('is-invalid', true); | |
} else { | |
if (tabInput.classList.contains('is-invalid')) | |
tabInput.classList.toggle('is-invalid', false); | |
} | |
accum.score_cols.push(tabInput.name); | |
accum.score_vals.push({ | |
type: tabInput.name, | |
value: [ | |
metricConverter( | |
Number(tabInput.value), | |
`${tabInput.name}O`, | |
metric | |
), | |
], | |
}); | |
return accum; | |
} | |
}; | |
const currentExerciseList = JSON.parse( | |
dataInput.value | |
) as Workouts.IExercise[]; | |
const newExerciseItem = [...tabInputs].reduce( | |
reduceExerciseItem, | |
newEntry | |
); | |
Object.assign( | |
currentExerciseList[currentTab === 0 ? currentTab : currentTab - 1], | |
newExerciseItem | |
); | |
// Update data input value: | |
dataInput.value = JSON.stringify(currentExerciseList); | |
// Check metric values | |
if ( | |
[...tabInputs].find((el) => el.classList.contains('is-invalid')) | |
) { | |
tabs[currentTab].style.display = 'block'; | |
// tabs[currentTab][0].style.width = 'unset'; | |
// tabs[currentTab][0].style.paddingRight = 0; | |
// tabs[currentTab][1].style.width = 'unset'; | |
// tabs[currentTab][1].style.marginTop = 0; | |
// return dispatch({ type: 'invalid' }); | |
} | |
// If on the last tab then submit form: | |
if ( | |
(tabs.length > 1 && currentTab === tabs.length - 1) || | |
(currentTab === 0 && superset && superset.exercises.length === 1) | |
) { | |
return dispatch({ type: 'submit', payload: dataInput.value }); | |
} | |
// ... else move to next tab: | |
return dispatch({ type: 'next' }); | |
} | |
default: | |
return dispatch({ type: 'reset' }); | |
} | |
}; | |
// Handle current tab | |
const showTab = React.useCallback(() => { | |
const tabs = document.getElementsByClassName( | |
'tab' | |
) as HTMLCollectionOf<HTMLElement> | null; | |
const prevBtn = document.getElementById( | |
'prevBtn' | |
) as HTMLButtonElement | null; | |
const nextBtn = document.getElementById( | |
'nextBtn' | |
) as HTMLButtonElement | null; | |
if (!tabs || !tabs[currentTab] || !prevBtn || !nextBtn) return; | |
const temp= document.getElementById("superset") | |
// const temp2= document.getElementById("tablist-container") | |
// console.log(temp2?.children[1].className) | |
// const oldClass=temp2?.children[1].className | |
if(currentTab){ | |
if(temp){ | |
// temp2?.children[1].setAttribute("class", `${oldClass} scroller`) | |
window.innerWidth>700 && temp.children[2].setAttribute("style", "width: 87vw") | |
// console.log(temp) | |
} | |
} | |
else{ | |
if(temp){ | |
window.innerWidth>700 && temp.children[2].setAttribute("style", "width: 512px") | |
// temp2?.children[1].setAttribute("class", `tab`) | |
} | |
} | |
// Handle step indicator: | |
const fixStepIndicator = (n: number) => { | |
// Remove class from all steps: | |
const steps = document.getElementsByClassName( | |
'step' | |
) as HTMLCollectionOf<HTMLSpanElement> | null; | |
if (!steps || !steps[n]) return; | |
[...steps].forEach((v, i) => { | |
steps[i].className = steps[i].className.replace(' active', ''); | |
}); | |
// If on first tab remove finish class: | |
if (n === 0) | |
steps[n].className = steps[n].className.replace(' finish', ''); | |
// ... else add finish class to previous step: | |
if (n !== 0) steps[n - 1].className += ' finish'; | |
// ... add active class to current step: | |
steps[n].className = steps[n].className.replace(' finish', ''); | |
steps[n].className += ' active'; | |
}; | |
// Display the current tab: | |
tabs[currentTab].style.display = 'block'; | |
let currentTabTabs = tabs[currentTab].children as HTMLCollectionOf<HTMLElement>; | |
currentTabTabs.length > 1 && (currentTabTabs[0].style.width = 'unset'); | |
currentTabTabs.length > 1 && (currentTabTabs[0].style.paddingRight = "0px"); | |
currentTabTabs.length > 1 && (currentTabTabs[1].style.width = 'unset'); | |
currentTabTabs.length > 1 && (currentTabTabs[1].style.marginTop = "0px"); | |
// Fix previous button: | |
if (currentTab === 0) { | |
prevBtn.style.display = 'none'; | |
} else { | |
prevBtn.style.display = 'inline'; | |
} | |
// Fix next button: | |
if (currentTab === tabs.length - 1) { | |
// console.log(props.superset && props.superset.exercises.length); | |
if (currentTab === 0 && superset && superset.exercises.length === 1) | |
nextBtn.innerHTML = 'Submit'; | |
else if (currentTab === 0) nextBtn.innerHTML = 'Next'; | |
else nextBtn.innerHTML = 'Submit'; | |
} else { | |
nextBtn.innerHTML = 'Next'; | |
} | |
// Fix step indicators: | |
fixStepIndicator(currentTab); | |
}, [currentTab, superset]); | |
// Effects | |
React.useEffect(showTab, [currentTab]); | |
return ( | |
<form id='step-form' className='step-form' onSubmit={props.onSubmit}> | |
{/* Tabs section */} | |
{props.children} | |
<TabsList | |
currentTab={currentTab} | |
superset={superset} | |
addExercise={addExercise} | |
supersetList={supersetList} | |
handleAction={handleAction} | |
/> | |
</form> | |
); | |
} | |
type TabListProps = { | |
currentTab: number; | |
supersetList?: Workouts.IExercise[] | null; | |
addExercise?: Workouts.IExercise[] | null; | |
superset?: Workouts.IExerciseSuperSet | null; | |
handleAction: (e: React.MouseEvent) => false | void; | |
children?: any; | |
}; | |
const TabsList: React.FC<TabListProps> = ({ | |
currentTab, | |
supersetList, | |
addExercise, | |
superset, | |
handleAction, | |
}) => { | |
const classes = useStyles(); | |
const { t } = useTranslation(); | |
if (supersetList) { | |
return ( | |
<div id='tablist-container' className={classes.tablList} style={{width:window.innerWidth>700 ?"48%" :"100%"}}> | |
{supersetList.map((exercise, exerciseIndex) => ( | |
<TabComponent | |
key={exerciseIndex} | |
exerciseIndex={exerciseIndex} | |
currentTab={currentTab} | |
exercise={exercise} | |
/> | |
))} | |
{/* Step indicator */} | |
<div className='step-indicator' id="setMarginTop"> | |
{currentTab ? <><span key={0} className='step' /> | |
{supersetList.map((exercise, exerciseIndex) => ( | |
<span key={exerciseIndex + 1} className='step' /> | |
))} | |
</> | |
: | |
<div style={{marginTop:"40px"}}/> | |
} | |
{/* Action buttons Previous, Next/Submit */} | |
<div className='action-group' | |
// style={{width:window.innerWidth>700 ?"60%": "unset", marginLeft:window.innerWidth>700 ?"31%" :"0px"}} | |
> | |
<Button | |
variant='contained' | |
size='small' | |
id='prevBtn' | |
type='button' | |
className='prev' | |
onClick={handleAction} | |
> | |
{t("Previous")} | |
</Button> | |
<Button | |
variant='contained' | |
size='small' | |
id='nextBtn' | |
type='button' | |
className='next' | |
onClick={handleAction} | |
> | |
{t("Next")} | |
</Button> | |
</div> | |
</div> | |
</div> | |
); | |
} else if (superset) { | |
return ( | |
<div id='tablist-container' className={classes.tablList} style={{width:window.innerWidth>700 ?"48%" :"100%"}}> | |
{superset.exercises.map((exercise, exerciseIndex) => { | |
return ( | |
<TabComponent | |
key={exerciseIndex} | |
currentTab={currentTab + 1} | |
exercise={exercise} | |
exerciseIndex={exerciseIndex} | |
/> | |
); | |
})} | |
{/* Step indicator */} | |
<div className='step-indicator' id="setMarginTop"> | |
{/* <span key={0} className='step' /> */} | |
{currentTab ? | |
<>{superset.exercises.map((exercise, exerciseIndex) => ( | |
<span key={exerciseIndex} className='step' /> | |
))} | |
</> | |
: | |
<div style={{marginTop:"40px"}}/> | |
} | |
{/* Action buttons Previous, Next/Submit */} | |
<div className='action-group' | |
// style={{width:window.innerWidth>700 ?"60%" :"unset", marginLeft:window.innerWidth>700 ?"31%":"0px"}} | |
> | |
<Button | |
variant='contained' | |
size='small' | |
id='prevBtn' | |
type='button' | |
className='prev' | |
onClick={handleAction} | |
> | |
{t("Previous")} | |
</Button> | |
<Button | |
variant='contained' | |
size='small' | |
id='nextBtn' | |
type='button' | |
className='next' | |
onClick={handleAction} | |
> | |
{t("Next")} | |
</Button> | |
</div> | |
</div> | |
</div> | |
); | |
} else if (addExercise) { | |
return ( | |
<div id='tablist-container' className={classes.tablList} style={{width:window.innerWidth>700 ?"48%" :"100%"}}> | |
{addExercise.map((exercise, exerciseIndex) => ( | |
<TabComponent | |
key={exerciseIndex} | |
exerciseIndex={exerciseIndex} | |
currentTab={currentTab} | |
exercise={exercise} | |
/> | |
))} | |
{/* Step indicator */} | |
<div className='step-indicator' id="setMarginTop"> | |
{currentTab ? <><span key={0} className='step' /> | |
{addExercise.map((exercise, exerciseIndex) => ( | |
<span key={exerciseIndex + 1} className='step' /> | |
))} </> | |
: | |
<div style={{marginTop:"40px"}}/> | |
} | |
{/* Action buttons Previous, Next/Submit */} | |
<div className='action-group'> | |
<Button | |
variant='contained' | |
size='small' | |
id='prevBtn' | |
type='button' | |
className='prev' | |
onClick={handleAction} | |
> | |
{t("Previous")} | |
</Button> | |
<Button | |
variant='contained' | |
size='small' | |
id='nextBtn' | |
type='button' | |
className='next' | |
onClick={handleAction} | |
> | |
{t("Next")} | |
</Button> | |
</div> | |
</div> | |
</div> | |
); | |
} | |
return <div id='tablist-container'></div>; | |
}; | |
type TabProps = { | |
currentTab?: number; | |
exerciseIndex: number; | |
exercise: Workouts.IExercise; | |
mystyle?: boolean | undefined | |
}; | |
export const TabComponent: React.FC<TabProps> = ({ | |
currentTab, | |
exerciseIndex, | |
exercise, | |
mystyle | |
}) => { | |
const { catalog } = useStateValue() as { catalog: CatalogClass }; | |
const {t} = useTranslation(); | |
const classes = useStyles(); | |
if (!catalog) return <></>; | |
const exerciseItem = | |
catalog.MyExercises.find((el) => el.id === exercise.exercise_id) || | |
catalog.Exercises.find((el) => el.id === exercise.exercise_id); | |
if (!exerciseItem) | |
return ( | |
<div className='tab' style={{width:"25vw"}}> | |
<h5 className='section-heading'>{t("Exercise not found")}</h5> | |
</div> | |
); | |
return ( | |
window.innerWidth > 700 ? | |
<div className={`tab ${mystyle && classes.drawerTab}`} style={{width:"25vw"}}> | |
{/* <div className='tabDiv1'> */} | |
<h5 className='section-heading' style={{ color: '#333333',fontWeight:"bold", marginLeft: "0.5%" }}> | |
{exerciseItem.name} | |
</h5> | |
{/* </div> | |
<div className='tabDiv2'> */} | |
<ExerciseDetails match={{ params: exercise }} mystyle={true} > | |
<Table | |
exercise={exercise} | |
index={exerciseIndex + 1} | |
currentTab={currentTab} | |
exerciseItem={exerciseItem} | |
/> | |
</ExerciseDetails> | |
{/* </div> */} | |
</div> : | |
<div className={`tab ${mystyle && classes.drawerTab}`} style={{width:"25vw"}}> | |
<h5 className='section-heading' style={{ color: '#333333',fontWeight:"bold", marginLeft: "1.5%" }}> | |
{exerciseItem.name} | |
</h5> | |
<ExerciseDetails match={{ params: exercise }} mystyle={true}> | |
<Table | |
exercise={exercise} | |
index={exerciseIndex + 1} | |
currentTab={currentTab} | |
exerciseItem={exerciseItem} | |
/> | |
</ExerciseDetails> | |
</div> | |
); | |
}; |
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
import React from 'react'; | |
import Popup from 'reactjs-popup'; | |
import { | |
ExerciseTypes, | |
WorkoutScoreType, | |
CardioScoreType, | |
CardioScoreCloumns, | |
StrengthScoreColumns, | |
WorkoutScoreColumns, | |
ScoreType, | |
} from '../../bin/models/StaticTypes'; | |
import { | |
useStateValue, | |
metricConverter, | |
metricUnit, | |
isInt, | |
} from '../../bin/config/AppContext'; | |
import { App, Workouts, Exercises } from 'app-interfaces'; | |
import { makeStyles } from '@material-ui/core/styles'; | |
import { | |
Table, | |
TableHead, | |
TableBody, | |
Dialog, | |
DialogActions, | |
DialogContent, | |
DialogTitle, | |
Button, | |
} from '@material-ui/core'; | |
import { Clear } from '@material-ui/icons'; | |
import { useTranslation } from 'react-i18next'; | |
import { ConfirmModal as ConfirmCol } from '../Modals'; | |
import Icons from '../Utilities/Icons'; | |
const useStyles = makeStyles((theme) => ({ | |
addSetBtn:{ | |
backgroundColor:"#7ed320", | |
borderColor:"#7ed320" | |
}, | |
tableToppy: { width: "10px", | |
borderTop: "3px solid var(--text-primary)", | |
borderLeft: "3px solid #5692F4", | |
borderBottom: "3px solid #5692F4" }, | |
tableContainer: { | |
margin: '12px 0', | |
maxWidth: '100%', | |
maxHeight: 560, | |
overflow: 'auto', | |
position: 'relative', | |
backgroundImage: `url(${process.env.PUBLIC_URL}/table.png)`, | |
backgroundSize: "248px", | |
'& .MuiTable-root': { | |
'& tr': { | |
height: 38, | |
'& th, & td, ': { | |
minWidth: 130, | |
border: '1px solid #ffffff', | |
padding: "12px 20px", | |
fontSize: 14, | |
fontWeight: 500, | |
color: '#ffffff', | |
}, | |
'& th:first-child': { | |
width: 124, | |
}, | |
}, | |
'& .MuiTableHead-root': { | |
backgroundColor: 'var(--text-primary)', | |
}, | |
'& .MuiTableBody-root': { | |
backgroundColor: '#5692F4', | |
'& tr': { | |
'& td': { | |
paddingTop: 5, | |
paddingBottom: 2, | |
verticalAlign: 'top', | |
'& .form-label, & .form-control': { | |
marginTop: "0px", | |
fontSize: 14, | |
fontWeight: 500, | |
textAlign: 'left', | |
textTransform: 'capitalize', | |
'& span': { | |
color: '#ffffff', | |
display: 'block', | |
textTransform: 'lowercase', | |
}, | |
}, | |
'& .form-label': { | |
color: '#ffffff', | |
}, | |
'& .form-control': { | |
display: 'block', | |
width: '100%', | |
height: 'calc(1.5em + .75em + 2px)', | |
padding: '.375rem .75rem', | |
color: '#495057', | |
backgroundColor: '#ffffff', | |
backgroundClip: 'padding-box', | |
border: '1px solid #ced4da', | |
borderRadius: '.25rem', | |
transition: | |
'border-color .15s ease-in-out,box-shadow .15s ease-in-out', | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
dialog: { | |
'& .MuiDialogTitle-root': { | |
height: 75, | |
backgroundColor: 'var(--text-primary)', | |
color: '#ffffff', | |
fontSize: 20, | |
fontWeight: 500, | |
padding: '25px 15px', | |
'& svg': { | |
float: 'right', | |
marginRight: 15, | |
marginTop: -2, | |
}, | |
'& .session-options': { | |
float: 'right', | |
fontSize: 14, | |
fontWeight: 400, | |
'& li': { | |
fontSize: 16, | |
fontWeight: 'bold', | |
padding: '5px 0', | |
'& svg': { | |
height: 24, | |
width: 24, | |
marginRight: 14, | |
}, | |
}, | |
}, | |
}, | |
'& .MuiDialogContent-root': { | |
'& textarea': { | |
marginTop: 15, | |
padding: 15, | |
borderRadius: 3, | |
width: '100%', | |
height: 83, | |
border: '1px solid #dcdcdc', | |
backgroundColor: 'transparent', | |
fontSize: 16, | |
}, | |
}, | |
'& .MuiDialogActions-root': { | |
paddingLeft: 20, | |
paddingRight: 20, | |
width: '100%', | |
'& button': { | |
height: 50, | |
backgroundColor: 'var(--text-primary)', | |
color: '#ffffff', | |
fontSize: theme.spacing(2), | |
textTransform: 'capitalize', | |
}, | |
}, | |
}, | |
})); | |
type TableProps = { | |
// tabDiv2:Boolean; | |
exercise: Workouts.IExercise; | |
exerciseItem: Exercises.IExercise; | |
currentTab?: number; | |
index: number; | |
}; | |
export default function TableComponent(props: TableProps) { | |
const classes = useStyles(); | |
const { t } = useTranslation(); | |
const { exercise, exerciseItem, currentTab, index } = props; | |
const { ic_del, ic_info } = Icons; | |
const { catalog, metric, Methods } = useStateValue() as { | |
catalog: App.State['catalog']; | |
metric: App.State['metric']; | |
Methods: App.Methods; | |
}; | |
const isSchedule = !/workouts/.test(window.location.href); | |
const [labelState, setLabelState]: any = React.useState([]); | |
const [tableHead, setTableHead] = React.useState<JSX.Element>(); | |
const [tableBody, setTableBody] = React.useState<JSX.Element[]>(); | |
const [resultRow, setResultRow] = React.useState<JSX.Element[]>(); | |
const [targetCol, setTargetCol] = React.useState<number | null>(null); | |
const [targetSetNotes, setTargetSetNotes] = React.useState<number | null>( | |
null | |
); | |
const [showConfirmCol, setShowConfirmCol] = React.useState(false); | |
const [showSetNotes, setShowSetNotes] = React.useState(false); | |
const [generateTableCount, setGenerateTableCount] = React.useState(0); | |
React.useEffect(()=>{ | |
setTimeout(function(){ | |
const x=document.getElementById( | |
`table-container-${index}`)?.clientHeight | |
const y=document.getElementById("setTop") | |
const z=document.getElementById( | |
`table-container-${index}`)?.clientWidth | |
const stepsIndicator=document.getElementById("setMarginTop") | |
// console.log('tablewidth', z) | |
if(y && x && window.innerWidth>700){ | |
y.style.top=`${x+130}px` | |
} | |
if(stepsIndicator && x && window.innerWidth>700){ | |
stepsIndicator.style.top=`${x+150}px` | |
if(z){ | |
stepsIndicator.style.width=`${z}px` | |
} | |
} | |
},10) | |
}) | |
// Show confirm rem Col | |
const showConfirmColModal = React.useCallback(() => { | |
if (!targetCol) return setShowConfirmCol(false); | |
return setShowConfirmCol(true); | |
}, [targetCol]); | |
// Show set note | |
const showSetNoteModal = React.useCallback(() => { | |
if (!targetSetNotes) return setShowSetNotes(false); | |
return setShowSetNotes(true); | |
}, [targetSetNotes]); | |
// Handle rem set click | |
const handleRemCol = (e: MouseEvent) => { | |
const currentTarget = e.currentTarget as HTMLSpanElement | null; | |
if (currentTarget) { | |
const tableHeadCell = currentTarget.parentElement as HTMLTableHeaderCellElement | null; | |
return tableHeadCell && setTargetCol(tableHeadCell.cellIndex); | |
} | |
}; | |
// Handle set notes click | |
const handleSetNotes = (e: MouseEvent) => { | |
const currentTarget = e.currentTarget as HTMLSpanElement | null; | |
if (currentTarget) { | |
const tableHeadCell = currentTarget.parentElement as HTMLTableHeaderCellElement | null; | |
return tableHeadCell && setTargetSetNotes(tableHeadCell.cellIndex); | |
} | |
}; | |
// Save set notes | |
const saveSetNotes = (e: React.FormEvent<HTMLFormElement>) => { | |
e.preventDefault(); | |
e.stopPropagation(); | |
const formData = new FormData(e.currentTarget); | |
const newNotes = formData.get('notes') as string; | |
const targetTable = document.getElementById( | |
`set-table-${index}` | |
) as HTMLTableElement | null; | |
if (!targetTable || !targetSetNotes) return; | |
const tableBody = targetTable.children[1] as HTMLTableElement; // <tbody/> | |
const tableBodyRows = tableBody.children as HTMLCollectionOf<HTMLTableRowElement>; // <tr/> | |
const tableSetNotes = tableBodyRows[0].children[targetSetNotes] | |
.children[1] as HTMLInputElement; | |
tableSetNotes.setAttribute('value', newNotes); | |
if (!exercise.notes_sets) Object.assign(exercise, { notes_sets: [] }); | |
exercise.notes_sets[targetSetNotes - 1] = newNotes; | |
return setTargetSetNotes(null); | |
}; | |
// Confirm remove column | |
const confirmRemCol = () => { | |
const targetTable = document.getElementById( | |
`set-table-${index}` | |
) as HTMLTableElement | null; | |
const dataInput = document.getElementById( | |
'data' | |
) as HTMLInputElement | null; | |
if (!targetTable || !targetCol || !dataInput) return; | |
const tableHead = targetTable.children[0] as HTMLTableElement; // <thead/> | |
const tableHeadRow = tableHead.children[0] as HTMLTableHeaderCellElement; // <th/> | |
const tableBody = targetTable.children[1] as HTMLTableElement; // <tbody/> | |
const tableBodyRows = tableBody.children as HTMLCollectionOf<HTMLTableRowElement>; // <tr/> | |
// Remove the head cell. | |
tableHeadRow.children[targetCol].remove(); | |
// Remove coresponding column cells. | |
[...tableBodyRows].forEach((v) => v.children[targetCol].remove()); | |
// Get current exercise | |
const currentExercise = exercise; | |
// Decrement score count. | |
currentExercise.score_count && currentExercise.score_count--; | |
// Splice score values. | |
currentExercise.score_vals && | |
currentExercise.score_vals.map((val) => | |
val.value.splice(targetCol - 1, 1) | |
); | |
// Update data input value | |
const currentDataInput = JSON.parse( | |
dataInput.value | |
) as Workouts.IExercise[]; | |
if (currentTab) { | |
currentDataInput[currentTab - 1] = currentExercise; | |
dataInput.value = JSON.stringify(currentDataInput); | |
} else { | |
dataInput.value = JSON.stringify([currentExercise]); | |
} | |
if (targetCol >= 1) { | |
for (let i = targetCol; i < tableHeadRow.children.length; i++) { | |
tableHeadRow.children[ | |
i | |
].innerHTML = `<td>${i} | |
<span class='info-icon' onClick='${handleSetNotes}'> | |
<img | |
src='${ic_info}' | |
alt='${t('notes')}' | |
title='${t(`Set ${i + 1} Notes`)}' | |
/> | |
</span> | |
<span class='del-icon' id='dlt${i + 1}'><img src='${ic_del}'/></span></td>`; | |
tableHeadRow.children[i].children[0].addEventListener("click", (e: any) => handleSetNotes(e)); | |
tableHeadRow.children[i].children[1].addEventListener("click", (e: any) => handleRemCol(e)); | |
} | |
} | |
return setTargetCol(null); | |
}; | |
// Generate Metrics | |
const generateMetrics = React.useCallback( | |
(name: string) => metricUnit(name, metric), | |
[metric] | |
); | |
// Generate HeadRow | |
const generateHeadRow = React.useCallback(() => { | |
const handleRemCol = (e: React.MouseEvent<HTMLSpanElement>) => { | |
if (e.currentTarget) { | |
const targetRow = e.currentTarget | |
.parentElement as HTMLTableHeaderCellElement | null; | |
return targetRow && setTargetCol(targetRow.cellIndex); | |
} | |
}; | |
const handleSetNotes = (e: React.MouseEvent<HTMLSpanElement>) => { | |
if (e.currentTarget) { | |
const tableHeadCell = e.currentTarget | |
.parentElement as HTMLTableHeaderCellElement | null; | |
return tableHeadCell && setTargetSetNotes(tableHeadCell.cellIndex); | |
} | |
}; | |
const setHeads = () => { | |
const scoreCount = exercise.score_count; | |
let heads: JSX.Element[] = scoreCount | |
? [] | |
: [ | |
<th key={0}> | |
{1} | |
<span className='info-icon' onClick={handleSetNotes}> | |
<img src={ic_info} alt={t('notes')} title={t('Set 1 Notes')} /> | |
</span> | |
</th>, | |
]; | |
if (scoreCount) { | |
for (let i = 0; i < scoreCount; i++) { | |
heads.push( | |
<th key={i + 1}> | |
{i + 1} | |
{i >= 0 ? ( | |
<> | |
<span className='info-icon' onClick={handleSetNotes}> | |
<img | |
src={ic_info} | |
alt={t('notes')} | |
title={t(`Set ${i + 1} Notes`)} | |
/> | |
</span> | |
<span | |
className='del-icon' | |
onClick={handleRemCol} | |
> | |
<img | |
src={ic_del} | |
alt={t('delete')} | |
title={t(`Remove Set ${i + 1}`)} | |
/> | |
</span> | |
</> | |
) : ( | |
<span | |
className='info-icon' | |
onClick={() => setTargetSetNotes(i + 1)} | |
> | |
<img src={ic_info} alt={t('notes')} title={t(`Set ${i + 1} Notes`)} /> | |
</span> | |
)} | |
</th> | |
); | |
} | |
} | |
return heads; | |
}; | |
if (!tableHead) { | |
return setTableHead( | |
<tr> | |
<th>{t("Sets")}</th> | |
{setHeads()} | |
</tr> | |
); | |
} | |
}, [tableHead, exercise, ic_del, ic_info, t]); | |
// Generate BodyRow | |
const generateBodyRow = React.useCallback(() => { | |
// toggle option select | |
const toggleOptions = (e: React.ChangeEvent<HTMLInputElement>) => { | |
e.preventDefault(); | |
const currentTargetValue = e.currentTarget.value; | |
const currentTargetDataCell = e.currentTarget | |
.parentElement as HTMLTableDataCellElement | null; | |
const currentTargetRow = | |
currentTargetDataCell && | |
(currentTargetDataCell.parentElement as HTMLTableRowElement | null); | |
const selectDataCell = | |
currentTargetRow && | |
(currentTargetRow.firstElementChild as HTMLTableDataCellElement | null); | |
const selectInput = | |
selectDataCell && | |
(selectDataCell.firstElementChild as HTMLSelectElement | null); | |
if (!selectInput) | |
return Methods.handleUnauthorized({ | |
error_code: 504, | |
error_message: t('Input not found'), | |
title: t('App Error!'), | |
}); | |
if (currentTargetValue === '') | |
return selectInput.removeAttribute('disabled'); | |
if (selectInput.disabled) return; | |
return selectInput.setAttribute('disabled', 'true'); | |
}; | |
// set name attribute for corresponding row inputs | |
const handleOptions = (e: React.ChangeEvent<HTMLSelectElement>) => { | |
e.preventDefault(); | |
const name = e.currentTarget.value; | |
const metric = e.currentTarget | |
.nextElementSibling as HTMLSpanElement | null; | |
const currentIndex = parseInt(e.currentTarget.id); | |
const targetTable = document.getElementById(`set-table-${currentIndex}`); | |
if (e.currentTarget.value === '' || !targetTable) return; | |
const targetTableBody = targetTable.children[1].children; | |
if (!targetTableBody[0] || !metric) return; | |
const targetTableBodyInputs = [ | |
...targetTableBody[0].querySelectorAll('input.form-control'), | |
] as HTMLInputElement[]; | |
targetTableBodyInputs.forEach((v, i) => { | |
if (targetTableBodyInputs[i].name !== 'notes_sets') { | |
targetTableBodyInputs[i].name = name; | |
targetTableBodyInputs[i].step = isInt(name) ? '1' : '0.01'; | |
} | |
}); | |
metric.innerText = generateMetrics(name); | |
if (isSchedule) { | |
const resultsRow = targetTable.children[1] | |
.lastElementChild as HTMLTableRowElement; | |
const resultMetric = resultsRow.getElementsByTagName('span')[0]; | |
if (resultMetric) resultMetric.innerText = generateMetrics(name); | |
} | |
}; | |
// Generate options list based on exercise type | |
const generateOptionSelect = () => { | |
let scoreColumnOptions; | |
let length; | |
const type = exerciseItem && exerciseItem.type; | |
if (!catalog || !type) return; | |
if (type.toString() === ExerciseTypes[1]) { | |
scoreColumnOptions = CardioScoreType; | |
length = Object.keys(CardioScoreType).length / 2; | |
} else { | |
scoreColumnOptions = WorkoutScoreType; | |
length = Object.keys(WorkoutScoreType).length / 2; | |
} | |
let options: JSX.Element[] = []; | |
for (let i = 0; i < length; i++) { | |
options = [ | |
...options, | |
<option | |
key={i} | |
value={catalog.typesDisplay(scoreColumnOptions[i])} | |
style={{ | |
textTransform: 'capitalize', | |
}} | |
> | |
{catalog.typesDisplay(scoreColumnOptions[i].toLowerCase())} | |
</option>, | |
]; | |
} | |
return exercise.score_vals && | |
exercise.score_vals.length > 0 && | |
exercise.score_vals[0].value[0] && | |
exercise.score_vals[0].value.find((el) => el === 0) ? ( | |
<> | |
<select | |
disabled | |
id={`${index}`} | |
name='score_type' | |
className='form-control' | |
onChange={handleOptions} | |
defaultValue={exercise.score_type} | |
> | |
{options} | |
</select> | |
<span>{generateMetrics(exercise.score_type)}</span> | |
</> | |
) : ( | |
<> | |
<select | |
id={`${index}`} | |
name='score_type' | |
className='form-control' | |
onChange={handleOptions} | |
defaultValue={exercise.score_type} | |
> | |
{options} | |
</select> | |
<span>{generateMetrics(exercise.score_type)}</span> | |
</> | |
); | |
}; | |
// Generate Table Data cells | |
const setData = (name: string) => { | |
const scoreCount = exercise.score_count; | |
const colIndex = | |
exercise.score_cols && | |
exercise.score_cols.findIndex((el) => el === name); | |
let dataCells: JSX.Element[] = | |
colIndex > -1 | |
? [ | |
<td key={0}> | |
<input | |
type='text' | |
className='form-control' | |
name={name} | |
autoComplete="off" | |
// min={0} | |
// step={isInt(name) ? 1 : 0.01} | |
defaultValue={ | |
exercise.score_vals && | |
exercise.score_vals.length > 0 && | |
exercise.score_vals[colIndex].value[0] | |
? isInt(name) | |
? metricConverter( | |
exercise.score_vals[colIndex].value[0], | |
name, | |
metric | |
) | |
: metricConverter( | |
exercise.score_vals[colIndex].value[0], | |
name, | |
metric | |
).toFixed(2) | |
: undefined | |
} | |
onChange={toggleOptions} | |
onKeyUp={handleInputChange} | |
/> | |
{name === exercise.score_type && ( | |
<input | |
type='text' | |
name='notes_sets' | |
className='form-control' | |
defaultValue={exercise.notes_sets[colIndex]} | |
style={{ display: 'none' }} | |
/> | |
)} | |
</td>, | |
] | |
: [ | |
<td key={0}> | |
<input | |
type='text' | |
className='form-control' | |
name={name} | |
autoComplete="off" | |
// min={0} | |
// step={isInt(name) ? 1 : 0.01} | |
defaultValue={ | |
exercise.score_vals && | |
exercise.score_vals.length > 0 && | |
exercise.score_vals[0].value[0] | |
? isInt(name) | |
? metricConverter( | |
exercise.score_vals[0].value[0], | |
name, | |
metric | |
) | |
: metricConverter( | |
exercise.score_vals[0].value[0], | |
name, | |
metric | |
).toFixed(2) | |
: undefined | |
} | |
onChange={toggleOptions} | |
onKeyUp={handleInputChange} | |
/> | |
<input | |
autoComplete="off" | |
type='text' | |
name='notes_sets' | |
className='form-control' | |
defaultValue={ | |
exercise.notes_sets ? exercise.notes_sets[0] : undefined | |
} | |
style={{ display: 'none' }} | |
/> | |
</td>, | |
]; | |
if (scoreCount >= 1) { | |
for (let i = 1; i < scoreCount; i++) { | |
dataCells = [ | |
...dataCells, | |
<td key={i}> | |
<input | |
set-index={i} | |
type='text' | |
className='form-control' | |
name={name} | |
autoComplete="off" | |
// min={0} | |
// step={isInt(name) ? 1 : 0.01} | |
onKeyUp={handleInputChange} | |
defaultValue={ | |
exercise.score_vals && | |
exercise.score_vals.length > 0 && | |
exercise.score_vals[colIndex].value[i] | |
? isInt(name) | |
? metricConverter( | |
exercise.score_vals[colIndex].value[i], | |
name, | |
metric | |
) | |
: metricConverter( | |
exercise.score_vals[colIndex].value[i], | |
name, | |
metric | |
).toFixed(2) | |
: undefined | |
} | |
/> | |
{name === exercise.score_type && ( | |
<input | |
type='text' | |
name='notes_sets' | |
className='form-control' | |
defaultValue={exercise.notes_sets[i]} | |
style={{ display: 'none' }} | |
/> | |
)} | |
</td>, | |
]; | |
} | |
} | |
return dataCells; | |
}; | |
if (!tableBody) { | |
const exerciseType = exerciseItem.type; | |
const scoreType = exercise.score_type; | |
const scoreCols = exercise.score_cols; | |
// If no score type or no score cols then return template | |
if (!scoreType || !scoreCols) { | |
// If exercise type is STRENGTH or KETTLEBELLS add KG_WEIGHTS to defau;t template | |
if ( | |
`${exerciseType}` === ExerciseTypes[4] | |
) { | |
return setTableBody([ | |
<tr key={0}> | |
<td>{generateOptionSelect()}</td> | |
{setData('TIME')} | |
</tr>, | |
<tr key={1}> | |
<td> | |
<label className='form-label'> | |
{t("pause")} | |
<span | |
style={{ display: 'block', textTransform: 'lowercase' }} | |
> | |
{generateMetrics(ScoreType[3])} | |
</span> | |
</label> | |
</td> | |
{setData('PAUSE')} | |
</tr>, | |
<tr key={2}> | |
<td> | |
<label className='form-label'> | |
{t("Weights")} | |
<span | |
style={{ display: 'block', textTransform: 'lowercase' }} | |
> | |
{generateMetrics(ScoreType[4])} | |
</span> | |
</label> | |
</td> | |
{setData(ScoreType[4])} | |
</tr>, | |
]); | |
} | |
// else default template | |
return setTableBody([ | |
<tr key={0}> | |
<td>{generateOptionSelect()}</td> | |
{setData('TIME')} | |
</tr>, | |
<tr key={1}> | |
<td> | |
<label className='form-label'> | |
{t("pause")} | |
<span>{generateMetrics(ScoreType[3])}</span> | |
</label> | |
</td> | |
{setData('PAUSE')} | |
</tr>, | |
]); | |
} | |
// ... else return existing set data | |
const exisitingSetData = scoreCols.map((col, i) => { | |
return i === 0 ? ( | |
// if first column then set option return option selector & score type | |
<tr key={i}> | |
<td>{generateOptionSelect()}</td> | |
{setData(col)} | |
</tr> | |
) : ( | |
// ...else return label and optional score keys | |
<tr key={i}> | |
<td> | |
<label className='form-label'> | |
{catalog.typesDisplay(col).toLowerCase()} | |
<span>{generateMetrics(col)}</span> | |
</label> | |
</td> | |
{setData(col)} | |
</tr> | |
); | |
}); | |
return setTableBody(exisitingSetData); | |
} | |
}, [ | |
t, | |
catalog, | |
index, | |
exercise, | |
exerciseItem, | |
tableBody, | |
metric, | |
generateMetrics, | |
Methods, | |
isSchedule, | |
]); | |
// Generate Results row | |
const generateResultsRow = React.useCallback(() => { | |
// Generate result columns | |
const generateResults = () => { | |
const results: JSX.Element[] = [ | |
<td key={0}> | |
<label className='form-label'> | |
{t("Results")} | |
<span>{generateMetrics(exercise.score_type)}</span> | |
</label> | |
</td>, | |
]; | |
if (!exercise.score_count) { | |
results.push( | |
<td key={1}> | |
<input | |
className='form-control' | |
name='result_vals' | |
type='text' | |
// min={0} | |
onKeyUp={handleInputChange} | |
// step={isInt(exercise.score_type) ? 0 : 0.01} | |
defaultValue={undefined} | |
/> | |
</td> | |
); | |
return setResultRow(results); | |
} | |
for (let i = 1; i <= exercise.score_count; i++) { | |
const value = metricConverter( | |
exercise.result_vals[i - 1], | |
exercise.score_type, | |
metric | |
); | |
results.push( | |
<td key={i}> | |
<input | |
className='form-control' | |
name='result_vals' | |
type='text' | |
// min={0} | |
onKeyUp={handleInputChange} | |
// step={isInt(exercise.score_type) ? 0 : 0.01} | |
defaultValue={value && value !== 0 ? value : undefined} | |
/> | |
</td> | |
); | |
} | |
return setResultRow(results); | |
}; | |
if (!resultRow && exercise.result_vals && isSchedule) | |
return generateResults(); | |
}, [exercise, resultRow, isSchedule, metric, generateMetrics, t]); | |
// Effects | |
React.useEffect(generateHeadRow, [currentTab]); | |
React.useEffect(generateBodyRow, [currentTab]); | |
React.useEffect(generateResultsRow, [exercise, resultRow, isSchedule]); | |
React.useEffect(showConfirmColModal, [targetCol]); | |
React.useEffect(showSetNoteModal, [targetSetNotes]); | |
if (!exerciseItem || !catalog) return <></>; | |
const exerciseType = exerciseItem.type; | |
// Generate select input & options: | |
const generateOptions = (close: () => void) => { | |
let scoreColumnOptions: any; | |
let length; | |
const type = exerciseType; | |
switch (`${type}`) { | |
case ExerciseTypes[1]: | |
scoreColumnOptions = CardioScoreCloumns; | |
length = Object.keys(CardioScoreCloumns).length / 2; | |
break; | |
case ExerciseTypes[4]: | |
scoreColumnOptions = StrengthScoreColumns; | |
length = Object.keys(StrengthScoreColumns).length / 2; | |
break; | |
default: | |
scoreColumnOptions = WorkoutScoreColumns; | |
length = Object.keys(WorkoutScoreColumns).length / 2; | |
break; | |
} | |
let options: JSX.Element[] = [ | |
<li | |
key={0} | |
value={''} | |
style={{ cursor: 'pointer', textTransform: 'capitalize', color: "var(--gray)" }} | |
onClick={handleAddOptions} | |
> | |
{t("SELECT")} | |
</li>, | |
]; | |
const targetTable = document.querySelector(`#set-table-${index}`); | |
const targetTableBody = targetTable && targetTable.children[1]; | |
const targetFormLabels: any = | |
targetTableBody && targetTableBody.querySelectorAll('label.form-label'); | |
for (let i = 0; i < length; i++) { | |
const isActive = [...targetFormLabels].find( | |
(el: HTMLLabelElement | any) => | |
el.innerText | |
.replace(generateMetrics(scoreColumnOptions[i]), '') | |
.trim() | |
.toUpperCase() === catalog.typesDisplay(scoreColumnOptions[i]) | |
); | |
options = [ | |
...options, | |
<li | |
key={i + 1} | |
value={catalog.typesDisplay(scoreColumnOptions[i])} | |
style={{ cursor: 'pointer', textTransform: 'capitalize', textAlign: "left" }} | |
onClick={handleAddOptions} | |
> | |
<span className={isActive ? 'checkbox active' : 'checkbox'} style={{ marginRight: 8 }}></span> | |
{catalog.typesDisplay(scoreColumnOptions[i].toLowerCase())} | |
</li>, | |
]; | |
} | |
options = [ | |
...options, | |
<li | |
key={options.length} | |
style={{ cursor: 'pointer', textTransform: 'capitalize' }} | |
> | |
<button | |
type='button' | |
className='btn' | |
style={{ width: '100%', backgroundColor: "black", color: "white", padding: ".3rem", margin: "10px 0px 0" }} | |
onClick={() => { | |
handleDone(); | |
close() | |
}} | |
> | |
{t("Done")} | |
</button> | |
</li>, | |
]; | |
return <ul style={{ marginBottom: '0' }}>{options}</ul>; | |
}; | |
// Check if add options is required: | |
const requireOptions = () => { | |
switch (exerciseType.toString()) { | |
case ExerciseTypes[1]: | |
return true; | |
default: | |
return false; | |
} | |
}; | |
const handleInputChange = (e:any)=>{ | |
let stVal = e.target.value | |
var regex = /^[0-9.,]+$/; | |
if(regex.test(e.key)){ | |
if(e.key === '.' ){ | |
let dotScore = 0; | |
for(let i = 0; i < e.target.value.length; i++ ){ | |
if(e.target.value[i] === '.'){ | |
dotScore = dotScore +1 ; | |
} | |
} | |
if(dotScore > 1){ | |
stVal = stVal.slice(0,-1) | |
e.target.value = stVal | |
} | |
} | |
if(e.key === ','){ | |
stVal = stVal.slice(0,-1) + "." | |
let dotScore = 0; | |
for(let i = 0; i < stVal.length; i++ ){ | |
if(stVal[i] === '.'){ | |
dotScore = dotScore +1 ; | |
} | |
} | |
if(dotScore > 1){ | |
stVal = stVal.slice(0,-1) | |
return e.target.value = stVal | |
} | |
return e.target.value = stVal | |
} | |
}else{ | |
stVal = stVal.slice(0,-1) | |
e.target.value = stVal | |
} | |
} | |
// Generate table heads after add set button click | |
const generateTableHeads = () => { | |
setGenerateTableCount(1 + generateTableCount); | |
const newTableHead = document.createElement('th'); | |
const newTableSpan = document.createElement('span'); | |
const newTableIcon = document.createElement('img'); | |
const newTableBody = document.createElement('td'); | |
const newTableBodyInput = document.createElement('input'); | |
const newTableNoteSpan = document.createElement('span'); | |
const newTableNoteIcon = document.createElement('img'); | |
const newTableBodyNote = document.createElement('input'); | |
newTableSpan.className = 'del-icon'; | |
// @ts-ignore | |
newTableSpan.style.float = 'right'; | |
newTableIcon.src = ic_del; | |
newTableBodyInput.className = 'form-control'; | |
newTableBodyInput.type = 'text'; | |
newTableBodyInput.autocomplete = 'off'; | |
newTableBodyInput.id = `newGeneratedInput-${generateTableCount}`; | |
// newTableBodyInput.addEventListener('keyup', handleInputChange); | |
// newTableBodyInput.oninput = (e:any) => console.log(e); | |
// newTableBodyInput.min = '0'; | |
console.log(newTableBodyInput , ' new element') | |
newTableNoteSpan.className = 'info-icon'; | |
newTableNoteIcon.src = ic_info; | |
newTableBodyNote.style.display = 'none'; | |
newTableBodyNote.className = 'form-control'; | |
newTableBodyNote.type = 'text'; | |
newTableBodyNote.name = 'notes_sets'; | |
const targetTable = document.getElementById( | |
`set-table-${index}` | |
) as HTMLTableElement | null; | |
if (!targetTable) return; | |
const tableHead = targetTable.children[0]; // <thead/> | |
const tableBody = targetTable.children[1]; // <tbody/> | |
const tableHeadRow = tableHead.children[0]; // <th/> | |
const tableBodyRows = tableBody.children as HTMLCollectionOf<HTMLTableRowElement>; // <tr/> | |
const scoreRows = tableBodyRows.length; // Number of scores | |
newTableHead.innerHTML = `${tableHeadRow.children.length}`; | |
newTableSpan.addEventListener('click', handleRemCol); | |
newTableNoteSpan.addEventListener('click', handleSetNotes); | |
// Add icon img to span | |
newTableSpan.append(newTableIcon); | |
newTableNoteSpan.append(newTableNoteIcon); | |
// Add new span to th | |
newTableHead.append(newTableSpan, newTableNoteSpan); | |
// Add new th to table head | |
tableHeadRow.append(newTableHead); | |
// Generate inputs for each selected option | |
for (let i = 0; i < scoreRows; i++) { | |
const targetRow = tableBodyRows.item(i) as HTMLTableRowElement; | |
newTableBodyInput.value = ''; | |
if (targetRow && targetRow.cells[targetRow.cells.length - 1]) { | |
const prevColInput = targetRow.cells[targetRow.cells.length - 1] | |
.firstElementChild as HTMLInputElement | null; | |
if (i === 0) { | |
const newSelectInput = targetRow.cells[0] | |
.firstElementChild as HTMLSelectElement; | |
// console.log('val', prevColInput?.id, prevColInput) | |
if (prevColInput && prevColInput?.id!=="1") { | |
newTableBodyInput.value = prevColInput.value; | |
} | |
else{ | |
newTableBodyInput.value = "" | |
} | |
newTableBodyInput.name = newSelectInput.value; | |
// newTableBodyInput.step = isInt(newSelectInput.value) ? '1' : '0.01'; | |
newTableBody.append(newTableBodyInput, newTableBodyNote); | |
} else if (/Results/.test(targetRow.innerText.trim())) { | |
newTableBodyInput.name = 'result_vals'; | |
// newTableBodyInput.step = isInt(`${exercise.score_type}`) | |
// ? '1' | |
// : '0.01'; | |
newTableBody.append(newTableBodyInput); | |
} else { | |
// console.log('val2', prevColInput?.id, prevColInput) | |
if (prevColInput?.value){ | |
newTableBodyInput.value = prevColInput.value;} | |
else{ | |
newTableBodyInput.value="" | |
} | |
newTableBodyInput.name = targetRow.innerText.includes("Weights") ? "KG_WEIGHTS" : | |
targetRow.innerText.split('(')[0] | |
.trim() | |
.toUpperCase(); | |
// newTableBodyInput.step = isInt(newTableBodyInput.name) ? '1' : '0.01'; | |
newTableBody.append(newTableBodyInput); | |
} | |
targetRow.append(newTableBody.cloneNode(true)); | |
for (let j = 0; j < newTableBody.children.length; j++) { | |
const tableBodyNote = newTableBody.children[j].getAttribute('name'); | |
if (!tableBodyNote) return; | |
if ( | |
/notes_sets/.test(tableBodyNote) || | |
/result_vals/.test(tableBodyNote) | |
) | |
newTableBody.removeChild(newTableBody.children[j]); | |
} | |
} | |
} | |
const tableContainer = document.getElementById( | |
`table-container-${index}` | |
) as HTMLDivElement | null; | |
tableContainer && | |
tableContainer.scrollTo({ | |
left: targetTable.scrollWidth, | |
behavior: 'smooth', | |
}); | |
document.querySelector(`#newGeneratedInput-${generateTableCount}`)!.addEventListener('keyup', handleInputChange); | |
}; | |
// Generate table rows | |
const generateTableRows = (label: string) => { | |
const targetTable = document.getElementById( | |
`set-table-${index}` | |
) as HTMLTableElement | null; | |
if (!targetTable) return; | |
const tableHeadRow = targetTable.children[0] | |
.children[0] as HTMLTableRowElement; | |
const setNumber = tableHeadRow.children.length; | |
const tableBody = targetTable.children[1] as HTMLTableRowElement; | |
// Check if entry already exists in the table: | |
const targetEntry = [...tableBody.children].find((row, i) => { | |
const td = row.firstElementChild as HTMLTableDataCellElement; | |
if (!td) return false; | |
if (i === 0) { | |
const selectInput = td.firstElementChild as HTMLSelectElement; | |
if (selectInput && selectInput.value === label) return true; | |
} | |
const labelElement = td.firstElementChild as HTMLLabelElement; | |
if ( | |
labelElement.innerText | |
.replace(generateMetrics(label), '') | |
.toUpperCase() | |
.trim() === label | |
) | |
return true; | |
return false; | |
}) as HTMLTableRowElement | undefined; | |
// If entry exists & not first row then remove corresponding row: | |
if (targetEntry) { | |
if (targetEntry.rowIndex > 1) { | |
return targetEntry.remove(); | |
} | |
// Prevent adding duplicate rows | |
return alert(`${label.toLowerCase()} ${t("already exists")}`); | |
} | |
// If entry doesn't exist then generate new table row: | |
const newTableBodyRow = document.createElement('tr'); | |
const newTableBodyRowData1 = document.createElement('td'); | |
const newTableBodyRowData2 = document.createElement('td'); | |
const newTableBodyRowDataLabel = document.createElement('label'); | |
const newTableBodyRowDataSpan = document.createElement('span'); | |
const newTableBodyRowDataInput = document.createElement('input'); | |
newTableBodyRowDataLabel.className = 'form-label'; | |
newTableBodyRowDataLabel.innerText = label.toLowerCase(); | |
Object.assign(newTableBodyRowDataLabel.style, { | |
textTransform: 'capitalize', | |
textAlign: 'left', | |
width: '100%', | |
}); | |
newTableBodyRowDataSpan.innerText = generateMetrics(label); | |
Object.assign(newTableBodyRowDataSpan.style, { | |
display: 'block', | |
textAlign: 'left', | |
width: '100%', | |
textTransform: 'lowercase', | |
}); | |
// newTableBodyRowDataLabel.addEventListener('click', handleRemRow); | |
newTableBodyRowDataInput.className = 'form-control'; | |
newTableBodyRowDataInput.name = label; | |
newTableBodyRowDataInput.type = 'number'; | |
newTableBodyRowDataInput.min = '0'; | |
newTableBodyRowDataInput.step = isInt(label) ? '1' : '0.01'; | |
newTableBodyRowDataInput.addEventListener('invalid', (e) => | |
console.log(e.currentTarget) | |
); | |
// Append the span element to the new label | |
newTableBodyRowDataLabel.append(newTableBodyRowDataSpan); | |
// Append the label element to the new td. | |
newTableBodyRowData1.append(newTableBodyRowDataLabel); | |
// Append the input element to the new td. | |
newTableBodyRowData2.append(newTableBodyRowDataInput); | |
// Append the new td to the new tr. | |
newTableBodyRow.append(newTableBodyRowData1); | |
// Append new inputs according to set number. | |
for (let i = 1; i < setNumber; i++) { | |
const newTableRowData = newTableBodyRowData2.cloneNode( | |
true | |
) as HTMLTableDataCellElement; | |
const existingValue = | |
exercise.score_vals && | |
exercise.score_vals.find((el) => el.type === label); | |
if (existingValue && newTableRowData.children[0]) | |
newTableRowData.children[0].setAttribute( | |
'value', | |
JSON.stringify(existingValue.value[i - 1]) | |
); | |
newTableBodyRow.append(newTableRowData); | |
} | |
// Append the new tr before results row | |
if (isSchedule) { | |
const resultRow = tableBody.lastChild; | |
tableBody.insertBefore(newTableBodyRow, resultRow); | |
// Append the new tr to the current table. | |
} else { | |
tableBody.append(newTableBodyRow); | |
} | |
}; | |
// Handle add set click | |
const handleAddSet = (e: React.MouseEvent<HTMLButtonElement>) => { | |
e.preventDefault(); | |
generateTableHeads(); | |
}; | |
// Handle options selection | |
const handleAddOptions = (e: React.MouseEvent<HTMLLIElement>) => { | |
e.preventDefault(); | |
const label = e.currentTarget.getAttribute('value'); | |
if (label === 'all') { | |
// TODO add select all functionality | |
} | |
const targetCheckBox = e.currentTarget.firstElementChild; | |
if (targetCheckBox) targetCheckBox.classList.toggle('active'); | |
if (!label || label === '') return; | |
let labelArray = [...labelState]; | |
labelArray.push(label) | |
setLabelState(labelArray) | |
}; | |
const handleDone = () => { | |
if (!labelState.length || labelState[0] === '') return; | |
labelState.forEach((v: string) => { | |
generateTableRows(v.toUpperCase()); | |
}) | |
setLabelState([]) | |
} | |
return ( | |
<> | |
<div className={`button-group`} style={{ padding: '5px 0px' }}> | |
{requireOptions() && ( | |
<Popup | |
trigger={ | |
<button | |
type='button' | |
style={{ | |
position: 'relative', | |
float: 'left', | |
textTransform: "none", | |
userSelect: 'none', | |
marginTop: 0, | |
marginRight: "10px", | |
}} | |
className='btn btn-primary' | |
> | |
{t("Add option")} | |
</button> | |
} | |
arrow={true} | |
arrowStyle={{ left: "22%" }} | |
position={'bottom center'} | |
contentStyle={{ | |
textAlign: 'left', | |
fontSize: 14, | |
maxWidth: '140px', | |
minWidth: '100px', | |
cursor: 'pointer', | |
borderRadius: '5px', | |
backgroundColor: 'var(--white)', | |
color: 'black', | |
}} | |
> | |
{generateOptions} | |
</Popup> | |
)} | |
<button | |
type='button' | |
style={{ float: 'left', textTransform: "none", marginTop: 0 }} | |
className={`btn btn-success ${classes.addSetBtn}`} | |
onClick={handleAddSet} | |
> | |
{t("Add set")} | |
</button> | |
</div> | |
<div id={`table-container-${index}`} className={classes.tableContainer}> | |
<Table id={`set-table-${index}`} className={`set-table ${classes.tableToppy}`}> | |
<TableHead>{tableHead}</TableHead> | |
<TableBody> | |
{tableBody} | |
{/* {console.log('resultRow', resultRow, tableHead)} */} | |
{isSchedule && resultRow && | |
<tr>{resultRow}</tr> | |
// : | |
// <tr> | |
// <td><label className="form-label">Results<span>(mm.ss)</span></label></td> | |
// <td> | |
// <input | |
// className='form-control' | |
// name='result_vals' | |
// type='number' | |
// min={0} | |
// onKeyUp={handleInputChange} | |
// step={isInt(exercise.score_type) ? 0 : 0.01} | |
// defaultValue={undefined} | |
// /> | |
// </td> | |
// </tr> | |
} | |
</TableBody> | |
</Table> | |
</div> | |
<ConfirmCol | |
show={showConfirmCol} | |
onSubmit={confirmRemCol} | |
onHide={() => setTargetCol(null)} | |
title={t('Remove Column')} | |
message={t('Are you sure you want to remove this column?')} | |
/> | |
<Dialog | |
fullWidth | |
maxWidth='sm' | |
open={showSetNotes} | |
onClose={() => setTargetSetNotes(null)} | |
className={classes.dialog} | |
> | |
<DialogTitle> | |
{t("Set")} {targetSetNotes} {t("Notes")}{' '} | |
<Clear onClick={() => setTargetSetNotes(null)} /> | |
</DialogTitle> | |
<DialogContent> | |
<form id='set-note' onSubmit={saveSetNotes}> | |
<textarea | |
name='notes' | |
defaultValue={ | |
targetSetNotes && exercise.notes_sets | |
? exercise.notes_sets[targetSetNotes - 1] | |
: undefined | |
} | |
placeholder={t(`Notes for set ${targetSetNotes}`)} | |
/> | |
</form> | |
</DialogContent> | |
<DialogActions> | |
<Button variant='contained' type='submit' form='set-note'> | |
{t("Save")} | |
</Button> | |
</DialogActions> | |
</Dialog> | |
</> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment