website: Enable label when clicking on disabled slider

This commit is contained in:
Adrian Cowan
2023-01-13 20:52:01 +11:00
parent 5d1b03b02d
commit 9f23ffb1b6
+99 -52
View File
@@ -22,7 +22,7 @@ import {
useId, useId,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { FlagIcon, QuestionMarkCircleIcon } from "@heroicons/react/20/solid"; import { FlagIcon, QuestionMarkCircleIcon } from "@heroicons/react/20/solid";
import { useEffect, useState } from "react"; import { useEffect, useReducer } from "react";
import fetcher from "src/lib/fetcher"; import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster"; import poster from "src/lib/poster";
import { Message } from "src/types/Conversation"; import { Message } from "src/types/Conversation";
@@ -30,10 +30,38 @@ import { colors } from "styles/Theme/colors";
import useSWR from "swr"; import useSWR from "swr";
import useSWRMutation from "swr/mutation"; import useSWRMutation from "swr/mutation";
interface textFlagLabels { interface Label {
attributeName: string; name: string;
labelText: string; display_text: string;
additionalExplanation?: string; help_text: string;
}
interface LoadLabelsAction {
type: "load_labels";
labels: Label[];
}
interface UpdateValueAction {
type: "update_value";
label_index: number;
value: number;
}
interface ToggleLabelAction {
type: "toggle_label";
label_index: number;
check: boolean;
}
interface LabelValue {
label: Label;
checked: boolean;
value: number;
}
interface FlagReportState {
label_values: LabelValue[];
submittable: boolean;
} }
interface FlaggableElementProps { interface FlaggableElementProps {
@@ -42,9 +70,34 @@ interface FlaggableElementProps {
} }
export const FlaggableElement = (props: FlaggableElementProps) => { export const FlaggableElement = (props: FlaggableElementProps) => {
const [labels, setLabels] = useState([]); const [report, updateReport] = useReducer(
const [checkboxValues, setCheckboxValues] = useState([]); (state: FlagReportState, action: LoadLabelsAction | UpdateValueAction | ToggleLabelAction): FlagReportState => {
const [sliderValues, setSliderValues] = useState([]); const makeState = (label_values: LabelValue[]): FlagReportState => {
const submittable = label_values.map(({ checked }) => checked).some(Boolean);
return { label_values, submittable };
};
switch (action.type) {
case "load_labels":
return makeState(
action.labels.map((label) => {
return { label, checked: false, value: 1 };
})
);
case "toggle_label": {
const values_copy = state.label_values.slice();
values_copy[action.label_index].checked = action.check;
return makeState(values_copy);
}
case "update_value": {
const values_copy = state.label_values.slice();
values_copy[action.label_index].value = action.value;
return makeState(values_copy);
}
}
},
{ label_values: [], submittable: false }
);
const [isEditing, setIsEditing] = useBoolean(); const [isEditing, setIsEditing] = useBoolean();
const backgroundColor = useColorModeValue("gray.200", "gray.700"); const backgroundColor = useColorModeValue("gray.200", "gray.700");
@@ -54,14 +107,7 @@ export const FlaggableElement = (props: FlaggableElementProps) => {
return; return;
} }
const { valid_labels } = data; const { valid_labels } = data;
const newLabels = valid_labels.map((valid_label) => ({ updateReport({ type: "load_labels", labels: valid_labels });
attributeName: valid_label.name,
labelText: valid_label.display_text,
additionalExplanation: valid_label.help_text,
}));
setSliderValues(new Array(newLabels.length).fill(1));
setCheckboxValues(new Array(newLabels.length).fill(false));
setLabels(newLabels);
}, [data, isLoading]); }, [data, isLoading]);
const { trigger } = useSWRMutation("/api/set_label", poster, { const { trigger } = useSWRMutation("/api/set_label", poster, {
@@ -72,9 +118,9 @@ export const FlaggableElement = (props: FlaggableElementProps) => {
const submitResponse = () => { const submitResponse = () => {
const label_map: Map<string, number> = new Map(); const label_map: Map<string, number> = new Map();
labels.forEach((flag, i) => { report.label_values.forEach(({ label, checked, value }) => {
if (checkboxValues[i]) { if (checked) {
label_map.set(flag.attributeName, sliderValues[i]); label_map.set(label.name, value);
} }
}); });
trigger({ trigger({
@@ -84,19 +130,11 @@ export const FlaggableElement = (props: FlaggableElementProps) => {
}); });
}; };
const handleCheckboxState = (isChecked, idx) => { const handleCheckboxState = (checked, label_index) => {
setCheckboxValues( updateReport({ type: "toggle_label", label_index, check: checked });
checkboxValues.map((val, i) => {
return i === idx ? isChecked : val;
})
);
}; };
const handleSliderState = (newVal, idx) => { const handleSliderState = (value, label_index) => {
setSliderValues( updateReport({ type: "update_value", label_index, value });
sliderValues.map((val, i) => {
return i === idx ? newVal : val;
})
);
}; };
return ( return (
@@ -127,20 +165,20 @@ export const FlaggableElement = (props: FlaggableElementProps) => {
<PopoverCloseButton /> <PopoverCloseButton />
</div> </div>
<PopoverBody> <PopoverBody>
{labels.map((option, i) => ( {report.label_values.map(({ label, checked, value }, i) => (
<FlagCheckbox <FlagCheckbox
option={option} label={label}
key={i} key={i}
idx={i} idx={i}
checkboxValues={checkboxValues} checked={checked}
sliderValues={sliderValues} sliderValue={value}
checkboxHandler={handleCheckboxState} checkboxHandler={handleCheckboxState}
sliderHandler={handleSliderState} sliderHandler={handleSliderState}
/> />
))} ))}
<Flex justify="center"> <Flex justify="center">
<Button <Button
isDisabled={!checkboxValues.some(Boolean)} isDisabled={!report.submittable}
onClick={submitResponse} onClick={submitResponse}
className={`bg-indigo-600 text-${useColorModeValue( className={`bg-indigo-600 text-${useColorModeValue(
colors.light.text, colors.light.text,
@@ -157,17 +195,17 @@ export const FlaggableElement = (props: FlaggableElementProps) => {
}; };
interface FlagCheckboxProps { interface FlagCheckboxProps {
option: textFlagLabels; label: Label;
idx: number; idx: number;
checkboxValues: boolean[]; checked: boolean;
sliderValues: number[]; sliderValue: number;
checkboxHandler: (newVal: boolean, idx: number) => void; checkboxHandler: (newVal: boolean, idx: number) => void;
sliderHandler: (newVal: number, idx: number) => void; sliderHandler: (newVal: number, idx: number) => void;
} }
export function FlagCheckbox(props: FlagCheckboxProps): JSX.Element { export function FlagCheckbox(props: FlagCheckboxProps): JSX.Element {
let AdditionalExplanation = null; let AdditionalExplanation = null;
if (props.option.additionalExplanation) { if (props.label.help_text) {
AdditionalExplanation = ( AdditionalExplanation = (
<a href="#" className="group flex items-center space-x-2.5 text-sm "> <a href="#" className="group flex items-center space-x-2.5 text-sm ">
<QuestionMarkCircleIcon <QuestionMarkCircleIcon
@@ -190,28 +228,37 @@ export function FlagCheckbox(props: FlagCheckboxProps): JSX.Element {
<Flex gap={1}> <Flex gap={1}>
<Checkbox <Checkbox
id={id} id={id}
isChecked={props.checked}
onChange={(e) => { onChange={(e) => {
props.checkboxHandler(e.target.checked, props.idx); props.checkboxHandler(e.target.checked, props.idx);
}} }}
/> />
<label className="text-sm form-check-label" htmlFor={id}> <label className="text-sm form-check-label" htmlFor={id}>
<span className={labelTextClass}>{props.option.labelText}</span> <span className={labelTextClass}>{props.label.display_text}</span>
{AdditionalExplanation} {AdditionalExplanation}
</label> </label>
<Spacer /> <Spacer />
<Slider <div
width="100px" onClick={() => {
isDisabled={!props.checkboxValues[props.idx]} if (!props.checked) {
defaultValue={100} props.checkboxHandler(true, props.idx);
onChangeEnd={(val) => { }
props.sliderHandler(val / 100, props.idx);
}} }}
> >
<SliderTrack> <Slider
<SliderFilledTrack /> width="100px"
<SliderThumb /> isDisabled={!props.checked}
</SliderTrack> defaultValue={100}
</Slider> onChangeEnd={(val) => {
props.sliderHandler(val / 100, props.idx);
}}
>
<SliderTrack>
<SliderFilledTrack />
<SliderThumb />
</SliderTrack>
</Slider>
</div>
</Flex> </Flex>
); );
} }