I didn’t refactor the code yet but it could look like this. I split the code to make it fit here.
import React, { useState, useCallback, useEffect, useMemo } from 'react';
import { Button } from '@/components/ui/button'; // Assuming path is correct
import { Input } from '@/components/ui/input'; // Assuming path is correct
import { cn } from '@/lib/utils'; // Assuming path is correct
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; // Assuming path is correct
import { AlertCircle } from "lucide-react";
import { motion, AnimatePresence } from 'framer-motion';
import { Checkbox } from "@/components/ui/checkbox"; // Assuming path is correct
import { Label } from "@/components/ui/label"; // Assuming path is correct
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; // Assuming path is correct
// --- Constants ---
const MESSAGE_TIMEOUT_MS = 3000;
const PERCENTAGE_FACTOR = 100;
// --- Types ---
interface Deal {
id: string;
/** The reference price for deviation checks (e.g., entry price, average price). */
max_price: number;
}
interface DealCheckerDemoProps {
/** Initial value for over deviation as a decimal (e.g., 0.02 for 2%). Defaults to 0.02. */
initialOverDeviation?: number;
/** Initial value for under deviation as a decimal (e.g., 0.03 for 3%). Defaults to 0.03. */
initialUnderDeviation?: number;
/** Initial list of deals. Defaults to an empty array. */
initialDeals?: Deal[];
/** Whether adding new deals is allowed. Defaults to true. */
allowAddDeals?: boolean;
/** Whether checking deals is allowed. Defaults to true. */
allowCheckDeals?: boolean;
/** Default minimum price deviation as a decimal (e.g., 0.05 for 5%). Defaults to 0.05. */
defaultMinPricePercentDecimal?: number; // Renamed for clarity
/** Custom validation function for the input price. Return null if valid, or an error string if invalid. */
customValidation?: (price: number) => string | null;
/** Label for the main price input field. Defaults to "Max Price (Entry/Avg)". */
maxPriceLabel?: string;
/** Label for the minimum price deviation configuration. Defaults to "Min Price Deviation (Last SO/SL)". */
minPriceLabel?: string;
/** Whether configuring the minimum price deviation is allowed. Defaults to true. */
allowMinPriceConfiguration?: boolean;
/** Label for the over deviation configuration. Defaults to "Over Deviation". */
overDeviationLabel?: string;
/** Label for the under deviation configuration. Defaults to "Under Deviation". */
underDeviationLabel?: string;
/** Whether configuring over/under deviation is allowed. Defaults to true. */
allowOverUnderDeviationConfiguration?: boolean;
}
interface CheckResult {
canOpen: boolean;
blockingDeal?: Deal;
/** Lower boundary below which a deal might be acceptable */
lowerBound?: number;
/** Upper boundary above which a deal might be acceptable */
upperBound?: number;
}
/** Configuration for calculations, using percentage numbers (e.g., 5 for 5%) */
interface CalculationConfig {
overDeviationPercent: number;
underDeviationPercent: number;
minPricePercent: number;
useMinPrice: boolean;
}
interface MessageState {
text: string | null;
type: 'success' | 'error' | null;
}
// --- Utility Functions ---
/**
* Parses a string to a positive number. Used for the main price input.
* @param value - The string to parse.
* @returns The parsed positive number, or NaN if invalid or not positive.
*/
const parsePositiveNumber = (value: string): number => {
const num = parseFloat(value);
return !isNaN(num) && num > 0 ? num : NaN;
};
/**
* Parses a percentage string (e.g., "5") into a non-negative number (e.g., 5).
* Used for configuration inputs.
* @param value - The percentage string to parse.
* @returns The parsed non-negative percentage number, or NaN if invalid or negative.
*/
const parsePercentageString = (value: string): number => {
const num = parseFloat(value);
return !isNaN(num) && num >= 0 ? num : NaN;
};
/**
* Converts a decimal value (e.g., 0.05) to its percentage string representation (e.g., "5").
* Used for initializing input state from decimal props.
* @param decimal - The decimal number to convert.
* @returns The percentage value as a string.
*/
const decimalToPercentageString = (decimal: number): string => {
// Multiply, round to avoid floating point issues like 5.0000000001, then convert to string
return (Math.round(decimal * PERCENTAGE_FACTOR * 100) / 100).toString();
};
/**
* Calculates a multiplier factor based on a percentage value.
* Converts the percentage (e.g., 2 or -3) into a decimal factor (e.g., 1.02 or 0.97).
* `toChangeFactor(2)` returns `1.02`.
* `toChangeFactor(-3)` returns `0.97`.
* @param percentage - The deviation percentage (positive for increase, negative for decrease).
* @returns The multiplier (1 + percentage / PERCENTAGE_FACTOR). Returns 1 if percentage is not a valid number.
*/
const toChangeFactor = (percentage: number): number => {
// Basic validation: if percentage is not a valid number, return a neutral factor (1)
if (isNaN(percentage)) {
console.warn("toChangeFactor received NaN, returning 1.");
return 1;
}
return 1 + (percentage / PERCENTAGE_FACTOR);
};
/**
* Calculates the lower and upper price boundaries.
* Receives configuration with percentage numbers (e.g., 5 for 5%) and uses `toChangeFactor`
* which handles the conversion to a decimal multiplier internally.
* @param refPrice - The reference price of the existing deal.
* @param config - The current calculation configuration containing percentage numbers.
* @returns An object containing the calculated lower and upper boundaries.
*/
const calculateDealBoundaries = (refPrice: number, config: CalculationConfig): { lowerBound: number; upperBound: number } => {
const minPriceRefFactor = config.useMinPrice
? toChangeFactor(-config.minPricePercent) // Pass percentage (-5) -> returns 0.95
: 1; // Neutral factor if min price check is disabled
const minPriceRef = refPrice * minPriceRefFactor;
// Calculate lower bound based on the (potentially adjusted) reference price and under-deviation
const lowerBound = minPriceRef * toChangeFactor(-config.underDeviationPercent); // Pass percentage (-3) -> returns 0.97
// Calculate upper bound based on the original reference price and over-deviation
const upperBound = refPrice * toChangeFactor(config.overDeviationPercent); // Pass percentage (2) -> returns 1.02
return { lowerBound, upperBound };
};
/**
* Checks if a new deal can be opened based on existing deals and configuration.
* @param checkPrice - The price to check.
* @param deals - Array of existing deals.
* @param config - The current calculation configuration (using percentage numbers).
* @returns A CheckResult object indicating if the deal can be opened and details if blocked.
*/
const checkIfDealCanBeOpened = (checkPrice: number, deals: Deal[], config: CalculationConfig): CheckResult => {
// Basic validation for checkPrice
if (isNaN(checkPrice) || checkPrice <= 0) {
return { canOpen: false }; // Cannot open if checkPrice is invalid
}
for (const deal of deals) {
// Skip deals with invalid reference price
if (isNaN(deal.max_price) || deal.max_price <= 0) {
console.warn(`Skipping deal ${deal.id} due to invalid max_price: ${deal.max_price}`);
continue;
}
// calculateDealBoundaries now expects config with percentages
const { lowerBound, upperBound } = calculateDealBoundaries(deal.max_price, config);
// Check if boundaries are valid before comparison
if (isNaN(lowerBound) || isNaN(upperBound)) {
console.warn(`Invalid boundaries calculated for deal ${deal.id}. Lower: ${lowerBound}, Upper: ${upperBound}`);
continue; // Skip this deal if boundaries are invalid
}
// Check for overlap
if (lowerBound < checkPrice && checkPrice < upperBound) {
return {
canOpen: false,
blockingDeal: deal,
lowerBound,
upperBound,
};
}
}
// If no blocking deals found after checking all valid deals
return { canOpen: true };
};
// --- Sub-Components ---
/** Displays the list of existing deals */
const DealList: React.FC<{
deals: Deal[];
onDelete: (id: string) => void;
maxPriceLabel: string;
}> = React.memo(({ deals, onDelete, maxPriceLabel }) => (
<Card className="bg-gray-900/90 backdrop-blur-md border border-gray-800">
<CardHeader>
<CardTitle className="text-xl sm:text-2xl font-semibold text-gray-200">Existing Deals</CardTitle>
<CardDescription className="text-gray-400">Deals currently being tracked.</CardDescription>
</CardHeader>
<CardContent>
{deals.length === 0 ? (
<p className="text-gray-400">No deals added yet.</p>
) : (
<div className="space-y-2">
<AnimatePresence initial={false}>
{deals.map((deal) => (
<motion.div
key={deal.id}
layout // Animate layout changes (like deletion)
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 50, transition: { duration: 0.2 } }} // Exit animation
className="flex items-center justify-between bg-gray-800/80 border border-gray-700 rounded-md p-2 sm:p-3"
>
<span className="text-gray-300 truncate pr-2" title={deal.max_price.toString()}>
{maxPriceLabel}: {deal.max_price.toFixed(2)}
</span>
<Button
variant="destructive"
size="sm"
onClick={() => onDelete(deal.id)}
className="bg-red-500/20 text-red-400 hover:bg-red-500/30 hover:text-red-300 flex-shrink-0"
aria-label={`Delete deal with price ${deal.max_price.toFixed(2)}`}
>
Delete
</Button>
</motion.div>
))}
</AnimatePresence>
</div>
)}
</CardContent>
</Card>
));
DealList.displayName = "DealList";
/** Handles price input and Check/Add actions */
const DealForm: React.FC<{
priceInput: string;
onPriceInputChange: (value: string) => void;
onCheck: () => void;
onAdd: () => void;
canAddDeal: boolean;
allowCheckDeals: boolean;
allowAddDeals: boolean;
maxPriceLabel: string;
cardTitle: string;
cardDescription: string;
}> = React.memo(({
priceInput, onPriceInputChange, onCheck, onAdd, canAddDeal, allowCheckDeals,
allowAddDeals, maxPriceLabel, cardTitle, cardDescription
}) => (
<Card className="bg-gray-900/90 backdrop-blur-md border border-gray-800">
<CardHeader>
<CardTitle className="text-xl sm:text-2xl font-semibold text-gray-200">{cardTitle}</CardTitle>
<CardDescription className="text-gray-400">{cardDescription}</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Input
type="number"
placeholder={maxPriceLabel}
value={priceInput}
onChange={(e) => onPriceInputChange(e.target.value)}
className="bg-gray-800/80 border-gray-700 text-white placeholder:text-gray-400"
aria-label={maxPriceLabel}
step="any" // Allow decimals
min="0" // Prevent negative numbers via browser validation (though we validate further)
/>
<div className="flex flex-col sm:flex-row gap-2">
{allowCheckDeals && (
<Button
onClick={onCheck}
className="bg-green-500/20 text-green-400 hover:bg-green-500/30 hover:text-green-300 flex-1"
>
Check Availability
</Button>
)}
{allowAddDeals && (
<Button
onClick={onAdd}
className="bg-blue-500/20 text-blue-400 hover:bg-blue-500/30 hover:text-blue-300 flex-1"
disabled={!canAddDeal}
aria-disabled={!canAddDeal}
>
Add Deal
</Button>
)}
</div>
</CardContent>
</Card>
));
DealForm.displayName = "DealForm";
/** Displays and manages configuration options */
const ConfigurationPanel: React.FC<{
minPricePercentInput: string;
overDeviationInput: string;
underDeviationInput: string;
isMinPriceEnabled: boolean;
isOverDeviationEnabled: boolean;
isUnderDeviationEnabled: boolean;
onInputChange: (field: 'minPricePercent' | 'overDeviation' | 'underDeviation', value: string) => void;
onToggleChange: (field: 'minPrice' | 'overDeviation' | 'underDeviation', checked: boolean) => void;
allowMinPriceConfiguration: boolean;
allowOverUnderDeviationConfiguration: boolean;
minPriceLabel: string;
overDeviationLabel: string;
underDeviationLabel: string;
}> = React.memo(({
minPricePercentInput, overDeviationInput, underDeviationInput,
isMinPriceEnabled, isOverDeviationEnabled, isUnderDeviationEnabled,
onInputChange, onToggleChange,
allowMinPriceConfiguration,
allowOverUnderDeviationConfiguration, minPriceLabel, overDeviationLabel, underDeviationLabel
}) => {
// Helper to render each config input row
const renderConfigInput = (
fieldKey: keyof typeof configEnabled,
inputKey: keyof typeof configInputs,
labelText: string, // The main label text (e.g., "Min Price Deviation")
inputValue: string,
isEnabled: boolean,
allowToggle: boolean
) => {
const inputId = `${fieldKey}-input`;
const checkboxId = `${fieldKey}-checkbox`;
// Label now refers to the input, not the checkbox
const inputLabelId = `${fieldKey}-label`;
return (
// Use flex row for the overall layout
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-3 py-3 border-b border-gray-800 last:border-b-0">
{/* Checkbox (if applicable) - First element */}
{allowToggle && (
<div className="flex-shrink-0 order-1 mb-2 sm:mb-0">
<Checkbox
id={checkboxId}
checked={isEnabled}
onCheckedChange={(checked) => onToggleChange(fieldKey, Boolean(checked))}
className="border-gray-700 data-[state=checked]:bg-blue-600 data-[state=checked]:border-blue-500 mt-1" // Added mt-1 for alignment potentially
aria-label={`Enable ${labelText} configuration`} // More descriptive aria-label
/>
</div>
)}
{/* Input and Label Group - Second element */}
<div className={cn(
"flex flex-grow items-center gap-2 order-2 w-full",
// If checkbox isn't shown, remove potential left margin that might be added implicitly by gap
!allowToggle && "sm:ml-0"
)}>
{/* Input Field */}
<Input
id={inputId}
type="number"
placeholder="%" // Very simple placeholder
value={inputValue}
onChange={(e) => onInputChange(inputKey, e.target.value)}
className={cn(
// Assign specific width, let label take remaining space? Or keep input flexible?
"w-24 sm:w-28 bg-gray-800/80 border-gray-700 text-white placeholder:text-gray-400",
!isEnabled && "opacity-50 cursor-not-allowed",
)}
disabled={!isEnabled}
aria-labelledby={inputLabelId} // Point aria-labelledby to the input's label
step="any"
min="0"
/>
{/* Descriptive Label for the Input */}
<Label id={inputLabelId} htmlFor={inputId} className="text-sm text-gray-300 select-none">
{labelText} (%)
</Label>
</div>
</div>
);
};
return (
<Card className="bg-gray-900/90 backdrop-blur-md border border-gray-800">
<CardHeader>
<CardTitle className="text-xl sm:text-2xl font-semibold text-gray-200">Configuration</CardTitle>
<CardDescription className="text-gray-400">Enable and set deviation percentages.</CardDescription>
</CardHeader>
<CardContent className="space-y-0"> {/* Remove space-y-4, handled by py-3 */}
{renderConfigInput(
'minPrice',
'minPricePercent',
minPriceLabel,
minPricePercentInput,
isMinPriceEnabled,
allowMinPriceConfiguration
)}
{renderConfigInput(
'overDeviation',
'overDeviation',
overDeviationLabel,
overDeviationInput,
isOverDeviationEnabled,
allowOverUnderDeviationConfiguration
)}
{renderConfigInput(
'underDeviation',
'underDeviation',
underDeviationLabel,
underDeviationInput,
isUnderDeviationEnabled,
allowOverUnderDeviationConfiguration
)}
</CardContent>
</Card>
);
});
ConfigurationPanel.displayName = "ConfigurationPanel";
/** Displays success or error messages with animation */
const NotificationAlert: React.FC<{ message: MessageState | null }> = React.memo(({ message }) => (
<AnimatePresence>
{message?.text && (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20, transition: { duration: 0.3 } }}
className="mb-6" // Adjust spacing as needed
role="alert"
aria-live="polite" // Announce changes politely
>
<Alert variant={message.type === 'error' ? "destructive" : "default"}>
{message.type === 'error' && <AlertCircle className="h-4 w-4" />}
<AlertTitle>{message.type === 'error' ? "Error" : "Success"}</AlertTitle>
<AlertDescription>{message.text}</AlertDescription>
</Alert>
</motion.div>
)}
</AnimatePresence>
));
NotificationAlert.displayName = "NotificationAlert";
// --- Main Component ---
/**
* DealCheckerDemo component allows users to check if a new deal can be opened
* based on its price relative to existing deals and configurable deviation percentages.
* It also allows adding valid deals to a list. Uses percentage values in configuration inputs.
*/
const DealCheckerDemo: React.FC<DealCheckerDemoProps> = ({
initialOverDeviation = 0.02,
initialUnderDeviation = 0.03,
initialDeals = [],
allowAddDeals = true,
allowCheckDeals = true,
defaultMinPricePercentDecimal = 0.05,
customValidation = () => null,
maxPriceLabel = "Max Price (Entry/Avg)",
minPriceLabel = "Min Price Deviation", // Simplified default label slightly
allowMinPriceConfiguration = true,
overDeviationLabel = "Over Deviation",
underDeviationLabel = "Under Deviation",
allowOverUnderDeviationConfiguration = true,
}) => {
// --- State (remains the same) ---
const [deals, setDeals] = useState<Deal[]>(initialDeals);
const [priceInput, setPriceInput] = useState<string>('');
const [canAddDeal, setCanAddDeal] = useState<boolean>(false);
const [message, setMessage] = useState<MessageState | null>(null);
const [configInputs, setConfigInputs] = useState({
minPricePercent: decimalToPercentageString(defaultMinPricePercentDecimal),
overDeviation: decimalToPercentageString(initialOverDeviation),
underDeviation: decimalToPercentageString(initialUnderDeviation),
});
const [configEnabled, setConfigEnabled] = useState<{
minPrice: boolean; overDeviation: boolean; underDeviation: boolean;
}>({
minPrice: true, overDeviation: true, underDeviation: true,
});
// --- Memoized Values (remains the same) ---
const { cardTitle, cardDescription } = useMemo(() => {
let title = "Deal Checker"; // ... (rest of logic is same)
let description = "Check if a new deal can be opened.";
if (allowAddDeals && allowCheckDeals) {
title = "Check & Add Deal";
description = "Enter a price, check validity, then add the deal.";
} else if (allowAddDeals) {
title = "Add New Deal";
description = "Enter price and add a new deal directly (validation recommended).";
} else if (allowCheckDeals) {
title = "Check Deal Validity";
description = "Enter a price to check if a new deal can be opened.";
}
return { cardTitle: title, cardDescription: description };
}, [allowAddDeals, allowCheckDeals]);
// --- Callbacks (remain the same) ---
const clearMessage = useCallback(() => setMessage(null), []);
const showTemporaryMessage = useCallback((text: string, type: 'success' | 'error') => {
setMessage({ text, type });
const timer = setTimeout(clearMessage, MESSAGE_TIMEOUT_MS);
}, [clearMessage]);
const handleConfigInputChange = useCallback((field: keyof typeof configInputs, value: string) => {
setConfigInputs(prev => ({ ...prev, [field]: value }));
setCanAddDeal(false);
}, []);
const handleConfigToggleChange = useCallback((field: keyof typeof configEnabled, checked: boolean) => {
setConfigEnabled(prev => ({ ...prev, [field]: checked }));
setCanAddDeal(false);
}, []);
const handleCheckDeal = useCallback(() => { // ... (logic remains the same)
setMessage(null);
const checkPrice = parsePositiveNumber(priceInput);
if (isNaN(checkPrice)) {
setMessage({ text: 'Please enter a valid positive price to check.', type: 'error' });
setCanAddDeal(false); return;
}
const customValidationError = customValidation(checkPrice);
if (customValidationError) {
setMessage({ text: customValidationError, type: 'error' });
setCanAddDeal(false); return;
}
const parsedMinPricePercent = configEnabled.minPrice ? parsePercentageString(configInputs.minPricePercent) : 0;
const parsedOverDeviationPercent = configEnabled.overDeviation ? parsePercentageString(configInputs.overDeviation) : 0;
const parsedUnderDeviationPercent = configEnabled.underDeviation ? parsePercentageString(configInputs.underDeviation) : 0;
if (isNaN(parsedMinPricePercent) || isNaN(parsedOverDeviationPercent) || isNaN(parsedUnderDeviationPercent)) {
setMessage({ text: 'Invalid configuration percentage(s). Please enter valid non-negative numbers.', type: 'error' });
setCanAddDeal(false); return;
}
const finalConfig: CalculationConfig = {
overDeviationPercent: parsedOverDeviationPercent,
underDeviationPercent: parsedUnderDeviationPercent,
minPricePercent: parsedMinPricePercent,
useMinPrice: configEnabled.minPrice,
};
const result = checkIfDealCanBeOpened(checkPrice, deals, finalConfig);
if (result.canOpen) {
setMessage({ text: `A new deal can be opened at ${checkPrice.toFixed(2)}`, type: 'success' });
} else {
const lower = result.lowerBound?.toFixed(2) ?? 'N/A';
const upper = result.upperBound?.toFixed(2) ?? 'N/A';
const refPrice = result.blockingDeal?.max_price.toFixed(2) ?? 'N/A';
setMessage({
text: `Cannot open deal at ${checkPrice.toFixed(2)}. Conflicts with Deal (Ref Price: ${refPrice}). Consider prices outside ${lower} - ${upper}.`,
type: 'error'
});
}
setCanAddDeal(result.canOpen);
}, [priceInput, customValidation, configInputs, configEnabled, deals]);
const handleAddDeal = useCallback(() => { // ... (logic remains the same)
if (!canAddDeal) {
showTemporaryMessage('Price must be checked and available before adding.', 'error'); return;
}
const entryPrice = parsePositiveNumber(priceInput);
if (isNaN(entryPrice)) {
showTemporaryMessage('Invalid price. Cannot add deal.', 'error'); return;
}
const newDeal: Deal = { id: crypto.randomUUID(), max_price: entryPrice };
setDeals(prevDeals => [...prevDeals, newDeal]);
setPriceInput('');
showTemporaryMessage('Deal added successfully.', 'success');
setCanAddDeal(false);
}, [canAddDeal, priceInput, showTemporaryMessage]);
const handleDeleteDeal = useCallback((id: string) => { // ... (logic remains the same)
setDeals(prevDeals => prevDeals.filter(deal => deal.id !== id));
showTemporaryMessage('Deal deleted.', 'success');
setCanAddDeal(false);
}, [showTemporaryMessage]);
// --- useEffect (remains the same) ---
useEffect(() => {
setConfigInputs(prev => ({
...prev,
overDeviation: decimalToPercentageString(initialOverDeviation),
underDeviation: decimalToPercentageString(initialUnderDeviation),
minPricePercent: decimalToPercentageString(defaultMinPricePercentDecimal),
}));
setCanAddDeal(false);
}, [initialOverDeviation, initialUnderDeviation, defaultMinPricePercentDecimal]);
// --- Prepare display strings (remains the same) ---
const formatPercentageForDisplay = (value: string) => {
const num = parseFloat(value);
return isNaN(num) ? 'Invalid' : num.toFixed(2);
};
const minPriceDisplay = configEnabled.minPrice ? formatPercentageForDisplay(configInputs.minPricePercent) : "0.00";
const overDeviationDisplay = configEnabled.overDeviation ? formatPercentageForDisplay(configInputs.overDeviation) : "0.00";
const underDeviationDisplay = configEnabled.underDeviation ? formatPercentageForDisplay(configInputs.underDeviation) : "0.00";
// --- Render ---
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 p-4 sm:p-8 text-gray-200 font-sans">
<div className="max-w-3xl mx-auto space-y-6">
{/* Title */}
<h1 className="text-3xl sm:text-4xl md:text-5xl font-bold text-white text-center mb-6">
Deal Checker Demo
</h1>
{/* Info Panel */}
<InfoPanel
maxPriceLabel={maxPriceLabel}
minPriceLabel={minPriceLabel}
minPricePercentDisplay={minPriceDisplay}
allowMinPriceConfiguration={allowMinPriceConfiguration}
overDeviationLabel={overDeviationLabel}
overDeviationDisplay={overDeviationDisplay}
underDeviationLabel={underDeviationLabel}
underDeviationDisplay={underDeviationDisplay}
allowOverUnderDeviationConfiguration={allowOverUnderDeviationConfiguration}
/>
{/* Configuration Panel (Conditional) - Uses the refactored component */}
{(allowMinPriceConfiguration || allowOverUnderDeviationConfiguration) && (
<ConfigurationPanel
minPricePercentInput={configInputs.minPricePercent}
overDeviationInput={configInputs.overDeviation}
underDeviationInput={configInputs.underDeviation}
isMinPriceEnabled={configEnabled.minPrice}
isOverDeviationEnabled={configEnabled.overDeviation}
isUnderDeviationEnabled={configEnabled.underDeviation}
onInputChange={handleConfigInputChange}
onToggleChange={handleConfigToggleChange} // Pass the correct handler
allowMinPriceConfiguration={allowMinPriceConfiguration}
allowOverUnderDeviationConfiguration={allowOverUnderDeviationConfiguration}
minPriceLabel={minPriceLabel} // These props provide the text for the checkbox labels now
overDeviationLabel={overDeviationLabel}
underDeviationLabel={underDeviationLabel}
/>
)}
{/* Check/Add Form */}
{(allowAddDeals || allowCheckDeals) && (
<DealForm
priceInput={priceInput}
onPriceInputChange={setPriceInput}
onCheck={handleCheckDeal}
onAdd={handleAddDeal}
canAddDeal={canAddDeal}
allowCheckDeals={allowCheckDeals}
allowAddDeals={allowAddDeals}
maxPriceLabel={maxPriceLabel}
cardTitle={cardTitle}
cardDescription={cardDescription}
/>
)}
{/* Notification */}
<NotificationAlert message={message} />
{/* Deals List */}
<DealList deals={deals} onDelete={handleDeleteDeal} maxPriceLabel={maxPriceLabel} />
</div>
</div>
);
};
// --- Export ---
export default DealCheckerDemo;