diff --git a/src/components/checkbox-button.jsx b/src/components/checkbox-button.jsx new file mode 100644 index 000000000..be68edca4 --- /dev/null +++ b/src/components/checkbox-button.jsx @@ -0,0 +1,68 @@ +import React, { useEffect, useState } from 'react'; +import './checkbox-button.scss'; + +const CheckboxButton = ({ labelOn, labelOff, onChange, reset, customStyle, fontSize,ID }) => { + const [isChecked, setIsChecked] = useState(false); + const [isFocused, setIsFocused] = useState(false); + + useEffect(() => { + if (reset) { + setIsChecked(false); + onChange(false); //to tell our x button to reset + } + }, [reset, onChange]); + + + const handleDivClick = () => { + const newCheckedState = !isChecked; + setIsChecked(newCheckedState); + onChange(newCheckedState); + }; + + const handleKeyPress = (event) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + handleDivClick(); + } + }; + + const handleFocus = (event) => { + setIsFocused(true); + const parentDiv = event.target.closest('.CheckboxButton'); + if (parentDiv) { + parentDiv.classList.add('focused'); + } + }; + + const handleBlur = (event) => { + setIsFocused(false); + const parentDiv = event.target.closest('.CheckboxButton'); + if(parentDiv) { + parentDiv.classList.remove('focused'); + } + }; + + return ( +
+ + {}} + tabIndex="0" + id={ID} + /> +
+ ); +}; + +export default CheckboxButton; + diff --git a/src/components/checkbox-button.scss b/src/components/checkbox-button.scss new file mode 100644 index 000000000..03bf278c3 --- /dev/null +++ b/src/components/checkbox-button.scss @@ -0,0 +1,48 @@ +$default-font-size: 0.7rem; + +.CheckboxButton { + padding: 1.5px; + cursor: pointer; + background-color: lightgray; + color: black; + text-align: center; + border-radius: 4px; + // min-width: 80px; + display: flex; + justify-content: center; + align-items: center; + outline: none; + + + label { + font-size: $default-font-size; + cursor: pointer; + font-weight: normal; + text-align: center; + } + + input { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; + } + + &.on { + background-color: #d9a0ff; + color: black; + } + + &.off { + background-color: #5e5c5c; + color: white; + } + + &.focused { + outline: 3px solid #3b82f6; + // outline-offset: 2px; + } +} diff --git a/src/components/my-widgets-page.jsx b/src/components/my-widgets-page.jsx index 1fe6bc3b1..c9338d4bc 100644 --- a/src/components/my-widgets-page.jsx +++ b/src/components/my-widgets-page.jsx @@ -407,9 +407,6 @@ const MyWidgetsPage = () => { {alertDialogRender}
-
- {mainContentRender()} -
{ beardMode={beardMode} beards={beards} /> +
+ {mainContentRender()} +
) } -export default MyWidgetsPage \ No newline at end of file +export default MyWidgetsPage diff --git a/src/components/my-widgets-page.scss b/src/components/my-widgets-page.scss index 7a7e4f220..ac6d3a11a 100644 --- a/src/components/my-widgets-page.scss +++ b/src/components/my-widgets-page.scss @@ -3,6 +3,7 @@ .my_widgets { display: flex; justify-content: center; + margin-bottom: 100px; .error { width: 80%; @@ -14,7 +15,7 @@ position: relative; display: flex; align-items: flex-start; - margin: 0 auto 100px auto; + // margin: 0 auto 100px auto; // width: 970px; overflow-x: visible; @@ -75,7 +76,7 @@ z-index: 100; width: 700px; min-height: 850px; - margin: 0 0 0 260px; + // margin: 0 0 0 260px; background: #fff; } @@ -84,12 +85,12 @@ // background: #fff; // border: rgba(0, 0, 0, 0.11) 1px solid; // box-shadow: 1px 3px 10px #888; - width: 250px; + min-width: 250px; height: 100%; margin: 0; // padding: 0 20px 0 0; - position: absolute; - top: 0; + // position: absolute; + // top: 0; // left: 5px; z-index: 80; // border-top-left-radius: 10px; @@ -108,9 +109,63 @@ // background: linear-gradient(#83caf3 16%, #37a9e6 89%); } + .searchContainer { + //maybe some display flex + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .filtersGroup { + // display: none; + display: grid; + //make the grid span out for 4 columsn but have the elements take up 2 spaces + grid-template-columns: 1fr 1fr 1fr 1fr; + // grid-template-columns: repeat(auto-fit, 50%); + justify-content: center; + overflow: hidden; + width: 0; + max-height: 0; + opacity: 0; + transition: max-height 0.3s ease, opacity 0.3s ease; + gap: 5px; + padding-bottom: 0.5rem; + padding-top: 0.1rem; + + div { + grid-column: span 2; + margin: 2px; + } + + //this is to style the last item in the grid so its centered + div:last-child:nth-child(odd) { + grid-column: span 4; /* If last item is by itself, span across 4 columns to center */ + grid-column-end: 5; + justify-self: center; + min-width: 122.5px; + } + + &.show { + // display: flex; + max-height: 500px; + opacity: 1; + width: 100%; + padding: 5px; + box-sizing: border-box; + } + } + + .searchBarGroup{ + display: flex; + align-items: center; + gap: 0.1rem; + } + + .search { - position: relative; - padding: 10px 0 30px 0; + // position: relative; + padding: 10px 0 0 0; background: #ebebeb; @@ -130,7 +185,7 @@ } .search-icon { - position: absolute; + // position: absolute; top: 12px; left: 13px; height: 14px; @@ -143,16 +198,16 @@ } .search-close { - position: absolute; - top: 8px; - right: 40px; - margin-top: 1px; + // position: absolute; + // top: 8px; + // right: 40px; + margin-top: -1px; color: rgb(221, 221, 221); cursor: pointer; } .textbox { - position: absolute; + // position: absolute; border: none; outline: none; width: 165px; @@ -166,7 +221,8 @@ } .textbox-background { - position: absolute; + // position: absolute; + display: flex; width: 205px; height: 20px; left: 9px; @@ -174,6 +230,7 @@ border: solid 1px #b0b0b0; border-radius: 12px; } + } // .courses { diff --git a/src/components/my-widgets-side-bar.jsx b/src/components/my-widgets-side-bar.jsx index 14e933b0a..044dbda06 100644 --- a/src/components/my-widgets-side-bar.jsx +++ b/src/components/my-widgets-side-bar.jsx @@ -1,26 +1,77 @@ import React, { useState, useMemo } from 'react' +// import { useQuery } from 'react-query' import MyWidgetsInstanceCard from './my-widgets-instance-card' import LoadingIcon from './loading-icon' +import CheckboxButton from './checkbox-button' +import { apiGetScoreSummaryBatch } from '../util/api'; + const MyWidgetsSideBar = ({ instances, isFetching, selectedId, onClick, beardMode, beards }) => { const [searchText, setSearchText] = useState('') + const [filterDrafts, setFilterDrafts] = useState(false); + const [filterPublished, setFilterPublished] = useState(false); + const [filterAttempts, setFilterAttempts] = useState(false); + const [filterGuestAccess, setFilterGuestAccess] = useState(false); + const [filterOpen, setFilterOpen] = useState(false); // Separate filter for open widgets + const [filterExpired, setFilterExpired] = useState(false); + const [filterEmbedded, setFilterEmbedded] = useState(false); + const [resetFilters, setResetFilters] = useState(false); + const [showFilters, setShowFilters] = useState(false); + const handleToggleFilters = (isChecked) => { + setShowFilters(isChecked); + }; const hiddenSet = useMemo(() => { const result = new Set() - if (searchText == '') return result - const re = RegExp(searchText, 'i') + const currentTime = Math.floor(Date.now()/1000); instances.forEach(i => { - if (!re.test(`${i.name} ${i.widget.name} ${i.id}`)) { + const matchesSearch = re.test(`${i.name} ${i.widget.name} ${i.id}`) + const matchesDrafts = filterDrafts ? i.is_draft : true + const matchesPublished = filterPublished ? !i.is_draft : true + const hasAttempts = filterAttempts ? i.attempts !== -1 : true; + const hasGuestAccess = filterGuestAccess ? i.guest_access : true; + //filtering open widgets + const isOpen = i.open_at <= currentTime && (i.close_at === -1 || i.close_at > currentTime); + const matchesOpen = filterOpen ? isOpen : true; + //filtering expired widgets + const isExpired = i.close_at !== -1 && i.close_at < currentTime; + const matchesExpired = filterExpired ? isExpired : true; + //filtering for embedded widgets + const isEmbedded = filterEmbedded ? i.is_embedded : true; + + if (!matchesSearch || !matchesDrafts || !matchesPublished || + !hasAttempts || !hasGuestAccess || !matchesOpen || !matchesExpired || !isEmbedded ) { result.add(i.id) } }) return result - }, [instances, searchText]) + }, [instances, searchText, filterDrafts, filterPublished, filterAttempts, filterGuestAccess, + filterOpen, filterExpired, filterEmbedded]) const handleSearchInputChange = e => setSearchText(e.target.value) - const handleSearchCloseClick = () => setSearchText('') + + const handleSearchCloseClick = () => { + setSearchText(''); + setFilterDrafts(false); + setFilterPublished(false); + setFilterAttempts(false); + setFilterGuestAccess(false); + setFilterOpen(false); + setFilterExpired(false); + setFilterEmbedded(false); + setResetFilters(true); + setTimeout(() => setResetFilters(false), 0); // Clear reset after it propagates + }; + + const handleDraftsChange = (isChecked) => setFilterDrafts(isChecked); + const handlePublishedChange = (isChecked) => setFilterPublished(isChecked); + const handleAttemptsChange = (isChecked) => setFilterAttempts(isChecked); + const handleGuestAccessChange = (isChecked) => setFilterGuestAccess(isChecked); + const handleOpenChange = isChecked => setFilterOpen(isChecked); + const handleExpiredChange = isChecked => setFilterExpired(isChecked); + const handleEmbeddedChange = isChecked => setFilterEmbedded(isChecked); let widgetInstanceElementsRender = null if (!isFetching || instances?.length > 0) { @@ -40,31 +91,106 @@ const MyWidgetsSideBar = ({ instances, isFetching, selectedId, onClick, beardMod let searchBoxRender = null if (isFetching) { - searchBoxRender = + searchBoxRender =
Loading Widgets...
} else { searchBoxRender = -
-
- -
- - - +
+
+
+
+ {/* Search Icon */} +
+ + + +
+ + {/* Search Input */} + + + {/* Search Close */} +
+ x +
+ +
+
-
- x + + {/* add a button to toggle filters */} + + +
+
+ + + + + + +
+
} @@ -81,4 +207,4 @@ const MyWidgetsSideBar = ({ instances, isFetching, selectedId, onClick, beardMod ) } -export default MyWidgetsSideBar \ No newline at end of file +export default MyWidgetsSideBar