diff --git a/src/lib/components/dropdowns/index.tsx b/src/lib/components/dropdowns/index.tsx index 804b7cd4cdc69aabe660324c5cdfc2d9a57866cf..579862c1846f3b5ff33ae86ba3930444049edb34 100644 --- a/src/lib/components/dropdowns/index.tsx +++ b/src/lib/components/dropdowns/index.tsx @@ -20,6 +20,7 @@ type DropdownTriggerProps = { onClick?: () => void; children?: ReactNode; noDropdownArrow?: boolean; + validateFlag?: boolean; }; export function DropdownTrigger({ @@ -32,6 +33,7 @@ export function DropdownTrigger({ noDropdownArrow = false, popover = true, children = undefined, + validateFlag = true, }: DropdownTriggerProps) { const paddingClass = size === 'xs' ? 'py-0' : size === 'sm' ? 'px-1 py-1' : size === 'md' ? 'px-2 py-1' : 'px-4 py-2'; const textSizeClass = size === 'xs' ? 'text-xs' : size === 'sm' ? 'text-sm' : size === 'md' ? 'text-base' : 'text-lg'; @@ -51,7 +53,7 @@ export function DropdownTrigger({ <div className={`inline-flex w-full truncate justify-between items-center gap-x-1.5 ${variantClass} ${textSizeClass} ${paddingClass} text-secondary-900 shadow-sm ${ noDropdownArrow ? `pointer-events-none cursor-default` : '' - } ${disabled ? ` cursor-not-allowed text-secondary-400 bg-secondary-100` : 'cursor-pointer'} pl-1 truncate`} + } ${disabled ? ` cursor-not-allowed text-secondary-400 bg-secondary-100` : 'cursor-pointer'} pl-1 truncate ${!validateFlag ? 'border border-danger-600' : ' border border-secondary-200'} `} > <span className={`text-${size}`}>{title}</span> {!noDropdownArrow && <Icon component="icon-[ic--baseline-arrow-drop-down]" size={16} />} diff --git a/src/lib/components/inputs/DropdownInput.tsx b/src/lib/components/inputs/DropdownInput.tsx index 3337eac70ae746b76728ecf64f5eb913f8cb2610..94ff9f50aeadcd42e101d0891b9f58e900f1bfcb 100644 --- a/src/lib/components/inputs/DropdownInput.tsx +++ b/src/lib/components/inputs/DropdownInput.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import { KeyboardEvent, useEffect, useRef, useState } from 'react'; import { DropdownContainer, DropdownItem, DropdownItemContainer, DropdownTrigger } from '../dropdowns'; import Info from '../info'; import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'; @@ -31,7 +31,7 @@ const AutocompleteRenderer = ({ }: { onChange: (e: string) => void; value: string; - onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void; + onKeyDown?: (e: KeyboardEvent<HTMLInputElement>) => void; }) => { const ref = useRef<HTMLInputElement>(null); @@ -67,10 +67,11 @@ export const DropdownInput = ({ autocomplete = false, onKeyDown, info, + validateFlag = true, }: DropdownProps) => { - const [isDropdownOpen, setIsDropdownOpen] = React.useState<boolean>(false); - const [filterInput, setFilterInput] = React.useState<string | number | undefined>(value); - const [filteredOptions, setFilteredOptions] = React.useState<(string | number | { [key: string]: string })[]>(options); + const [isDropdownOpen, setIsDropdownOpen] = useState<boolean>(false); + const [filterInput, setFilterInput] = useState<string | number | undefined>(value); + const [filteredOptions, setFilteredOptions] = useState<(string | number | { [key: string]: string })[]>(options); useEffect(() => { setFilteredOptions(options); @@ -93,7 +94,7 @@ export const DropdownInput = ({ setFilteredOptions(newFilteredOptions); }; - function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) { + function handleKeyDown(e: KeyboardEvent<HTMLInputElement>) { if (e.key === 'Enter') { if (onChange) { onChange(filterInput as string); @@ -131,6 +132,7 @@ export const DropdownInput = ({ size={size} className="cursor-pointer w-full" disabled={disabled} + validateFlag={validateFlag} onClick={() => { setIsDropdownOpen(!isDropdownOpen); }} diff --git a/src/lib/components/inputs/EmailInput.tsx b/src/lib/components/inputs/EmailInput.tsx index bccab45b5e4ea873436be4ddfcda31d0f2206b27..0cbd2c19f23de701e3ebf03263e4e9ddbee2e572 100644 --- a/src/lib/components/inputs/EmailInput.tsx +++ b/src/lib/components/inputs/EmailInput.tsx @@ -11,6 +11,7 @@ export const EmailInput = ({ onEmailsChange, className, label, + validateFlag = true, }: EmailProps) => { const [emails, setEmails] = useState<string[]>(value); const [currentInput, setCurrentInput] = useState<string>(''); @@ -89,6 +90,7 @@ export const EmailInput = ({ placeholder={placeholder} className="w-full outline-none text-sm text-gray-700" label="" + validateFlag={validateFlag} errorText={errorText} /> </div> diff --git a/src/lib/components/inputs/TextInput.tsx b/src/lib/components/inputs/TextInput.tsx index 42a8ad0ad0b7172dd4d5be5f40b71728223e62fb..39416bad006db912d26a9b3ff86048e43bd00001 100644 --- a/src/lib/components/inputs/TextInput.tsx +++ b/src/lib/components/inputs/TextInput.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import Info from '../info'; import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'; import styles from './inputs.module.scss'; @@ -21,10 +21,15 @@ export const TextInput = ({ tooltip, info, className, + validateFlag = true, ...props }: TextProps) => { const [isValid, setIsValid] = useState<boolean>(true); + useEffect(() => { + setIsValid(validateFlag); + }, [validateFlag]); + const handleInputChange = (inputValue: string) => { if (required && validate) { setIsValid(validate(inputValue)); @@ -55,8 +60,8 @@ export const TextInput = ({ type={visible ? 'text' : 'password'} placeholder={placeholder} className={ - `${size} bg-light border border-secondary-200 placeholder-secondary-400 w-auto min-w-6 shrink-0 grow-1 focus:outline-none block focus:ring-1 ${ - isValid ? '' : 'input-error' + `${size} bg-light placeholder-secondary-400 w-auto min-w-6 shrink-0 grow-1 focus:outline-none block focus:ring-1 ${!validateFlag ? 'border border-danger-600' : ' border border-secondary-200'} ${ + !isValid || !validateFlag ? 'input-error' : '' }` + (className ? ` ${className}` : '') } value={value.toString()} diff --git a/src/lib/components/inputs/types.ts b/src/lib/components/inputs/types.ts index 2fc9c75bd0fe1193ae3db966506920e600a0e91d..7f512c8de45f17611cf266c56c4218f9fb92399e 100644 --- a/src/lib/components/inputs/types.ts +++ b/src/lib/components/inputs/types.ts @@ -37,6 +37,7 @@ export type TextProps = CommonInputProps<'text', string> & { onKeyUp?: (e: React.KeyboardEvent<HTMLInputElement>) => void; onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void; autoFocus?: boolean; + validateFlag?: boolean; }; export type NumberProps = CommonInputProps<'number', number> & { @@ -90,6 +91,7 @@ export type DropdownProps = CommonInputProps<'dropdown', number | string | undef options: number[] | string[] | { [key: string]: string }[]; autocomplete?: boolean; onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void; + validateFlag?: boolean; }; export type EmailProps = { @@ -103,6 +105,7 @@ export type EmailProps = { visible?: boolean; disabled?: boolean; className?: string; + validateFlag?: boolean; onEmailsChange?: (emails: string[]) => void; }; diff --git a/src/lib/insight-sharing/FormInsight.tsx b/src/lib/insight-sharing/FormInsight.tsx index 7c35ddedf436e6161e685500f88deef5f29d03d7..f0845f12b740d827b1795c800f4b2b2f1a61c5db 100644 --- a/src/lib/insight-sharing/FormInsight.tsx +++ b/src/lib/insight-sharing/FormInsight.tsx @@ -28,13 +28,22 @@ export function FormInsight(props: Props) { hour: number; dayOfWeek?: (typeof FrequencyWeekDays)[number]; }>({ - frequency: 'Specific Day of the Week', + frequency: '', minute: 0, hour: 0, - dayOfWeek: 'SUN', + dayOfWeek: '', + }); + + const [inputErrors, setInputErrors] = useState<{ [key: string]: boolean }>({ + name: false, + recipients: false, + frequency: false, + minutes: false, + hours: false, + frequencyDayOfWeek: false, + nodeLabel: false, }); - const [valid, setValid] = useState(true); const [selectedQueryID, setSelectedQueryID] = useState<{ label: string; queryId: number; entities: string[] }>({ label: '', queryId: 0, @@ -62,6 +71,7 @@ export function FormInsight(props: Props) { } if (props.insight.frequency && props.insight.frequency !== '') { const [minute, hour, , , dayOfWeek] = props.insight.frequency.split(' '); + setFrequency({ frequency: dayOfWeek === '*' ? (hour === '*' ? 'Hourly' : 'Daily') : 'Specific Day of the Week', minute: parseInt(minute), @@ -70,10 +80,10 @@ export function FormInsight(props: Props) { }); } else { setFrequency({ - frequency: 'Specific Day of the Week', + frequency: '', minute: 0, hour: 0, - dayOfWeek: 'SUN', + dayOfWeek: '', }); } @@ -97,24 +107,56 @@ export function FormInsight(props: Props) { }; }, [props.insight]); - useEffect(() => { - if (!localInsight.name || localInsight.name.trim() === '') { - setValid(false); - } else if (frequency.hour < 0 || frequency.hour > 23) { - setValid(false); - } else if (frequency.minute < 0 || frequency.minute > 59) { - setValid(false); - } else { - setValid(true); - } - }, [localInsight, frequency]); - const handleSave = async (element: SerializedEditorState, generateEmail: boolean) => { if (!localInsight.name || localInsight.name.trim() === '') { dispatch(addError('Name is required')); + setInputErrors(prev => ({ ...prev, name: true })); return; } + if (localInsight.recipients.length == 0) { + dispatch(addError('Recipients is required')); + setInputErrors(prev => ({ ...prev, recipients: true })); + + return; + } + + if (frequency.frequency == '') { + dispatch(addError('Frequency to trigger is required')); + setInputErrors(prev => ({ ...prev, frequency: true })); + + return; + } + + if (frequency.minute < 0 || frequency.minute >= 59) { + dispatch(addError('Minute range is not correct')); + return; + } + + if (frequency.frequency == 'Daily' || frequency.frequency == 'Specific Day of the Week') { + if (frequency.hour < 0 || frequency.hour >= 24) { + dispatch(addError('Hour range is not correct')); + return; + } + } + + if (frequency.frequency == 'Specific Day of the Week') { + if (frequency.dayOfWeek == '') { + dispatch(addError('Day of Week is required')); + setInputErrors(prev => ({ ...prev, frequencyDayOfWeek: true })); + + return; + } + } + + if (localInsight.alarmMode == 'conditional') { + if (localInsight.conditionsCheck?.[0]?.nodeLabel == '') { + setInputErrors(prev => ({ ...prev, nodeLabel: true })); + dispatch(addError('Node Label is required')); + return; + } + } + if (!session.currentSaveState) { dispatch(addError('No save state selected')); return; @@ -158,18 +200,34 @@ export function FormInsight(props: Props) { {capitalizeFirstLetter(props.insight.type)} ID: {props.insight.id} </h2> <div className="p-2 flex flex-col gap-2"> - <Input label="Name" type="text" value={localInsight.name} onChange={e => setLocalInsight({ ...localInsight, name: e })} /> + <Input + label="Name" + type="text" + value={localInsight.name} + required + validateFlag={!inputErrors.name} + errorText={inputErrors.name ? 'Name is required' : undefined} + onChange={e => { + setLocalInsight({ ...localInsight, name: e }); + setInputErrors(prev => ({ ...prev, name: false })); + }} + /> + <Input label="Description" type="text" value={localInsight.description} onChange={e => setLocalInsight({ ...localInsight, description: e })} /> + <Input type="email" label="Recipient(s)" value={localInsight.recipients} + validateFlag={!inputErrors.recipients} + errorText={inputErrors.re ? 'Email is required' : undefined} onEmailsChange={emails => { + setInputErrors(prev => ({ ...prev, recipients: false })); setLocalInsight(prev => ({ ...prev, recipients: emails, @@ -281,12 +339,15 @@ export function FormInsight(props: Props) { type="dropdown" value={localInsight.conditionsCheck[0].nodeLabel} onChange={value => { + setInputErrors(prev => ({ ...prev, nodeLabel: false })); const updatedCondition = { ...localInsight.conditionsCheck[0], nodeLabel: String(value) }; setLocalInsight({ ...localInsight, conditionsCheck: [updatedCondition] }); }} options={selectedQueryID.entities} inline={false} size="md" + validateFlag={!inputErrors.nodeLabel} + required info="Select the node label to check" /> <Input @@ -309,7 +370,7 @@ export function FormInsight(props: Props) { onChange={value => { const updatedCondition = { ...localInsight.conditionsCheck[0], - condition: InsightConditionMap[String(value)], + operator: InsightConditionMap[String(value)], }; setLocalInsight({ ...localInsight, conditionsCheck: [updatedCondition] }); }} @@ -339,10 +400,15 @@ export function FormInsight(props: Props) { label={'Frequency to trigger the ' + props.insight.type} type="dropdown" value={frequency.frequency} - onChange={value => generateCronExpression(value as string, frequency.minute, frequency.hour, frequency.dayOfWeek)} + validateFlag={!inputErrors.frequency} + onChange={value => { + setInputErrors(prev => ({ ...prev, frequency: false })); + generateCronExpression(value as string, frequency.minute || 0, frequency.hour || 0, frequency.dayOfWeek); + }} options={FrequencyOptions} inline={false} size="md" + required info={'Select the frequency to trigger the ' + props.insight.type} /> <Input @@ -352,8 +418,8 @@ export function FormInsight(props: Props) { max={59} validate={value => value >= 0 && value <= 59} required - value={frequency.minute} - onChange={value => generateCronExpression(frequency.frequency, value, frequency.hour, frequency.dayOfWeek)} + value={frequency.minute ?? ''} + onChange={value => generateCronExpression(frequency.frequency, value, frequency.hour || 0, frequency.dayOfWeek)} info={'Enter the minute of the hour to trigger the ' + props.insight.type} /> {frequency.frequency !== 'Hourly' && ( @@ -364,8 +430,8 @@ export function FormInsight(props: Props) { max={23} validate={value => value >= 0 && value <= 23} required - value={frequency.hour} - onChange={value => generateCronExpression(frequency.frequency, frequency.minute, value, frequency.dayOfWeek)} + value={frequency.hour ?? ''} + onChange={value => generateCronExpression(frequency.frequency, frequency.minute || 0, value, frequency.dayOfWeek)} info={'Enter the hour of the day to trigger the ' + props.insight.type} /> )} @@ -374,9 +440,14 @@ export function FormInsight(props: Props) { label="Day of Week" type="dropdown" value={frequency.dayOfWeek} - onChange={value => generateCronExpression(frequency.frequency, frequency.minute, frequency.hour, value as string)} + onChange={value => { + setInputErrors(prev => ({ ...prev, FrequencyWeekDays: false })); + generateCronExpression(frequency.frequency, frequency.minute || 0, frequency.hour || 0, value as string); + }} options={FrequencyWeekDays} inline={false} + validateFlag={!inputErrors.frequencyDayOfWeek} + required size="md" info={'Select the day of the week to trigger the ' + props.insight.type} /> @@ -398,7 +469,7 @@ export function FormInsight(props: Props) { showToolbar={true} placeholder={`Start typing your ${props.insight.type} template...`} handleSave={handleSave} - saveDisabled={!valid} + saveDisabled={false} variableOptions={selectedQueryID.entities} > <Button