export default function RuleManager()

in src/application/ui/src/components/RuleManager.js [58:236]


export default function RuleManager() {
  const [rules, setRules] = useState([]);
  const [loading, setLoading] = useState(false);
  const [openDialog, setOpenDialog] = useState(false);
  const [editingRule, setEditingRule] = useState(null);
  const [error, setError] = useState(null);
  const [apiHealth, setApiHealth] = useState(null);
  const [currentRule, setCurrentRule] = useState({
    type: RULE_TYPES.BOOST,
    conditionType: CONDITION_TYPES.CATEGORY,
    condition: '',
    score: 1.0,
  });

  const checkApiHealth = async () => {
    try {
      const health = await ruleService.checkHealth();
      setApiHealth(health);
      setError(null);
    } catch (error) {
      setApiHealth(null);
      setError(`API Connection Error: Mock rule service unavailable`);
    }
  };

  const fetchRules = async () => {
    setLoading(true);
    try {
      const rulesList = await ruleService.getRules();
      setRules(rulesList || []);
      setError(null);
    } catch (error) {
      setError('Failed to fetch mock rules. Please try again.');
      console.error('Error fetching rules:', error);
      setRules([]);
    }
    setLoading(false);
  };

  useEffect(() => {
    checkApiHealth();
    fetchRules();
  }, []);

  const handleOpenDialog = (rule = null) => {
    if (rule) {
      setEditingRule(rule);
      setCurrentRule(rule);
    } else {
      setEditingRule(null);
      setCurrentRule({
        type: RULE_TYPES.BOOST,
        conditionType: CONDITION_TYPES.CATEGORY,
        condition: '',
        score: 1.0,
      });
    }
    setOpenDialog(true);
  };

  const handleCloseDialog = () => {
    setOpenDialog(false);
    setEditingRule(null);
    setError(null);
  };

  const validateRule = (rule) => {
    if (!rule.condition.trim()) {
      throw new Error('Condition cannot be empty');
    }
    if (rule.conditionType === CONDITION_TYPES.PRICE_RANGE) {
      try {
        const [min, max] = rule.condition.split('-').map(Number);
        if (isNaN(min) || isNaN(max) || min >= max) {
          throw new Error();
        }
      } catch {
        throw new Error('Price range must be in format min-max (e.g., 0-100)');
      }
    }
    if (!rule.score || rule.score <= 0) {
      throw new Error('Score must be greater than 0');
    }
  };

  const handleSaveRule = async () => {
    setLoading(true);
    try {
      validateRule(currentRule);
      
      if (editingRule) {
        await ruleService.updateRule(editingRule.id, currentRule);
      } else {
        await ruleService.createRule(currentRule);
      }
      
      await fetchRules();
      handleCloseDialog();
      setError(null);
    } catch (error) {
      setError(error.message || 'Failed to save mock rule. Please try again.');
      console.error('Error saving rule:', error);
    }
    setLoading(false);
  };

  const handleDeleteRule = async (ruleId) => {
    if (window.confirm('Are you sure you want to delete this rule?')) {
      setLoading(true);
      try {
        await ruleService.deleteRule(ruleId);
        await fetchRules();
        setError(null);
      } catch (error) {
        setError('Failed to delete mock rule. Please try again.');
        console.error('Error deleting rule:', error);
      }
      setLoading(false);
    }
  };

  if (loading && rules.length === 0) {
    return (
      <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
        <CircularProgress />
      </Box>
    );
  }

  return (
    <Box sx={{ p: 3 }}>
      <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
        <Box>
          <Typography variant="h5" sx={{ mb: 1 }}>Search Rules Management</Typography>
          <Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexDirection: 'column', alignItems: 'flex-start' }}>
            {apiHealth && (
              <Chip
                label={`Status: ${apiHealth.status}`}
                color={apiHealth.status === 'healthy' ? 'success' : 'error'}
                size="small"
              />
            )}
            <Typography variant="caption" color="text.secondary" sx={{ fontStyle: 'italic' }}>
              Using mock data (Spanner implementation coming soon)
            </Typography>
          </Box>
        </Box>
        <Button
          variant="contained"
          onClick={() => handleOpenDialog()}
          disabled={loading || !apiHealth}
        >
          Add New Rule
        </Button>
      </Box>

      {error && (
        <Alert severity="error" sx={{ mb: 3 }}>
          {error}
        </Alert>
      )}

      <Grid container spacing={3}>
        {rules.map((rule) => (
          <Grid item xs={12} md={6} key={rule.id}>
            <Card>
              <CardContent>
                <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
                  <Typography 
                    variant="h6" 
                    color={rule.type === RULE_TYPES.BOOST ? 'success.main' : 'error.main'}
                  >
                    {(rule.type || '').toUpperCase()}
                  </Typography>
                  <Box>
                    <IconButton onClick={() => handleOpenDialog(rule)}>
                      <EditIcon />
                    </IconButton>
                    <IconButton onClick={() => handleDeleteRule(rule.id)}>