feat: search ok

issue #46
This commit is contained in:
Ricardo Campos
2024-09-20 19:32:05 -03:00
parent 081efbcf12
commit 1c7576109e
8 changed files with 180 additions and 64 deletions
+47 -6
View File
@@ -1,11 +1,16 @@
import { API_TOKEN } from '../app-constants/app-constants';
import { HomeSearchResponse } from '../types/HomeSearchResponse';
import { SummaryResponse } from '../types/SummaryResponse';
import ApiConfig from './apiConfig';
/**
* Sends a GET request to the server to get summaries for the home page.
*
* @returns {Promise<SummaryResponse>} A promise that resolves to SummaryResponse if the request
* was successful.
* @throws {Error} An error object if there was an error
*/
async function getHomeSummary(): Promise<SummaryResponse | Error> {
async function getHomeSummary(): Promise<SummaryResponse> {
try {
const tokenState = localStorage.getItem(API_TOKEN);
const response = await fetch(`${ApiConfig.homeUrl}/summary`, {
@@ -21,17 +26,53 @@ async function getHomeSummary(): Promise<SummaryResponse | Error> {
return data;
}
if (response.status === 403) {
return new Error('Forbidden! Access denied');
throw new Error('Forbidden! Access denied');
}
if (response.status === 500) {
return new Error('Internal Server Error!');
throw new Error('Internal Server Error!');
}
} catch (e) {
if (typeof e === 'string') {
return new Error(e as string);
throw new Error(e as string);
}
}
return new Error('Unknown error');
throw new Error('Unknown error');
}
export { getHomeSummary };
/**
* Sends a GET request to the server to get summaries for the home page.
*
* @returns {Promise<HomeSearchResponse>} A promise that resolves to HomeSearchResponse if the
* request was successful.
* @throws {Error} An error object if there was an error
*/
async function searchHomeRequest(term: string): Promise<HomeSearchResponse> {
try {
const tokenState = localStorage.getItem(API_TOKEN);
const response = await fetch(`${ApiConfig.homeUrl}/search?term=${term}`, {
method: 'GET',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${tokenState}`
}
});
if (response.ok) {
const data = await response.json();
return data;
}
if (response.status === 403) {
throw new Error('Forbidden! Access denied');
}
if (response.status === 500) {
throw new Error('Internal Server Error!');
}
} catch (e) {
if (typeof e === 'string') {
throw new Error(e as string);
}
}
throw new Error('Unknown error');
}
export { getHomeSummary, searchHomeRequest };
+7
View File
@@ -0,0 +1,7 @@
import { NoteResponse } from './NoteResponse';
import { TaskResponse } from './TaskResponse';
export type HomeSearchResponse = {
tasks: TaskResponse[],
notes: NoteResponse[]
}
+112 -48
View File
@@ -1,11 +1,15 @@
import React, { useEffect, useState } from 'react';
import {
Accordion,
Alert,
Button, Card, Col, Container, Form, InputGroup, Row
} from 'react-bootstrap';
import './style.css';
import { getHomeSummary } from '../../api-service/homeService';
import { getHomeSummary, searchHomeRequest } from '../../api-service/homeService';
import { SummaryResponse } from '../../types/SummaryResponse';
import { HomeSearchResponse } from '../../types/HomeSearchResponse';
import { TaskResponse } from '../../types/TaskResponse';
import { NoteResponse } from '../../types/NoteResponse';
/**
*
@@ -15,6 +19,7 @@ function Home(): JSX.Element {
const [summary, setSummary] = useState<SummaryResponse | undefined>();
const [validated, setValidated] = useState<boolean>(true);
const [formInvalid, setFormInvalid] = useState<boolean>(false);
const [searchResults, setSearchResults] = useState<HomeSearchResponse | null>(null);
const handleError = (e: unknown): void => {
if (typeof e === 'string') {
@@ -25,14 +30,25 @@ function Home(): JSX.Element {
};
const getSummary = async () => {
const response = await getHomeSummary();
if (response instanceof Error) {
handleError(response);
} else {
try {
const response = await getHomeSummary();
setSummary(response);
} catch (e) {
handleError(e);
}
};
const searchTerm = async (term: string): Promise<boolean> => {
try {
const response: HomeSearchResponse = await searchHomeRequest(term);
setSearchResults(response);
return true;
} catch (e) {
handleError(e);
}
return false;
};
const handleSearch = async (event: React.FormEvent<HTMLFormElement>): Promise<void> => {
event.preventDefault();
event.stopPropagation();
@@ -41,15 +57,15 @@ function Home(): JSX.Element {
const form = event.currentTarget;
if (form.checkValidity() === false) {
setFormInvalid(true);
setErrorMessage('Please type at least 3 characters');
return;
}
setFormInvalid(false);
// console.log('search for', form.search_term.value);
// const added: boolean = await addTask(form.description.value, form.url.value);
// if (added) {
// form.reset();
// }
const searched: boolean = await searchTerm(form.search_term.value);
if (searched) {
form.reset();
}
};
useEffect(() => {
@@ -58,11 +74,51 @@ function Home(): JSX.Element {
return (
<Container>
<Row className="mt-3">
<h1 className="mt-5 mb-4">Welcome to Your Dashboard</h1>
<Row className="mb-4">
<Col xs={12} md={6}>
<Card className="text-center h-100">
<Card.Header className="bg-primary text-white">
Tasks Summary
</Card.Header>
<Card.Body className="d-flex flex-column align-items-center justify-content-center">
<Card.Title className="display-4">
{summary?.pendingTaskCount || '0'}
</Card.Title>
<Card.Text>
{summary?.pendingTaskCount && summary?.pendingTaskCount > 0
? 'Pending Tasks'
: 'No pending tasks!'}
</Card.Text>
<Button variant="primary" href="/tasks">
Go to Tasks
</Button>
</Card.Body>
</Card>
</Col>
<Col xs={12} md={6}>
<Card className="text-center h-100">
<Card.Header className="bg-success text-white">
Notes Summary
</Card.Header>
<Card.Body className="d-flex flex-column align-items-center justify-content-center">
<Card.Title className="display-4">10</Card.Title>
<Card.Text>Notes</Card.Text>
<Button variant="success" href="/notes">
Go to Notes
</Button>
</Card.Body>
</Card>
</Col>
</Row>
<Row className="mb-4">
<Col xs={12}>
<Card>
<Card.Body>
<Card.Title>Search task or note</Card.Title>
<Card.Title>Search Tasks and Notes</Card.Title>
{formInvalid ? (
<Alert variant="danger">
@@ -78,51 +134,59 @@ function Home(): JSX.Element {
type="text"
id="search_term"
name="search_term"
placeholder="Search term"
placeholder="Enter your search term..."
/>
<Button type="submit" variant="outline-secondary" id="button-search">
<Button type="submit" variant="primary" id="button-search">
Search
</Button>
</InputGroup>
</Form>
</Card.Body>
<Card.Footer>3 record(s) found!</Card.Footer>
</Card>
</Col>
</Row>
<Row className="mt-3">
<Col xs={12} sm={6}>
<Card className="text-center">
<Card.Header>Tasks summary</Card.Header>
<Card.Body>
<Card.Title>
{summary?.pendingTaskCount && summary?.pendingTaskCount > 0
? `${summary?.pendingTaskCount}` : 'Zero!'}
</Card.Title>
<Card.Text>
{summary?.pendingTaskCount && summary?.pendingTaskCount > 0
? `${summary?.pendingTaskCount} Pending Tasks` : 'No pending tasks!'}
</Card.Text>
<Button variant="primary" type="button">Go to Tasks</Button>
</Card.Body>
<Card.Footer>
{summary?.doneTaskCount && summary?.doneTaskCount > 0
? 'All tasks finished! Well done!' : '🤔 No tasks done yet!?'}
</Card.Footer>
</Card>
</Col>
<Col xs={12} sm={6}>
<Card className="text-center">
<Card.Header>Notes</Card.Header>
<Card.Body>
<Card.Title>Notes summary</Card.Title>
<Card.Text>
10
</Card.Text>
<Button variant="primary" type="button">Go to Notes</Button>
</Card.Body>
<Card.Footer>Test?</Card.Footer>
</Card>
<Row>
<Col xs={12}>
<h2>Search Results</h2>
<Accordion defaultActiveKey="0">
{searchResults && searchResults.tasks.length > 0 && (
searchResults.tasks.map((task: TaskResponse) => (
<Accordion.Item key={task.description} eventKey={task.description}>
<Accordion.Header>
[Task]
{' '}
{task.description}
</Accordion.Header>
<Accordion.Body>
{task.urls.length > 0 ? (
<a href={`${task.urls[0].url}`}>{task.urls[0].url}</a>
) : 'No URL!'}
</Accordion.Body>
</Accordion.Item>
))
)}
{searchResults && searchResults.notes.length > 0 && (
searchResults.notes.map((note: NoteResponse) => (
<Accordion.Item key={note.title} eventKey={note.title}>
<Accordion.Header>
[Note]
{' '}
{note.title}
</Accordion.Header>
<Accordion.Body>
<span className="span-line-break">
{ note.description }
</span>
</Accordion.Body>
</Accordion.Item>
))
)}
{searchResults?.tasks.length === 0 && searchResults?.notes.length === 0 && (
<h3>No results</h3>
)}
</Accordion>
</Col>
</Row>
</Container>
@@ -82,6 +82,5 @@ public class HomeController {
@RequestParam(value = "term", required = false)
String term) {
return homeService.search(term);
// keep going from here
}
}
@@ -2,9 +2,4 @@ package br.com.tasknoteapp.java_api.response;
import java.util.List;
public record SearchResponse(
List<TaskResponse> tasks
//List<NoteResponse> notes
) {
}
public record SearchResponse(List<TaskResponse> tasks, List<NoteResponse> notes) {}
@@ -1,9 +1,11 @@
package br.com.tasknoteapp.java_api.service.impl;
import br.com.tasknoteapp.java_api.response.NoteResponse;
import br.com.tasknoteapp.java_api.response.SearchResponse;
import br.com.tasknoteapp.java_api.response.SummaryResponse;
import br.com.tasknoteapp.java_api.response.TaskResponse;
import br.com.tasknoteapp.java_api.service.HomeService;
import br.com.tasknoteapp.java_api.service.NoteService;
import br.com.tasknoteapp.java_api.service.TaskService;
import java.util.List;
import lombok.AllArgsConstructor;
@@ -18,6 +20,8 @@ class HomeServiceImpl implements HomeService {
private final TaskService taskService;
private final NoteService noteService;
/**
* Get summary for the home page.
*
@@ -45,7 +49,11 @@ class HomeServiceImpl implements HomeService {
log.info("Searching for {}", term);
List<TaskResponse> tasks = taskService.searchTasks(term);
log.info("{} tasks found!", tasks.size());
return new SearchResponse(tasks);
List<NoteResponse> notes = noteService.searchNotes(term);
log.info("{} notes found!", notes.size());
return new SearchResponse(tasks, notes);
}
}
@@ -143,7 +143,8 @@ public class NoteServiceImpl implements NoteService {
log.info("Searching notes to user {}", user.getId());
List<NoteEntity> notes = noteRepository.findAllBySearchTerm(searchTerm, user.getId());
List<NoteEntity> notes =
noteRepository.findAllBySearchTerm(searchTerm.toUpperCase(), user.getId());
log.info("{} tasks found!", notes.size());
return notes.stream().map(NoteResponse::fromEntity).toList();
@@ -144,7 +144,8 @@ class TaskServiceImpl implements TaskService {
log.info("Searching tasks to user {}", user.getId());
List<TaskEntity> tasks = taskRepository.findAllBySearchTerm(searchTerm, user.getId());
List<TaskEntity> tasks =
taskRepository.findAllBySearchTerm(searchTerm.toUpperCase(), user.getId());
log.info("{} tasks found!", tasks.size());
return tasks.stream().map(TaskResponse::fromEntity).toList();