feat(page): Replace Select components with SearchableSelect for improved usability
This commit is contained in:
@@ -9,19 +9,15 @@ import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
|||||||
import { DashboardShell } from "@/components/templates/DashboardShell";
|
import { DashboardShell } from "@/components/templates/DashboardShell";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import SearchableSelect, {
|
||||||
|
type SearchableSelectOption,
|
||||||
|
} from "@/components/ui/searchable-select";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { getApiBaseUrl } from "@/lib/api-base";
|
import { getApiBaseUrl } from "@/lib/api-base";
|
||||||
import {
|
import {
|
||||||
DEFAULT_IDENTITY_TEMPLATE,
|
DEFAULT_IDENTITY_TEMPLATE,
|
||||||
DEFAULT_SOUL_TEMPLATE,
|
DEFAULT_SOUL_TEMPLATE,
|
||||||
} from "@/lib/agent-templates";
|
} from "@/lib/agent-templates";
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
|
|
||||||
const apiBase = getApiBaseUrl();
|
const apiBase = getApiBaseUrl();
|
||||||
|
|
||||||
@@ -43,6 +39,17 @@ type Board = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const HEARTBEAT_TARGET_OPTIONS: SearchableSelectOption[] = [
|
||||||
|
{ value: "none", label: "None (no outbound message)" },
|
||||||
|
{ value: "last", label: "Last channel" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const getBoardOptions = (boards: Board[]): SearchableSelectOption[] =>
|
||||||
|
boards.map((board) => ({
|
||||||
|
value: board.id,
|
||||||
|
label: board.name,
|
||||||
|
}));
|
||||||
|
|
||||||
export default function EditAgentPage() {
|
export default function EditAgentPage() {
|
||||||
const { getToken, isSignedIn } = useAuth();
|
const { getToken, isSignedIn } = useAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -231,22 +238,19 @@ export default function EditAgentPage() {
|
|||||||
<label className="text-sm font-medium text-slate-900">
|
<label className="text-sm font-medium text-slate-900">
|
||||||
Board <span className="text-red-500">*</span>
|
Board <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Select
|
<SearchableSelect
|
||||||
|
ariaLabel="Select board"
|
||||||
value={boardId}
|
value={boardId}
|
||||||
onValueChange={(value) => setBoardId(value)}
|
onValueChange={setBoardId}
|
||||||
|
options={getBoardOptions(boards)}
|
||||||
|
placeholder="Select board"
|
||||||
|
searchPlaceholder="Search boards..."
|
||||||
|
emptyMessage="No matching boards."
|
||||||
|
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
||||||
|
contentClassName="rounded-xl border border-slate-200 shadow-lg"
|
||||||
|
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
|
||||||
disabled={boards.length === 0}
|
disabled={boards.length === 0}
|
||||||
>
|
/>
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select board" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{boards.map((board) => (
|
|
||||||
<SelectItem key={board.id} value={board.id}>
|
|
||||||
{board.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
{boards.length === 0 ? (
|
{boards.length === 0 ? (
|
||||||
<p className="text-xs text-slate-500">
|
<p className="text-xs text-slate-500">
|
||||||
Create a board before assigning agents.
|
Create a board before assigning agents.
|
||||||
@@ -260,7 +264,7 @@ export default function EditAgentPage() {
|
|||||||
<p className="text-xs font-semibold uppercase tracking-wider text-slate-500">
|
<p className="text-xs font-semibold uppercase tracking-wider text-slate-500">
|
||||||
Agent persona
|
Agent persona
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 space-y-4">
|
<div className="mt-4 grid gap-6 md:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-slate-900">
|
<label className="text-sm font-medium text-slate-900">
|
||||||
Identity template
|
Identity template
|
||||||
@@ -271,6 +275,10 @@ export default function EditAgentPage() {
|
|||||||
rows={8}
|
rows={8}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
|
<p className="text-xs text-slate-500">
|
||||||
|
Keep the agent_name and agent_id variables unchanged so
|
||||||
|
the gateway can render them correctly.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-slate-900">
|
<label className="text-sm font-medium text-slate-900">
|
||||||
@@ -309,21 +317,19 @@ export default function EditAgentPage() {
|
|||||||
<label className="text-sm font-medium text-slate-900">
|
<label className="text-sm font-medium text-slate-900">
|
||||||
Target
|
Target
|
||||||
</label>
|
</label>
|
||||||
<Select
|
<SearchableSelect
|
||||||
|
ariaLabel="Select heartbeat target"
|
||||||
value={heartbeatTarget}
|
value={heartbeatTarget}
|
||||||
onValueChange={(value) => setHeartbeatTarget(value)}
|
onValueChange={setHeartbeatTarget}
|
||||||
|
options={HEARTBEAT_TARGET_OPTIONS}
|
||||||
|
placeholder="Select target"
|
||||||
|
searchPlaceholder="Search targets..."
|
||||||
|
emptyMessage="No matching targets."
|
||||||
|
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
||||||
|
contentClassName="rounded-xl border border-slate-200 shadow-lg"
|
||||||
|
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
/>
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select target" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="none">
|
|
||||||
None (no outbound message)
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="last">Last channel</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,19 +9,15 @@ import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
|||||||
import { DashboardShell } from "@/components/templates/DashboardShell";
|
import { DashboardShell } from "@/components/templates/DashboardShell";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import SearchableSelect, {
|
||||||
|
type SearchableSelectOption,
|
||||||
|
} from "@/components/ui/searchable-select";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { getApiBaseUrl } from "@/lib/api-base";
|
import { getApiBaseUrl } from "@/lib/api-base";
|
||||||
import {
|
import {
|
||||||
DEFAULT_IDENTITY_TEMPLATE,
|
DEFAULT_IDENTITY_TEMPLATE,
|
||||||
DEFAULT_SOUL_TEMPLATE,
|
DEFAULT_SOUL_TEMPLATE,
|
||||||
} from "@/lib/agent-templates";
|
} from "@/lib/agent-templates";
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
|
|
||||||
const apiBase = getApiBaseUrl();
|
const apiBase = getApiBaseUrl();
|
||||||
|
|
||||||
@@ -36,6 +32,17 @@ type Board = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const HEARTBEAT_TARGET_OPTIONS: SearchableSelectOption[] = [
|
||||||
|
{ value: "none", label: "None (no outbound message)" },
|
||||||
|
{ value: "last", label: "Last channel" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const getBoardOptions = (boards: Board[]): SearchableSelectOption[] =>
|
||||||
|
boards.map((board) => ({
|
||||||
|
value: board.id,
|
||||||
|
label: board.name,
|
||||||
|
}));
|
||||||
|
|
||||||
export default function NewAgentPage() {
|
export default function NewAgentPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { getToken, isSignedIn } = useAuth();
|
const { getToken, isSignedIn } = useAuth();
|
||||||
@@ -177,22 +184,19 @@ export default function NewAgentPage() {
|
|||||||
<label className="text-sm font-medium text-slate-900">
|
<label className="text-sm font-medium text-slate-900">
|
||||||
Board <span className="text-red-500">*</span>
|
Board <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Select
|
<SearchableSelect
|
||||||
|
ariaLabel="Select board"
|
||||||
value={boardId}
|
value={boardId}
|
||||||
onValueChange={(value) => setBoardId(value)}
|
onValueChange={setBoardId}
|
||||||
|
options={getBoardOptions(boards)}
|
||||||
|
placeholder="Select board"
|
||||||
|
searchPlaceholder="Search boards..."
|
||||||
|
emptyMessage="No matching boards."
|
||||||
|
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
||||||
|
contentClassName="rounded-xl border border-slate-200 shadow-lg"
|
||||||
|
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
|
||||||
disabled={boards.length === 0}
|
disabled={boards.length === 0}
|
||||||
>
|
/>
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select board" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{boards.map((board) => (
|
|
||||||
<SelectItem key={board.id} value={board.id}>
|
|
||||||
{board.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
{boards.length === 0 ? (
|
{boards.length === 0 ? (
|
||||||
<p className="text-xs text-slate-500">
|
<p className="text-xs text-slate-500">
|
||||||
Create a board before adding agents.
|
Create a board before adding agents.
|
||||||
@@ -206,7 +210,7 @@ export default function NewAgentPage() {
|
|||||||
<p className="text-xs font-semibold uppercase tracking-wider text-slate-500">
|
<p className="text-xs font-semibold uppercase tracking-wider text-slate-500">
|
||||||
Agent persona
|
Agent persona
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 space-y-4">
|
<div className="mt-4 grid gap-6 md:grid-cols-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-slate-900">
|
<label className="text-sm font-medium text-slate-900">
|
||||||
Identity template
|
Identity template
|
||||||
@@ -217,6 +221,10 @@ export default function NewAgentPage() {
|
|||||||
rows={8}
|
rows={8}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
|
<p className="text-xs text-slate-500">
|
||||||
|
Keep the agent_name and agent_id variables unchanged so
|
||||||
|
the gateway can render them correctly.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium text-slate-900">
|
<label className="text-sm font-medium text-slate-900">
|
||||||
@@ -255,21 +263,19 @@ export default function NewAgentPage() {
|
|||||||
<label className="text-sm font-medium text-slate-900">
|
<label className="text-sm font-medium text-slate-900">
|
||||||
Target
|
Target
|
||||||
</label>
|
</label>
|
||||||
<Select
|
<SearchableSelect
|
||||||
|
ariaLabel="Select heartbeat target"
|
||||||
value={heartbeatTarget}
|
value={heartbeatTarget}
|
||||||
onValueChange={(value) => setHeartbeatTarget(value)}
|
onValueChange={setHeartbeatTarget}
|
||||||
|
options={HEARTBEAT_TARGET_OPTIONS}
|
||||||
|
placeholder="Select target"
|
||||||
|
searchPlaceholder="Search targets..."
|
||||||
|
emptyMessage="No matching targets."
|
||||||
|
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
|
||||||
|
contentClassName="rounded-xl border border-slate-200 shadow-lg"
|
||||||
|
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
/>
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select target" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="none">
|
|
||||||
None (no outbound message)
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="last">Last channel</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user