From 6e3d3347e20b95acbd8d2489065cb85a95fa6737 Mon Sep 17 00:00:00 2001 From: kasun Date: Wed, 24 Jun 2026 15:57:10 +0200 Subject: [PATCH] added search response into searchmodal ui, restructured folders --- .../app/search/HomeboxSearchProvider.java | 5 ++- .../com/vaessl/app/search/SearchResponse.java | 3 +- .../test/java/com/vaessl/app/Mockdata.java | 1 + .../vaessl/app/search/SearchResponseTest.java | 7 +-- frontend/src/App.tsx | 2 +- frontend/src/api/searches.ts | 5 +-- .../{ => connections}/ConnectModal.tsx | 6 +-- .../{ => connections}/Dashboard.scss | 0 .../{ => connections}/Dashboard.tsx | 6 +-- .../{ => connections}/ServiceCard.scss | 0 .../{ => connections}/ServiceCard.tsx | 4 +- .../components/{ => search}/SearchModal.tsx | 42 ++++++++++++------ .../src/components/{ => ui}/ActionButton.scss | 0 .../src/components/{ => ui}/ActionButton.tsx | 0 .../{ConnectModal.scss => ui/Modal.scss} | 44 ++++++++++++++++++- frontend/src/types/search.ts | 19 +++++++- 16 files changed, 110 insertions(+), 34 deletions(-) rename frontend/src/components/{ => connections}/ConnectModal.tsx (95%) rename frontend/src/components/{ => connections}/Dashboard.scss (100%) rename frontend/src/components/{ => connections}/Dashboard.tsx (94%) rename frontend/src/components/{ => connections}/ServiceCard.scss (100%) rename frontend/src/components/{ => connections}/ServiceCard.tsx (93%) rename frontend/src/components/{ => search}/SearchModal.tsx (51%) rename frontend/src/components/{ => ui}/ActionButton.scss (100%) rename frontend/src/components/{ => ui}/ActionButton.tsx (100%) rename frontend/src/components/{ConnectModal.scss => ui/Modal.scss} (72%) diff --git a/backend/src/main/java/com/vaessl/app/search/HomeboxSearchProvider.java b/backend/src/main/java/com/vaessl/app/search/HomeboxSearchProvider.java index d21f9c6..b494f36 100644 --- a/backend/src/main/java/com/vaessl/app/search/HomeboxSearchProvider.java +++ b/backend/src/main/java/com/vaessl/app/search/HomeboxSearchProvider.java @@ -64,10 +64,11 @@ public class HomeboxSearchProvider implements SearchProvider { } List items = hbResponse.items().stream().map(i -> { + String id = i.id(); String title = i.name(); String description = i.description(); Map extraSearchResponseData = Map.of("location", i.parent()); - return new SearchResponse(title, description, extraSearchResponseData); + return new SearchResponse(id, title, description, extraSearchResponseData); }).toList(); log.info("Search results for query '{}': {}", request.query(), items); @@ -79,7 +80,7 @@ public class HomeboxSearchProvider implements SearchProvider { List items) { } - private record HomeboxItem(String name, String description, HomeboxLocation parent) { + private record HomeboxItem(String id, String name, String description, HomeboxLocation parent) { } private record HomeboxLocation(String name, String description) { diff --git a/backend/src/main/java/com/vaessl/app/search/SearchResponse.java b/backend/src/main/java/com/vaessl/app/search/SearchResponse.java index 9257fd8..5327334 100644 --- a/backend/src/main/java/com/vaessl/app/search/SearchResponse.java +++ b/backend/src/main/java/com/vaessl/app/search/SearchResponse.java @@ -2,7 +2,8 @@ package com.vaessl.app.search; import java.util.Map; -public record SearchResponse(String title, String description, Map extraData) { +public record SearchResponse(String id, String title, String description, + Map extraData) { public String getExtra(String key) { if (extraData == null) { diff --git a/backend/src/test/java/com/vaessl/app/Mockdata.java b/backend/src/test/java/com/vaessl/app/Mockdata.java index 9d435e3..6e0c411 100644 --- a/backend/src/test/java/com/vaessl/app/Mockdata.java +++ b/backend/src/test/java/com/vaessl/app/Mockdata.java @@ -8,6 +8,7 @@ public final class Mockdata { public static final String MOCK_SERVICE_TYPE = "SERVICE_TYPE"; public static final String MOCK_USER = "user"; public static final String MOCK_PASS = "pw"; + public static final String MOCK_ID = "item-1"; public static final String MOCK_TITLE = "title"; public static final String MOCK_DESCRIPTION = "desc"; } diff --git a/backend/src/test/java/com/vaessl/app/search/SearchResponseTest.java b/backend/src/test/java/com/vaessl/app/search/SearchResponseTest.java index 265abcd..0970e25 100644 --- a/backend/src/test/java/com/vaessl/app/search/SearchResponseTest.java +++ b/backend/src/test/java/com/vaessl/app/search/SearchResponseTest.java @@ -9,7 +9,7 @@ class SearchResponseTest { @Test void shouldReturnNullWhenExtraDataIsNull() { - SearchResponse response = new SearchResponse(MOCK_TITLE, MOCK_DESCRIPTION, null); + SearchResponse response = new SearchResponse(MOCK_ID, MOCK_TITLE, MOCK_DESCRIPTION, null); assertThat(response.getExtra(null)).isNull(); } @@ -17,7 +17,7 @@ class SearchResponseTest { @Test void shouldReturnNullWhenExtraDataKeyIsMissing() { - SearchResponse response = new SearchResponse(MOCK_TITLE, MOCK_DESCRIPTION, Map.of("key", "value")); + SearchResponse response = new SearchResponse(MOCK_ID, MOCK_TITLE, MOCK_DESCRIPTION, Map.of("key", "value")); assertThat(response.getExtra("missing")).isNull(); } @@ -25,8 +25,9 @@ class SearchResponseTest { @Test void shouldReturnExtraDataValue() { - SearchResponse response = new SearchResponse(MOCK_TITLE, MOCK_DESCRIPTION, Map.of("key", "value")); + SearchResponse response = new SearchResponse(MOCK_ID, MOCK_TITLE, MOCK_DESCRIPTION, Map.of("key", "value")); + assertThat(response.id()).isEqualTo(MOCK_ID); assertThat(response.getExtra("key")).contains("value"); } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c30b522..44a492a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,5 @@ -import { Dashboard } from './components/Dashboard' +import { Dashboard } from './components/connections/Dashboard' function App() { return ( diff --git a/frontend/src/api/searches.ts b/frontend/src/api/searches.ts index ad471f7..18aaa08 100644 --- a/frontend/src/api/searches.ts +++ b/frontend/src/api/searches.ts @@ -1,9 +1,8 @@ import { apiFetch } from "./client"; -import type { AuthResponse } from "../types/connection"; -import type { SearchRequest } from "../types/search"; +import type { PagedSearchResponse, SearchRequest, SearchResponse } from "../types/search"; export const search = (req: SearchRequest) => - apiFetch("/search", { + apiFetch>("/search", { method: "POST", body: JSON.stringify(req), }); diff --git a/frontend/src/components/ConnectModal.tsx b/frontend/src/components/connections/ConnectModal.tsx similarity index 95% rename from frontend/src/components/ConnectModal.tsx rename to frontend/src/components/connections/ConnectModal.tsx index 692d74b..ebce9d0 100644 --- a/frontend/src/components/ConnectModal.tsx +++ b/frontend/src/components/connections/ConnectModal.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState, type SyntheticEvent } from 'react' -import { login } from '../api/connections' -import type { LoginRequest } from '../types/connection' -import './ConnectModal.scss' +import { login } from '../../api/connections' +import type { LoginRequest } from '../../types/connection' +import '../ui/Modal.scss' interface Props { serviceType: string diff --git a/frontend/src/components/Dashboard.scss b/frontend/src/components/connections/Dashboard.scss similarity index 100% rename from frontend/src/components/Dashboard.scss rename to frontend/src/components/connections/Dashboard.scss diff --git a/frontend/src/components/Dashboard.tsx b/frontend/src/components/connections/Dashboard.tsx similarity index 94% rename from frontend/src/components/Dashboard.tsx rename to frontend/src/components/connections/Dashboard.tsx index c2c6827..b7feed9 100644 --- a/frontend/src/components/Dashboard.tsx +++ b/frontend/src/components/connections/Dashboard.tsx @@ -1,10 +1,10 @@ import { useState, useEffect } from "react" -import { getStatuses, logout } from "../api/connections" -import type { ConnectionStatus } from "../types/connection" +import { getStatuses, logout } from "../../api/connections" +import type { ConnectionStatus } from "../../types/connection" import { ServiceCard } from "./ServiceCard" import { ConnectModal } from "./ConnectModal" import "./Dashboard.scss" -import { SearchModal } from "./SearchModal" +import { SearchModal } from "../search/SearchModal" const SERVICES = [ { serviceType: 'HOMEBOX', label: 'Homebox', icon: '📦' }, diff --git a/frontend/src/components/ServiceCard.scss b/frontend/src/components/connections/ServiceCard.scss similarity index 100% rename from frontend/src/components/ServiceCard.scss rename to frontend/src/components/connections/ServiceCard.scss diff --git a/frontend/src/components/ServiceCard.tsx b/frontend/src/components/connections/ServiceCard.tsx similarity index 93% rename from frontend/src/components/ServiceCard.tsx rename to frontend/src/components/connections/ServiceCard.tsx index 6ed8517..cfcbd91 100644 --- a/frontend/src/components/ServiceCard.tsx +++ b/frontend/src/components/connections/ServiceCard.tsx @@ -1,5 +1,5 @@ -import type { ConnectionStatus } from '../types/connection' -import { ActionButton } from './ActionButton' +import type { ConnectionStatus } from '../../types/connection' +import { ActionButton } from '../ui/ActionButton' import './ServiceCard.scss' interface Props { diff --git a/frontend/src/components/SearchModal.tsx b/frontend/src/components/search/SearchModal.tsx similarity index 51% rename from frontend/src/components/SearchModal.tsx rename to frontend/src/components/search/SearchModal.tsx index 904f05c..9b1fdbe 100644 --- a/frontend/src/components/SearchModal.tsx +++ b/frontend/src/components/search/SearchModal.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState, type SyntheticEvent } from 'react' -import './ConnectModal.scss' -import type { SearchRequest } from '../types/search' -import { search } from '../api/searches' +import '../ui/Modal.scss' +import { type PagedSearchResponse, type SearchResponse, type SearchRequest } from '../../types/search' +import { search } from '../../api/searches' interface Props { serviceType: string @@ -12,7 +12,8 @@ interface Props { } export function SearchModal({ serviceType, label, appUrl, username, onClose }: Readonly) { - const [query, setQuery] = useState(''); + const [query, setQuery] = useState('') + const [results, setResults] = useState | null>(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const firstInputRef = useRef(null) @@ -40,7 +41,10 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R setLoading(true) try { const req: SearchRequest = { appUrl, serviceType, username, query} - await search(req) + const res = await search(req) + console.log(req) + setResults(res) + console.log('results', results) } catch (err) { setError(err instanceof Error ? err.message : 'Search failed') } finally { @@ -49,21 +53,31 @@ export function SearchModal({ serviceType, label, appUrl, username, onClose }: R } return ( - -
- - + +
+ +
-
-
- +
+ setQuery(e.target.value)} />
- {error &&

{error}

} - + {results && ( +
+

{results.totalElements}

+ {results.content.map((item) => (
+

{item.title}

+ {item.description &&

{item.description}

} + +
))} +
+ )}
) } diff --git a/frontend/src/components/ActionButton.scss b/frontend/src/components/ui/ActionButton.scss similarity index 100% rename from frontend/src/components/ActionButton.scss rename to frontend/src/components/ui/ActionButton.scss diff --git a/frontend/src/components/ActionButton.tsx b/frontend/src/components/ui/ActionButton.tsx similarity index 100% rename from frontend/src/components/ActionButton.tsx rename to frontend/src/components/ui/ActionButton.tsx diff --git a/frontend/src/components/ConnectModal.scss b/frontend/src/components/ui/Modal.scss similarity index 72% rename from frontend/src/components/ConnectModal.scss rename to frontend/src/components/ui/Modal.scss index b816391..0bdd995 100644 --- a/frontend/src/components/ConnectModal.scss +++ b/frontend/src/components/ui/Modal.scss @@ -17,6 +17,8 @@ width: 100%; max-width: 420px; box-shadow: var(--shadow); + display: flex; + flex-direction: column; &__header { display: flex; @@ -87,7 +89,7 @@ font-size: 13px; color: #ef4444; padding: 8px 12px; - background: rgba(239, 68, 68, 0.08); + background: rgba(11, 0, 0, 0.499); border-radius: 6px; border: 1px solid rgba(239, 68, 68, 0.2); } @@ -110,4 +112,44 @@ &:disabled { opacity: 0.6; cursor: not-allowed; } &:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } } + + &--expanded { + max-height: 70vh; + } + + &__results { + overflow-y: auto; + flex: 1; + min-height: 0; // required: flex children won't shrink without this + margin-top: 20px; + display: flex; + flex-direction: column; + gap: 8px; + } + + &__results-count { + font-size: 12px; + color: var(--text); + margin: 0 0 8px 0; + } + + &__result-item { + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + } + + &__result-title { + font-weight: 500; + font-size: 14px; + color: var(--text-h); + margin: 0; + } + + &__result-desc { + font-size: 13px; + color: var(--text); + margin: 4px 0 0 0; + } } diff --git a/frontend/src/types/search.ts b/frontend/src/types/search.ts index c195a92..5c1b8c7 100644 --- a/frontend/src/types/search.ts +++ b/frontend/src/types/search.ts @@ -2,5 +2,22 @@ export interface SearchRequest { appUrl: string; serviceType: string; username: string; - query: string; + query: string | null; +} + +export interface SearchResponse { + id: string; + title: string; + description: string | null; + extraData: Record; +} + +export interface PagedSearchResponse { + content: T[]; + page: number; + pageSize: number; + totalElements: number; + first: boolean; + last: boolean; + sort: string; }