Search board info fields and confirm project device deletion

The distribution-board search also matches board number and location as
soon as the boards carry them (distribution-board info card of
feature/print-view); the fields are optional in the filter, so the branch
works with and without them. Deleting a project device now asks for
confirmation like the other delete actions, since the row action is a
small trash icon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Grovy311 2026-09-26 13:16:22 +02:00
parent 837b29c7c5
commit 68b9ee850b
3 changed files with 40 additions and 2 deletions

View file

@ -476,6 +476,11 @@ export default function ProjectDetailPage() {
if (!projectId || !project) { if (!projectId || !project) {
return; return;
} }
const device = projectDevices.find((item) => item.id === projectDeviceId);
const deviceName = device?.displayName || device?.name || "Projektgerät";
if (!window.confirm(`Projektgerät „${deviceName}“ wirklich löschen?`)) {
return;
}
setIsSaving(true); setIsSaving(true);
setError(null); setError(null);
try { try {
@ -1053,7 +1058,7 @@ export default function ProjectDetailPage() {
onChange={(query) => onChange={(query) =>
setBoardFilters((current) => ({ ...current, query })) setBoardFilters((current) => ({ ...current, query }))
} }
placeholder="Verteilung oder Etage …" placeholder="Name, Nr., Örtlichkeit oder Etage …"
value={boardFilters.query} value={boardFilters.query}
/> />
<MultiSelectDropdown <MultiSelectDropdown

View file

@ -154,8 +154,16 @@ export function buildSupplyTypeFilterOptions(
return options; return options;
} }
/**
* Board number and location come with the distribution-board info card
* (feature/print-view). They are optional here so the filter works with and
* without those fields and searches them as soon as the DTO carries them.
*/
export function filterDistributionBoards< export function filterDistributionBoards<
Board extends Pick<DistributionBoardDto, "name" | "floorId" | "supplyType">, Board extends Pick<DistributionBoardDto, "name" | "floorId" | "supplyType"> & {
boardNumber?: string | null;
location?: string | null;
},
>( >(
boards: readonly Board[], boards: readonly Board[],
filters: DistributionBoardFilters, filters: DistributionBoardFilters,
@ -166,6 +174,8 @@ export function filterDistributionBoards<
(board) => (board) =>
matchesQuery(query, [ matchesQuery(query, [
board.name, board.name,
board.boardNumber,
board.location,
board.floorId ? floorNameById.get(board.floorId) : undefined, board.floorId ? floorNameById.get(board.floorId) : undefined,
]) && ]) &&
matchesSelection( matchesSelection(

View file

@ -82,6 +82,29 @@ describe("overview filters: distribution boards", () => {
); );
}); });
it("also searches board number and location when the boards carry them", () => {
const withInfo = [
{ ...boards[0]!, boardNumber: "UV 0.1", location: "Raum 003 Technik" },
{ ...boards[1]!, boardNumber: null, location: null },
];
assert.deepEqual(
filterDistributionBoards(
withInfo,
{ ...emptyDistributionBoardFilters, query: "uv 0.1" },
floorNameById
).map((entry) => entry.id),
[withInfo[0]!.id]
);
assert.deepEqual(
filterDistributionBoards(
withInfo,
{ ...emptyDistributionBoardFilters, query: "technik" },
floorNameById
).map((entry) => entry.id),
[withInfo[0]!.id]
);
});
it("searches board and floor names case-insensitively and ignores blank queries", () => { it("searches board and floor names case-insensitively and ignores blank queries", () => {
const byFloorName = filterDistributionBoards( const byFloorName = filterDistributionBoards(
boards, boards,