@@ -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 };
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { NoteResponse } from './NoteResponse';
|
||||
import { TaskResponse } from './TaskResponse';
|
||||
|
||||
export type HomeSearchResponse = {
|
||||
tasks: TaskResponse[],
|
||||
notes: NoteResponse[]
|
||||
}
|
||||
+112
-48
@@ -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) {}
|
||||
|
||||
+9
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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();
|
||||
|
||||
+2
-1
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user