Files
configucci/src/componets/env/Environment.tsx
sokol 9f51379df9
Some checks failed
CI / build-and-test (push) Has been cancelled
CI / build-and-test (pull_request) Has been cancelled
feat: JSON comments support + full-height layout
2026-02-20 16:40:13 +03:00

158 lines
4.7 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Plus, Minus } from 'lucide-react';
import { Button, Select, Card, CardBody } from '../../components/ui';
import { Env, AddEvent, RemoveEvent, UpdateEvent } from '../../models/Env';
import { EnvParam } from '../../models/EnvParam';
import { EnvironmentParam } from './EnvironmentParam';
interface EnvironmentProps {
envs: Env[];
onChanged: (env: Env) => void;
onSelected: (envId: number) => void;
onAdd: (env: Env) => number;
onRemove: (envId: number) => void;
}
export function Environment({ envs, onChanged, onSelected, onAdd, onRemove }: EnvironmentProps) {
const [currEnvId, setCurrEnvId] = useState<number>(envs[0]?.id ?? 0);
// Sync currEnvId when envs changes
useEffect(() => {
if (!envs.find(e => e.id === currEnvId)) {
setCurrEnvId(envs[0]?.id ?? 0);
}
}, [envs, currEnvId]);
const currEnv = envs.find(e => e.id === currEnvId) ?? envs[0];
function handleParamChanged(event: AddEvent<EnvParam> | RemoveEvent<EnvParam> | UpdateEvent<EnvParam>) {
let newEnv: Env = currEnv;
let isChanged = false;
if (event instanceof RemoveEvent) {
newEnv = currEnv.delParam(event.payload);
isChanged = true;
} else if (event instanceof AddEvent) {
newEnv = currEnv.addParams(event.payload);
isChanged = true;
} else if (event instanceof UpdateEvent) {
newEnv = currEnv.updateParams(event.payload);
isChanged = true;
}
if (isChanged) {
onChanged(newEnv);
setCurrEnvId(newEnv.id ?? 0);
}
}
function handleAddEnv() {
const name = prompt('Enter new environment name:');
if (!name || name.trim() === '') return;
// Calculate next integer ID based on max existing ID
const maxId = envs.reduce((max, e) => Math.max(max, e.id ?? 0), -1);
const newId = maxId + 1;
const newEnv = new Env(newId, name.trim(), [...currEnv.params]);
const newIdx = onAdd(newEnv);
setCurrEnvId(newEnv.id ?? 0);
onSelected(newIdx);
}
function handleRemoveEnv() {
if (currEnv.isDefault()) {
alert('Cannot remove DEFAULT environment');
return;
}
if (!confirm(`Remove environment "${currEnv.name}"?`)) return;
const idx = envs.findIndex(x => x.id === currEnv.id);
if (idx > -1 && currEnv.id !== undefined) {
onRemove(currEnv.id);
const newIdx = Math.max(0, idx - 1);
const newEnv = envs[newIdx];
if (newEnv?.id !== undefined) {
setCurrEnvId(newEnv.id);
}
onSelected(newIdx);
}
}
const selectOptions = envs.map((x) => ({
value: x.id ?? 0,
label: x.name ?? 'Unknown',
}));
const paramCtrls = currEnv.params.map((x) => (
<EnvironmentParam
key={`${currEnv.id}-${x.id}`}
param={new EnvParam(x.id, x.name, x.value)}
onChanged={handleParamChanged}
isNew={false}
/>
));
return (
<Card variant="bordered" padding="none" className="h-full overflow-hidden flex flex-col">
<CardBody className="space-y-2 flex flex-col h-full overflow-hidden">
{/* Environment Selector */}
<div className="flex-shrink-0 flex gap-2">
<div className="flex-1">
<Select
label="Environment"
value={currEnvId}
options={selectOptions}
onChange={(e) => {
const id = Number.parseInt(e.target.value);
setCurrEnvId(id);
onSelected(id);
}}
id="environments"
/>
</div>
<div className="flex flex-col justify-center gap-2 pt-6 flex-shrink-0">
<div className="flex gap-2">
<Button
variant="success"
size="sm"
onClick={handleAddEnv}
title="Add environment"
icon={Plus}
/>
<Button
variant="danger"
size="sm"
onClick={handleRemoveEnv}
title="Remove environment"
icon={Minus}
disabled={currEnv.isDefault()}
/>
</div>
</div>
</div>
{/* Parameters Section - Scrollable */}
<div className="flex-1 overflow-hidden flex flex-col min-h-0">
<h3 className="text-sm font-semibold text-slate-700 mb-1 uppercase tracking-wide flex-shrink-0">
Parameters
</h3>
<div className="flex-1 overflow-y-auto space-y-0 pr-2 -mr-2">
{paramCtrls}
<EnvironmentParam
key={`${currEnv.id}-new`}
param={new EnvParam(-1, '', '')}
onChanged={handleParamChanged}
isNew={true}
/>
</div>
</div>
</CardBody>
</Card>
);
}