Files
archon/archon-ui-main/src/components/project-tasks/TaskInputComponents.tsx
sean-eskerium 1b5196d70f - Fix the threading service to properly handle rate limiting.
- Fix the clipboard functionality to work on non local hosts and https
- Improvements in sockets on front-end and backend. Storing session in local browser storage for reconnect. Logic to prevent socket echos coausing rerender and performance issues.
- Fixes and udpates to re-ordering logic in adding a new task, reordering items on the task table.
- Allowing assignee to not be hardcoded enum.
- Fix to Document Version Control (Improvements still needed in the Milkdown editor conversion to store in the docs.
- Adding types to remove [any] typescript issues.
2025-08-20 02:28:02 -04:00

172 lines
4.4 KiB
TypeScript

import React, { memo, useState, useEffect, useCallback, useRef } from 'react';
interface DebouncedInputProps {
value: string;
onChange: (value: string) => void;
placeholder?: string;
className?: string;
type?: 'text' | 'textarea';
rows?: number;
}
// Memoized input component that manages its own state
export const DebouncedInput = memo(({
value,
onChange,
placeholder,
className,
type = 'text',
rows = 5
}: DebouncedInputProps) => {
const [localValue, setLocalValue] = useState(value);
const timeoutRef = useRef<NodeJS.Timeout>();
const isFirstRender = useRef(true);
// Sync with external value only on mount or when externally changed
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
// Only update if the external value is different from local
if (value !== localValue) {
setLocalValue(value);
}
}, [value]);
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const newValue = e.target.value;
setLocalValue(newValue);
// Clear existing timeout
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// Set new timeout for debounced update
timeoutRef.current = setTimeout(() => {
onChange(newValue);
}, 300);
}, [onChange]);
// Cleanup timeout on unmount
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
if (type === 'textarea') {
return (
<textarea
value={localValue}
onChange={handleChange}
placeholder={placeholder}
className={className}
rows={rows}
/>
);
}
return (
<input
type="text"
value={localValue}
onChange={handleChange}
placeholder={placeholder}
className={className}
/>
);
}, (prevProps, nextProps) => {
// Custom comparison - only re-render if external value or onChange changes
return prevProps.value === nextProps.value &&
prevProps.onChange === nextProps.onChange &&
prevProps.placeholder === nextProps.placeholder &&
prevProps.className === nextProps.className;
});
DebouncedInput.displayName = 'DebouncedInput';
interface FeatureInputProps {
value: string;
onChange: (value: string) => void;
projectFeatures: import('../types/jsonb').ProjectFeature[];
isLoadingFeatures: boolean;
placeholder?: string;
className?: string;
}
// Memoized feature input with datalist
export const FeatureInput = memo(({
value,
onChange,
projectFeatures,
isLoadingFeatures,
placeholder,
className
}: FeatureInputProps) => {
const [localValue, setLocalValue] = useState(value);
const timeoutRef = useRef<NodeJS.Timeout>();
// Sync with external value
useEffect(() => {
if (value !== localValue) {
setLocalValue(value);
}
}, [value]);
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
setLocalValue(newValue);
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
onChange(newValue);
}, 300);
}, [onChange]);
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return (
<div className="relative">
<input
type="text"
value={localValue}
onChange={handleChange}
placeholder={placeholder}
className={className}
list="features-list"
/>
<datalist id="features-list">
{projectFeatures.map((feature) => (
<option key={feature.id} value={feature.label}>
{feature.label} ({feature.type})
</option>
))}
</datalist>
{isLoadingFeatures && (
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
<div className="w-4 h-4 animate-spin rounded-full border-2 border-cyan-400 border-t-transparent" />
</div>
)}
</div>
);
}, (prevProps, nextProps) => {
return prevProps.value === nextProps.value &&
prevProps.onChange === nextProps.onChange &&
prevProps.isLoadingFeatures === nextProps.isLoadingFeatures &&
prevProps.projectFeatures === nextProps.projectFeatures;
});
FeatureInput.displayName = 'FeatureInput';