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