refactor(organization): extract shared GoalForm and ProjectForm shells
Introduce goal-form/ and project-form/ directories each holding a single shared shell that handles both create and edit modes via a discriminated mode prop. The existing GoalCreator/GoalEditor and ProjectCreator/ProjectEditor become thin wrappers that forward props to the shared shell, eliminating duplicated state, JSX, and save/ delete handlers across the four files.
This commit is contained in:
1 parent
e892bf3366
commit
aa1204babe
8 files changed
+375
-312
No files matched your search
@@ -1,8 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { Target } from "@phosphor-icons/react";
|
||||
import type { AgentRecord, GoalRecord } from "@shared/types";
|
||||
import { ActionButton, Input, Select, TextArea } from "../ui";
|
||||
import { useT } from "../../i18n";
|
||||
import { GoalForm } from "./goal-form";
|
||||
|
||||
export function GoalCreator({
|
||||
companyId,
|
||||
@@ -15,62 +12,5 @@ export function GoalCreator({
|
||||
goals: GoalRecord[];
|
||||
onSaved: () => Promise<void>;
|
||||
}) {
|
||||
const t = useT();
|
||||
const [title, setTitle] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [ownerAgentId, setOwnerAgentId] = useState(agents[0]?.id ?? "");
|
||||
const [parentId, setParentId] = useState("");
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<Input label={t("org.goalTitle")} value={title} onChange={setTitle} placeholder={t("org.goalTitle")} />
|
||||
<TextArea
|
||||
label={t("org.successCriteria")}
|
||||
value={description}
|
||||
onChange={setDescription}
|
||||
placeholder={t("org.successCriteria")}
|
||||
/>
|
||||
<Select
|
||||
label={t("org.ownerAgent")}
|
||||
value={ownerAgentId}
|
||||
onChange={setOwnerAgentId}
|
||||
options={[
|
||||
{ value: "", label: t("org.noOwner") },
|
||||
...agents.map((agent) => ({ value: agent.id, label: agent.name })),
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label={t("org.parentGoal")}
|
||||
value={parentId}
|
||||
onChange={setParentId}
|
||||
options={[
|
||||
{ value: "", label: t("org.noParentGoal") },
|
||||
...goals.map((g) => ({ value: g.id, label: g.title })),
|
||||
]}
|
||||
/>
|
||||
<ActionButton
|
||||
label={t("org.createGoal")}
|
||||
tone="accent"
|
||||
onClick={() =>
|
||||
void (async () => {
|
||||
try {
|
||||
await window.agentCompany.saveGoal({
|
||||
companyId,
|
||||
title,
|
||||
description,
|
||||
ownerAgentId: ownerAgentId || null,
|
||||
parentId: parentId || null,
|
||||
status: "active",
|
||||
});
|
||||
setTitle("");
|
||||
setDescription("");
|
||||
await onSaved();
|
||||
} catch (error) {
|
||||
void error;
|
||||
}
|
||||
})()
|
||||
}
|
||||
icon={Target}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return <GoalForm mode="create" companyId={companyId} agents={agents} goals={goals} onSaved={onSaved} />;
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { Pencil, Trash } from "@phosphor-icons/react";
|
||||
import type { AgentRecord, GoalRecord } from "@shared/types";
|
||||
import { ActionButton, Input, Select, TextArea } from "../ui";
|
||||
import { ConfirmDelete } from "./helpers";
|
||||
import { useT } from "../../i18n";
|
||||
import { GoalForm } from "./goal-form";
|
||||
|
||||
export function GoalEditor({
|
||||
goal,
|
||||
@@ -20,94 +16,15 @@ export function GoalEditor({
|
||||
onSaved: () => Promise<void>;
|
||||
onDeleted: () => Promise<void>;
|
||||
}) {
|
||||
const [title, setTitle] = useState(goal.title);
|
||||
const [description, setDescription] = useState(goal.description);
|
||||
const [status, setStatus] = useState<GoalRecord["status"]>(goal.status);
|
||||
const [parentId, setParentId] = useState(goal.parentId ?? "");
|
||||
const [ownerAgentId, setOwnerAgentId] = useState(goal.ownerAgentId ?? "");
|
||||
const t = useT();
|
||||
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
||||
|
||||
const parentCandidates = goals.filter((g) => g.id !== goal.id);
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<Input label={t("org.goalTitle")} value={title} onChange={setTitle} placeholder={t("org.goalTitle")} />
|
||||
<TextArea label={t("org.description")} value={description} onChange={setDescription} placeholder={t("org.description")} />
|
||||
<Select
|
||||
label={t("org.status")}
|
||||
value={status}
|
||||
onChange={(v) => setStatus(v as GoalRecord["status"])}
|
||||
options={[
|
||||
{ value: "planned", label: t("org.statusPlanned") },
|
||||
{ value: "active", label: t("org.statusActive") },
|
||||
{ value: "achieved", label: t("org.statusAchieved") },
|
||||
{ value: "cancelled", label: t("org.statusCancelled") },
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label={t("org.parentGoal")}
|
||||
value={parentId}
|
||||
onChange={setParentId}
|
||||
options={[
|
||||
{ value: "", label: t("org.noParentGoal") },
|
||||
...parentCandidates.map((g) => ({ value: g.id, label: g.title })),
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label={t("org.ownerAgent")}
|
||||
value={ownerAgentId}
|
||||
onChange={setOwnerAgentId}
|
||||
options={[
|
||||
{ value: "", label: t("org.noOwner") },
|
||||
...agents.map((a) => ({ value: a.id, label: a.name })),
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<ActionButton
|
||||
label={t("org.saveChanges")}
|
||||
tone="accent"
|
||||
icon={Pencil}
|
||||
onClick={() =>
|
||||
void (async () => {
|
||||
try {
|
||||
await window.agentCompany.saveGoal({
|
||||
id: goal.id,
|
||||
companyId,
|
||||
title,
|
||||
description,
|
||||
status,
|
||||
parentId: parentId || null,
|
||||
ownerAgentId: ownerAgentId || null,
|
||||
});
|
||||
await onSaved();
|
||||
} catch (error) {
|
||||
void error;
|
||||
}
|
||||
})()
|
||||
}
|
||||
/>
|
||||
{confirmingDelete ? (
|
||||
<ConfirmDelete
|
||||
entityLabel={goal.title}
|
||||
onConfirm={() =>
|
||||
void (async () => {
|
||||
await window.agentCompany.deleteGoal({ id: goal.id, companyId });
|
||||
await onDeleted();
|
||||
})()
|
||||
}
|
||||
onCancel={() => setConfirmingDelete(false)}
|
||||
/>
|
||||
) : (
|
||||
<ActionButton
|
||||
label={t("common.delete")}
|
||||
tone="danger"
|
||||
icon={Trash}
|
||||
onClick={() => setConfirmingDelete(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<GoalForm
|
||||
mode="edit"
|
||||
companyId={companyId}
|
||||
agents={agents}
|
||||
goals={goals}
|
||||
goal={goal}
|
||||
onSaved={onSaved}
|
||||
onDeleted={onDeleted}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { Kanban } from "@phosphor-icons/react";
|
||||
import type { AgentRecord, GoalRecord } from "@shared/types";
|
||||
import { ActionButton, Input, Select, TextArea } from "../ui";
|
||||
import { ProjectForm } from "./project-form";
|
||||
|
||||
export function ProjectCreator({
|
||||
companyId,
|
||||
@@ -14,65 +12,5 @@ export function ProjectCreator({
|
||||
agents: AgentRecord[];
|
||||
onSaved: () => Promise<void>;
|
||||
}) {
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [goalId, setGoalId] = useState(goals[0]?.id ?? "");
|
||||
const [leadAgentId, setLeadAgentId] = useState(agents[0]?.id ?? "");
|
||||
const [targetDate, setTargetDate] = useState("");
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<Input label="Project name" value={name} onChange={setName} placeholder="Project name" />
|
||||
<TextArea
|
||||
label="Description"
|
||||
value={description}
|
||||
onChange={setDescription}
|
||||
placeholder="Project description"
|
||||
/>
|
||||
<Select
|
||||
label="Linked goal"
|
||||
value={goalId}
|
||||
onChange={setGoalId}
|
||||
options={[
|
||||
{ value: "", label: "No linked goal" },
|
||||
...goals.map((goal) => ({ value: goal.id, label: goal.title })),
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label="Lead agent"
|
||||
value={leadAgentId}
|
||||
onChange={setLeadAgentId}
|
||||
options={[
|
||||
{ value: "", label: "No lead" },
|
||||
...agents.map((agent) => ({ value: agent.id, label: agent.name })),
|
||||
]}
|
||||
/>
|
||||
<Input label="Target date" value={targetDate} onChange={setTargetDate} placeholder="YYYY-MM-DD" />
|
||||
<ActionButton
|
||||
label="Create project"
|
||||
tone="accent"
|
||||
onClick={() =>
|
||||
void (async () => {
|
||||
try {
|
||||
await window.agentCompany.saveProject({
|
||||
companyId,
|
||||
goalId: goalId || null,
|
||||
name,
|
||||
description,
|
||||
leadAgentId: leadAgentId || null,
|
||||
targetDate: targetDate || null,
|
||||
status: "planned",
|
||||
});
|
||||
setName("");
|
||||
setDescription("");
|
||||
setTargetDate("");
|
||||
await onSaved();
|
||||
} catch (error) {
|
||||
void error;
|
||||
}
|
||||
})()
|
||||
}
|
||||
icon={Kanban}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return <ProjectForm mode="create" companyId={companyId} goals={goals} agents={agents} onSaved={onSaved} />;
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { Pencil, Trash } from "@phosphor-icons/react";
|
||||
import type { AgentRecord, GoalRecord, ProjectRecord } from "@shared/types";
|
||||
import { ActionButton, Input, Select, TextArea } from "../ui";
|
||||
import { ConfirmDelete } from "./helpers";
|
||||
import { ProjectForm } from "./project-form";
|
||||
|
||||
export function ProjectEditor({
|
||||
project,
|
||||
@@ -19,95 +16,15 @@ export function ProjectEditor({
|
||||
onSaved: () => Promise<void>;
|
||||
onDeleted: () => Promise<void>;
|
||||
}) {
|
||||
const [name, setName] = useState(project.name);
|
||||
const [description, setDescription] = useState(project.description);
|
||||
const [status, setStatus] = useState<ProjectRecord["status"]>(project.status);
|
||||
const [goalId, setGoalId] = useState(project.goalId ?? "");
|
||||
const [leadAgentId, setLeadAgentId] = useState(project.leadAgentId ?? "");
|
||||
const [targetDate, setTargetDate] = useState(project.targetDate ?? "");
|
||||
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<Input label="Project name" value={name} onChange={setName} placeholder="Project name" />
|
||||
<TextArea label="Description" value={description} onChange={setDescription} placeholder="Description" />
|
||||
<Select
|
||||
label="Status"
|
||||
value={status}
|
||||
onChange={(v) => setStatus(v as ProjectRecord["status"])}
|
||||
options={[
|
||||
{ value: "backlog", label: "Backlog" },
|
||||
{ value: "planned", label: "Planned" },
|
||||
{ value: "in_progress", label: "In Progress" },
|
||||
{ value: "completed", label: "Completed" },
|
||||
{ value: "cancelled", label: "Cancelled" },
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label="Linked goal"
|
||||
value={goalId}
|
||||
onChange={setGoalId}
|
||||
options={[
|
||||
{ value: "", label: "No linked goal" },
|
||||
...goals.map((g) => ({ value: g.id, label: g.title })),
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label="Lead agent"
|
||||
value={leadAgentId}
|
||||
onChange={setLeadAgentId}
|
||||
options={[
|
||||
{ value: "", label: "No lead" },
|
||||
...agents.map((a) => ({ value: a.id, label: a.name })),
|
||||
]}
|
||||
/>
|
||||
<Input label="Target date" value={targetDate} onChange={setTargetDate} placeholder="YYYY-MM-DD" />
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<ActionButton
|
||||
label="Save changes"
|
||||
tone="accent"
|
||||
icon={Pencil}
|
||||
onClick={() =>
|
||||
void (async () => {
|
||||
try {
|
||||
await window.agentCompany.saveProject({
|
||||
id: project.id,
|
||||
companyId,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
goalId: goalId || null,
|
||||
leadAgentId: leadAgentId || null,
|
||||
targetDate: targetDate || null,
|
||||
});
|
||||
await onSaved();
|
||||
} catch (error) {
|
||||
void error;
|
||||
}
|
||||
})()
|
||||
}
|
||||
/>
|
||||
{confirmingDelete ? (
|
||||
<ConfirmDelete
|
||||
entityLabel={project.name}
|
||||
onConfirm={() =>
|
||||
void (async () => {
|
||||
await window.agentCompany.deleteProject({ id: project.id, companyId });
|
||||
await onDeleted();
|
||||
})()
|
||||
}
|
||||
onCancel={() => setConfirmingDelete(false)}
|
||||
/>
|
||||
) : (
|
||||
<ActionButton
|
||||
label="Delete"
|
||||
tone="danger"
|
||||
icon={Trash}
|
||||
onClick={() => setConfirmingDelete(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ProjectForm
|
||||
mode="edit"
|
||||
companyId={companyId}
|
||||
goals={goals}
|
||||
agents={agents}
|
||||
project={project}
|
||||
onSaved={onSaved}
|
||||
onDeleted={onDeleted}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import { useState } from "react";
|
||||
import { Pencil, Target, Trash } from "@phosphor-icons/react";
|
||||
import type { AgentRecord, GoalRecord } from "@shared/types";
|
||||
import { ActionButton, Input, Select, TextArea } from "../../ui";
|
||||
import { ConfirmDelete } from "../helpers";
|
||||
import { useT } from "../../../i18n";
|
||||
|
||||
type CreateProps = {
|
||||
mode: "create";
|
||||
companyId: string;
|
||||
agents: AgentRecord[];
|
||||
goals: GoalRecord[];
|
||||
onSaved: () => Promise<void>;
|
||||
};
|
||||
|
||||
type EditProps = {
|
||||
mode: "edit";
|
||||
companyId: string;
|
||||
agents: AgentRecord[];
|
||||
goals: GoalRecord[];
|
||||
goal: GoalRecord;
|
||||
onSaved: () => Promise<void>;
|
||||
onDeleted: () => Promise<void>;
|
||||
};
|
||||
|
||||
export type GoalFormProps = CreateProps | EditProps;
|
||||
|
||||
export function GoalForm(props: GoalFormProps) {
|
||||
const t = useT();
|
||||
const isEdit = props.mode === "edit";
|
||||
const editGoal = isEdit ? props.goal : null;
|
||||
|
||||
const [title, setTitle] = useState(editGoal?.title ?? "");
|
||||
const [description, setDescription] = useState(editGoal?.description ?? "");
|
||||
const [status, setStatus] = useState<GoalRecord["status"]>(editGoal?.status ?? "active");
|
||||
const [ownerAgentId, setOwnerAgentId] = useState(
|
||||
editGoal ? (editGoal.ownerAgentId ?? "") : (props.agents[0]?.id ?? ""),
|
||||
);
|
||||
const [parentId, setParentId] = useState(editGoal?.parentId ?? "");
|
||||
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
||||
|
||||
const parentCandidates = editGoal
|
||||
? props.goals.filter((g) => g.id !== editGoal.id)
|
||||
: props.goals;
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<Input label={t("org.goalTitle")} value={title} onChange={setTitle} placeholder={t("org.goalTitle")} />
|
||||
<TextArea
|
||||
label={isEdit ? t("org.description") : t("org.successCriteria")}
|
||||
value={description}
|
||||
onChange={setDescription}
|
||||
placeholder={isEdit ? t("org.description") : t("org.successCriteria")}
|
||||
/>
|
||||
{isEdit && (
|
||||
<Select
|
||||
label={t("org.status")}
|
||||
value={status}
|
||||
onChange={(v) => setStatus(v as GoalRecord["status"])}
|
||||
options={[
|
||||
{ value: "planned", label: t("org.statusPlanned") },
|
||||
{ value: "active", label: t("org.statusActive") },
|
||||
{ value: "achieved", label: t("org.statusAchieved") },
|
||||
{ value: "cancelled", label: t("org.statusCancelled") },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{isEdit ? (
|
||||
<>
|
||||
<Select
|
||||
label={t("org.parentGoal")}
|
||||
value={parentId}
|
||||
onChange={setParentId}
|
||||
options={[
|
||||
{ value: "", label: t("org.noParentGoal") },
|
||||
...parentCandidates.map((g) => ({ value: g.id, label: g.title })),
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label={t("org.ownerAgent")}
|
||||
value={ownerAgentId}
|
||||
onChange={setOwnerAgentId}
|
||||
options={[
|
||||
{ value: "", label: t("org.noOwner") },
|
||||
...props.agents.map((a) => ({ value: a.id, label: a.name })),
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Select
|
||||
label={t("org.ownerAgent")}
|
||||
value={ownerAgentId}
|
||||
onChange={setOwnerAgentId}
|
||||
options={[
|
||||
{ value: "", label: t("org.noOwner") },
|
||||
...props.agents.map((agent) => ({ value: agent.id, label: agent.name })),
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label={t("org.parentGoal")}
|
||||
value={parentId}
|
||||
onChange={setParentId}
|
||||
options={[
|
||||
{ value: "", label: t("org.noParentGoal") },
|
||||
...parentCandidates.map((g) => ({ value: g.id, label: g.title })),
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isEdit ? (
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
<ActionButton
|
||||
label={t("org.saveChanges")}
|
||||
tone="accent"
|
||||
icon={Pencil}
|
||||
onClick={() =>
|
||||
void (async () => {
|
||||
try {
|
||||
await window.agentCompany.saveGoal({
|
||||
id: props.goal.id,
|
||||
companyId: props.companyId,
|
||||
title,
|
||||
description,
|
||||
status,
|
||||
parentId: parentId || null,
|
||||
ownerAgentId: ownerAgentId || null,
|
||||
});
|
||||
await props.onSaved();
|
||||
} catch (error) {
|
||||
void error;
|
||||
}
|
||||
})()
|
||||
}
|
||||
/>
|
||||
{confirmingDelete ? (
|
||||
<ConfirmDelete
|
||||
entityLabel={props.goal.title}
|
||||
onConfirm={() =>
|
||||
void (async () => {
|
||||
await window.agentCompany.deleteGoal({ id: props.goal.id, companyId: props.companyId });
|
||||
await props.onDeleted();
|
||||
})()
|
||||
}
|
||||
onCancel={() => setConfirmingDelete(false)}
|
||||
/>
|
||||
) : (
|
||||
<ActionButton
|
||||
label={t("common.delete")}
|
||||
tone="danger"
|
||||
icon={Trash}
|
||||
onClick={() => setConfirmingDelete(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<ActionButton
|
||||
label={t("org.createGoal")}
|
||||
tone="accent"
|
||||
icon={Target}
|
||||
onClick={() =>
|
||||
void (async () => {
|
||||
try {
|
||||
await window.agentCompany.saveGoal({
|
||||
companyId: props.companyId,
|
||||
title,
|
||||
description,
|
||||
ownerAgentId: ownerAgentId || null,
|
||||
parentId: parentId || null,
|
||||
status: "active",
|
||||
});
|
||||
setTitle("");
|
||||
setDescription("");
|
||||
await props.onSaved();
|
||||
} catch (error) {
|
||||
void error;
|
||||
}
|
||||
})()
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { GoalForm } from "./GoalForm";
|
||||
export type { GoalFormProps } from "./GoalForm";
|
||||
@@ -0,0 +1,162 @@
|
||||
import { useState } from "react";
|
||||
import { Kanban, Pencil, Trash } from "@phosphor-icons/react";
|
||||
import type { AgentRecord, GoalRecord, ProjectRecord } from "@shared/types";
|
||||
import { ActionButton, Input, Select, TextArea } from "../../ui";
|
||||
import { ConfirmDelete } from "../helpers";
|
||||
|
||||
type CreateProps = {
|
||||
mode: "create";
|
||||
companyId: string;
|
||||
goals: GoalRecord[];
|
||||
agents: AgentRecord[];
|
||||
onSaved: () => Promise<void>;
|
||||
};
|
||||
|
||||
type EditProps = {
|
||||
mode: "edit";
|
||||
companyId: string;
|
||||
goals: GoalRecord[];
|
||||
agents: AgentRecord[];
|
||||
project: ProjectRecord;
|
||||
onSaved: () => Promise<void>;
|
||||
onDeleted: () => Promise<void>;
|
||||
};
|
||||
|
||||
export type ProjectFormProps = CreateProps | EditProps;
|
||||
|
||||
export function ProjectForm(props: ProjectFormProps) {
|
||||
const isEdit = props.mode === "edit";
|
||||
const editProject = isEdit ? props.project : null;
|
||||
|
||||
const [name, setName] = useState(editProject?.name ?? "");
|
||||
const [description, setDescription] = useState(editProject?.description ?? "");
|
||||
const [status, setStatus] = useState<ProjectRecord["status"]>(editProject?.status ?? "planned");
|
||||
const [goalId, setGoalId] = useState(
|
||||
editProject ? (editProject.goalId ?? "") : (props.goals[0]?.id ?? ""),
|
||||
);
|
||||
const [leadAgentId, setLeadAgentId] = useState(
|
||||
editProject ? (editProject.leadAgentId ?? "") : (props.agents[0]?.id ?? ""),
|
||||
);
|
||||
const [targetDate, setTargetDate] = useState(editProject?.targetDate ?? "");
|
||||
const [confirmingDelete, setConfirmingDelete] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<Input label="Project name" value={name} onChange={setName} placeholder="Project name" />
|
||||
<TextArea
|
||||
label="Description"
|
||||
value={description}
|
||||
onChange={setDescription}
|
||||
placeholder={isEdit ? "Description" : "Project description"}
|
||||
/>
|
||||
{isEdit && (
|
||||
<Select
|
||||
label="Status"
|
||||
value={status}
|
||||
onChange={(v) => setStatus(v as ProjectRecord["status"])}
|
||||
options={[
|
||||
{ value: "backlog", label: "Backlog" },
|
||||
{ value: "planned", label: "Planned" },
|
||||
{ value: "in_progress", label: "In Progress" },
|
||||
{ value: "completed", label: "Completed" },
|
||||
{ value: "cancelled", label: "Cancelled" },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<Select
|
||||
label="Linked goal"
|
||||
value={goalId}
|
||||
onChange={setGoalId}
|
||||
options={[
|
||||
{ value: "", label: "No linked goal" },
|
||||
...props.goals.map((g) => ({ value: g.id, label: g.title })),
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
label="Lead agent"
|
||||
value={leadAgentId}
|
||||
onChange={setLeadAgentId}
|
||||
options={[
|
||||
{ value: "", label: "No lead" },
|
||||
...props.agents.map((a) => ({ value: a.id, label: a.name })),
|
||||
]}
|
||||
/>
|
||||
<Input label="Target date" value={targetDate} onChange={setTargetDate} placeholder="YYYY-MM-DD" />
|
||||
|
||||
{isEdit ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<ActionButton
|
||||
label="Save changes"
|
||||
tone="accent"
|
||||
icon={Pencil}
|
||||
onClick={() =>
|
||||
void (async () => {
|
||||
try {
|
||||
await window.agentCompany.saveProject({
|
||||
id: props.project.id,
|
||||
companyId: props.companyId,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
goalId: goalId || null,
|
||||
leadAgentId: leadAgentId || null,
|
||||
targetDate: targetDate || null,
|
||||
});
|
||||
await props.onSaved();
|
||||
} catch (error) {
|
||||
void error;
|
||||
}
|
||||
})()
|
||||
}
|
||||
/>
|
||||
{confirmingDelete ? (
|
||||
<ConfirmDelete
|
||||
entityLabel={props.project.name}
|
||||
onConfirm={() =>
|
||||
void (async () => {
|
||||
await window.agentCompany.deleteProject({ id: props.project.id, companyId: props.companyId });
|
||||
await props.onDeleted();
|
||||
})()
|
||||
}
|
||||
onCancel={() => setConfirmingDelete(false)}
|
||||
/>
|
||||
) : (
|
||||
<ActionButton
|
||||
label="Delete"
|
||||
tone="danger"
|
||||
icon={Trash}
|
||||
onClick={() => setConfirmingDelete(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<ActionButton
|
||||
label="Create project"
|
||||
tone="accent"
|
||||
icon={Kanban}
|
||||
onClick={() =>
|
||||
void (async () => {
|
||||
try {
|
||||
await window.agentCompany.saveProject({
|
||||
companyId: props.companyId,
|
||||
goalId: goalId || null,
|
||||
name,
|
||||
description,
|
||||
leadAgentId: leadAgentId || null,
|
||||
targetDate: targetDate || null,
|
||||
status: "planned",
|
||||
});
|
||||
setName("");
|
||||
setDescription("");
|
||||
setTargetDate("");
|
||||
await props.onSaved();
|
||||
} catch (error) {
|
||||
void error;
|
||||
}
|
||||
})()
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { ProjectForm } from "./ProjectForm";
|
||||
export type { ProjectFormProps } from "./ProjectForm";
|
||||
Reference in new issue
Block a user