refactor(frontend): align request person chips
This commit is contained in:
parent
4127e65c4f
commit
192140c95d
@ -19,6 +19,7 @@ import {
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
} from "react";
|
||||
import { RequestUserBadge } from "./RequestUserBadge";
|
||||
|
||||
type RequestComposeModalProps = {
|
||||
actionError: string | null;
|
||||
@ -70,7 +71,7 @@ export function RequestComposeModal({
|
||||
const selectableEmployees = useMemo(
|
||||
() => employees
|
||||
.filter((employee) => !currentUserId || employee.id !== currentUserId)
|
||||
.map((employee) => ({ id: employee.id, name: displayUserName(employee) })),
|
||||
.map((employee) => ({ id: employee.id, name: displayUserName(employee), user: employee })),
|
||||
[currentUserId, employees],
|
||||
);
|
||||
|
||||
@ -312,6 +313,13 @@ export function RequestComposeModal({
|
||||
items={selectableEmployees}
|
||||
selectedIds={form.recipient_ids}
|
||||
onToggle={(id) => toggleSelection("recipient_ids", id)}
|
||||
renderSelectedItem={(item) => (
|
||||
<RequestUserBadge
|
||||
person={item.user}
|
||||
currentUserId={currentUserId}
|
||||
disableLink
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{showCcField && (
|
||||
<SearchableSelectMulti
|
||||
@ -321,6 +329,13 @@ export function RequestComposeModal({
|
||||
items={selectableEmployees}
|
||||
selectedIds={form.cc_user_ids}
|
||||
onToggle={(id) => toggleSelection("cc_user_ids", id)}
|
||||
renderSelectedItem={(item) => (
|
||||
<RequestUserBadge
|
||||
person={item.user}
|
||||
currentUserId={currentUserId}
|
||||
disableLink
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -53,16 +53,7 @@ type RequestListItemProps = {
|
||||
function renderDecisionMaker(decisionMaker: User | undefined, currentUserId?: number | null) {
|
||||
if (!decisionMaker) return <span className="app-text-muted">—</span>;
|
||||
|
||||
const decisionMakerLink = userProfileLink(decisionMaker, currentUserId);
|
||||
const decisionMakerName = displayUserName(decisionMaker);
|
||||
|
||||
return decisionMakerLink ? (
|
||||
<Link href={decisionMakerLink} className="app-link-accent font-medium">
|
||||
{decisionMakerName}
|
||||
</Link>
|
||||
) : (
|
||||
<span className="font-medium text-[var(--foreground)]">{decisionMakerName}</span>
|
||||
);
|
||||
return <RequestUserBadge person={decisionMaker} currentUserId={currentUserId} />;
|
||||
}
|
||||
|
||||
export function RequestListItem({
|
||||
|
||||
@ -5,12 +5,14 @@ import { RequestAvatar } from "./RequestAvatar";
|
||||
|
||||
type RequestUserBadgeProps = {
|
||||
currentUserId?: number | null;
|
||||
disableLink?: boolean;
|
||||
large?: boolean;
|
||||
person: User;
|
||||
};
|
||||
|
||||
export function RequestUserBadge({
|
||||
currentUserId,
|
||||
disableLink = false,
|
||||
large = false,
|
||||
person,
|
||||
}: RequestUserBadgeProps) {
|
||||
@ -31,7 +33,7 @@ export function RequestUserBadge({
|
||||
|
||||
const className = `app-badge inline-flex max-w-full items-center gap-2 rounded-full ${large ? "px-3 py-1.5 text-sm" : "px-2.5 py-1 text-xs"} font-medium`;
|
||||
|
||||
return personLink
|
||||
return personLink && !disableLink
|
||||
? <Link href={personLink} className={`${className} hover:bg-[var(--surface-tertiary)]`}>{chip}</Link>
|
||||
: <span className={className}>{chip}</span>;
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
/* ── Single-select dropdown with search ── */
|
||||
@ -91,7 +92,7 @@ export function SearchableSelectSingle<T extends string | number>({
|
||||
}
|
||||
|
||||
/* ── Multi-select dropdown with search ── */
|
||||
export function SearchableSelectMulti({
|
||||
export function SearchableSelectMulti<T extends { id: number; name: string }>({
|
||||
label,
|
||||
items,
|
||||
selectedIds,
|
||||
@ -99,14 +100,16 @@ export function SearchableSelectMulti({
|
||||
placeholder,
|
||||
layout = "stacked",
|
||||
className = "",
|
||||
renderSelectedItem,
|
||||
}: {
|
||||
label: string;
|
||||
items: { id: number; name: string }[];
|
||||
items: T[];
|
||||
selectedIds: number[];
|
||||
onToggle: (id: number) => void;
|
||||
placeholder?: string;
|
||||
layout?: "stacked" | "inline";
|
||||
className?: string;
|
||||
renderSelectedItem?: (item: T) => ReactNode;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [q, setQ] = useState("");
|
||||
@ -140,9 +143,15 @@ export function SearchableSelectMulti({
|
||||
<span className="app-text-muted w-14 shrink-0 pt-0.5 text-sm font-medium">{label}</span>
|
||||
<span className="flex min-w-0 flex-1 flex-wrap gap-1.5">
|
||||
{selectedItems.length > 0 ? selectedItems.map((item) => (
|
||||
<span key={item.id} className="app-badge inline-flex max-w-full items-center rounded-full px-2.5 py-1 text-xs font-medium">
|
||||
<span className="truncate">{item.name}</span>
|
||||
</span>
|
||||
renderSelectedItem ? (
|
||||
<span key={item.id} className="min-w-0 max-w-full">
|
||||
{renderSelectedItem(item)}
|
||||
</span>
|
||||
) : (
|
||||
<span key={item.id} className="app-badge inline-flex max-w-full items-center rounded-full px-2.5 py-1 text-xs font-medium">
|
||||
<span className="truncate">{item.name}</span>
|
||||
</span>
|
||||
)
|
||||
)) : <span className="app-text-muted py-0.5">{placeholder || "Выбрать..."}</span>}
|
||||
</span>
|
||||
</>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user