Skip to content

Commit

Permalink
Merge pull request #6 from soprodecarnaval/feature/drag-and-drop
Browse files Browse the repository at this point in the history
Add pagination, sorting and deleting for results tables
  • Loading branch information
danielSbastos authored Nov 5, 2023
2 parents be94747 + e59930e commit ddac4e4
Show file tree
Hide file tree
Showing 15 changed files with 365 additions and 107 deletions.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Musicoteca</title>
<title>Cadern.in</title>
</head>
<body>
<div id="root"></div>
Expand Down
4 changes: 4 additions & 0 deletions src/css/ArrangementItem.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@
.arrow-down {
transform: rotate(0.50turn)
}

.cursor:hover {
cursor: pointer;
}
14 changes: 14 additions & 0 deletions src/css/PaginationBar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}

/* Firefox */
input[type=number] {
-moz-appearance: textfield;
}

.page {
width: 4.5vh !important;
}
3 changes: 3 additions & 0 deletions src/css/PartItem.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.cursor:hover {
cursor: pointer;
}
8 changes: 8 additions & 0 deletions src/css/PreviewModal.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
.modal-dialog {
display: table
}

img {
padding: 0;
display: block;
margin: 0 auto;
max-height: 90vh;
max-width: 90vh;
}
67 changes: 48 additions & 19 deletions src/tsx/App.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,56 @@
import { useState } from "react";
import { Col, Container, Navbar, Row } from "react-bootstrap";

import { Sort } from "./Sort";
import { SearchBar } from "./SearchBar";
import { ArrangementsTable } from "./ArrangementsTable";
import { ChosenArrangementsTable } from "./ChosenArrangementsTable";
import { PDFGenerator } from "./PdfGenerator";
import { sortByColumn } from "./helper/sorter";

import type { HydratedSong } from "../types";

import "bootstrap/dist/css/bootstrap.css";
import "../css/App.css";

function App() {
const [results, setResults] = useState<HydratedSong[]>([]);
const [checkedResults, setCheckedResults] = useState<HydratedSong[]>([]);
const [selectedResults, setSelectedResults] = useState<HydratedSong[]>([]);

const handleSelectSong = (song: HydratedSong, checked: boolean) => {
checked ? handleAddSong(song) : handleRemoveSong(song)
};

const handleCheck = (song: HydratedSong, checked: boolean) => {
if (checked) {
setCheckedResults([...checkedResults, song]);
return;
}
const clearSelected = () => {
setSelectedResults([]);
}

const updatedRes = checkedResults.filter(
const handleAddSong = (song: HydratedSong) => {
setSelectedResults([...selectedResults, song]);
const updatedRes = results.filter(
(r) => r.arrangements[0].id !== song.arrangements[0].id
);
setCheckedResults(updatedRes);
};

setResults(updatedRes);
}

const handleRemoveSong = (song: HydratedSong) => {
const updatedRes = selectedResults.filter(
(r) => r.arrangements[0].id !== song.arrangements[0].id
);
setResults([ song, ...results]);
setSelectedResults(updatedRes);
}

const handleSelectedResultsSortBy = (column : string, direction: string) => {
const sorted = sortByColumn(selectedResults, column, direction)
setSelectedResults(sorted.slice())
}

const handleResultsSortBy = (column: string, direction: string) => {
const sorted = sortByColumn(results, column, direction)
setResults(sorted.slice())
}

return (
<>
Expand All @@ -34,34 +62,35 @@ function App() {
>
<Container>
<Navbar.Brand className="nav-bar-title" href="#">
Musicoteca
Cadern.in
</Navbar.Brand>
</Container>
</Navbar>
<Container>
<SearchBar handleResults={setResults} />
<PDFGenerator songs={checkedResults}></PDFGenerator>
<PDFGenerator songs={selectedResults}></PDFGenerator>
<Row className="mt-4">
<Col sm={6}>
{results.length > 0 && (
<>
<h3 className="results">Resultados</h3>
<Sort onSortBy={handleResultsSortBy} />
<ArrangementsTable
songs={results}
handleCheck={handleCheck}
readOnly={false}
handleSelect={handleSelectSong}
/>
</>
)}
</Col>
<Col sm={6}>
{results.length > 0 && (
{(selectedResults.length > 0 || results.length > 0) && (
<>
<h3 className="results">Resultados Selecionados</h3>
<ArrangementsTable
songs={checkedResults}
handleCheck={handleCheck}
readOnly={true}
<h3 className="results">Resultados selecionados</h3>
<Sort onSortBy={handleSelectedResultsSortBy} />
<ChosenArrangementsTable
songs={selectedResults}
handleSelect={handleSelectSong}
handleClear={clearSelected}
/>
</>
)}
Expand Down
61 changes: 11 additions & 50 deletions src/tsx/ArrangementItem.tsx
Original file line number Diff line number Diff line change
@@ -1,87 +1,48 @@
import { useState } from "react";
import { Form } from "react-bootstrap";
import { BsTriangleFill } from "react-icons/bs";
import { AiFillEye } from "react-icons/ai";

import type { Part, Arrangement, HydratedSong } from "../types";

import { PreviewModal } from "./PreviewModal";
import type { Arrangement, HydratedSong } from "../types";
import { PartItem } from "./PartItem";

import "../css/ArrangementItem.css";

interface ArrangementItemProps {
handleCheck: (song: HydratedSong, checked: boolean) => void;
handleSelect: (song: HydratedSong, checked: boolean) => void;
song: HydratedSong;
arrangement: Arrangement;
readOnly: boolean;
}

interface PartItemProps {
part: Part;
}

const PartItem = ({ part }: PartItemProps) => {
const [showPreview, setShowPreview] = useState(false);

return (
<tr className="instrument">
<td onClick={() => setShowPreview(true)} colSpan={4}>
<AiFillEye className="visualize-icon" />
<label>{part.instrument}</label>
</td>
<PreviewModal
show={showPreview}
handleShow={setShowPreview}
part={part}
/>
</tr>
);
};

const ArrangementItem = ({
handleCheck,
readOnly,
handleSelect,
song,
arrangement,
}: ArrangementItemProps) => {
const [expand, setExpand] = useState(false);
const [checked, setChecked] = useState(false);

const handleOnChange = () => {
handleCheck({ ...song, arrangements: [arrangement] }, !checked);
handleSelect({ ...song, arrangements: [arrangement] }, !checked);
setChecked(!checked);
};

return (
<>
<tr>
<tr className="cursor">
<td className="action-cell">
<BsTriangleFill
onClick={() => setExpand(!expand)}
className={`arrow ${expand ? "arrow-down" : "arrow-right"}`}
/>
{!readOnly && (
<Form.Check
checked={checked}
onChange={handleOnChange}
className="arrangement-checkbox"
type="checkbox"
id="default-checkbox"
/>
)}
</td>
<td>{song.title}</td>
<td>{song.composer}</td>
<td>{arrangement.name}</td>
<td>{arrangement.tags}</td>
<td onClick={handleOnChange}>{song.title}</td>
<td onClick={handleOnChange}>{arrangement.name}</td>
<td onClick={handleOnChange}>{arrangement.tags}</td>
</tr>
{expand ? (
{expand &&
arrangement.parts.map((part) => (
<PartItem part={part} key={part.name} />
))
) : (
<></>
)}
}
</>
);
};
Expand Down
65 changes: 37 additions & 28 deletions src/tsx/ArrangementsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,52 @@ import { Table } from "react-bootstrap";
import type { HydratedSong } from "../types";

import { ArrangementItem } from "./ArrangementItem";
import { PaginationBar } from './PaginationBar';
import { useState } from "react";

interface ArrangementsTableProps {
songs: HydratedSong[];
readOnly: boolean;
handleCheck: (song: HydratedSong, checked: boolean) => void;
handleSelect: (song: HydratedSong, checked: boolean) => void;
}

const ArrangementsTable = ({
songs,
readOnly,
handleCheck,
handleSelect,
}: ArrangementsTableProps) => {
const [currentPage, setCurrentPagee] = useState(1);

const maxNumberPages = Math.round(songs.length / 10) + 1;

const handlePageChange = (pageNumber : number) => {
setCurrentPagee(pageNumber);
};

return (
<Table striped borderless hover>
<thead>
<tr>
<th></th>
<th>Título</th>
<th>Compositor</th>
<th>Arranjo</th>
<th>Tags</th>
</tr>
</thead>
<tbody>
{songs.map((song, songIdx) =>
song.arrangements.map((arrangement) => (
<ArrangementItem
readOnly={readOnly}
handleCheck={handleCheck}
arrangement={arrangement}
song={songs[songIdx]}
key={arrangement.name}
/>
))
)}
</tbody>
</Table>
<>
<Table striped borderless hover>
<thead>
<tr>
<th></th>
<th>Título</th>
<th>Arranjo</th>
<th>Tags</th>
</tr>
</thead>
<tbody>
{songs.slice((currentPage - 1) * 10, currentPage * 10).map((song, songIdx) =>
song.arrangements.map((arrangement) => (
<ArrangementItem
handleSelect={handleSelect}
arrangement={arrangement}
song={songs[songIdx]}
key={arrangement.name}
/>
))
)}
</tbody>
</Table>
<PaginationBar onChange={handlePageChange} currentPage={currentPage} maxNumberPages={maxNumberPages} />
</>
);
};

Expand Down
49 changes: 49 additions & 0 deletions src/tsx/ChosenArrangementItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useState } from "react";
import { BsTriangleFill, BsFillTrashFill } from "react-icons/bs";

import type { Arrangement, HydratedSong } from "../types";
import { PartItem } from "./PartItem";

import "../css/ArrangementItem.css";

interface ArrangementItemProps {
handleSelect: (song: HydratedSong, checked: boolean) => void;
song: HydratedSong;
arrangement: Arrangement;
}

const ChosenArrangementItem = ({
handleSelect,
song,
arrangement,
}: ArrangementItemProps) => {
const [expand, setExpand] = useState(false);

const handleDelete = () => {
handleSelect({ ...song, arrangements: [arrangement] }, false);
}

return (
<>
<tr className="cursor">
<td className="action-cell">
<BsTriangleFill
onClick={() => setExpand(!expand)}
className={`arrow ${expand ? "arrow-down" : "arrow-right"}`}
/>
</td>
<td onClick={() => setExpand(!expand)}>{song.title}</td>
<td onClick={() => setExpand(!expand)}>{arrangement.name}</td>
<td onClick={() => setExpand(!expand)}>{arrangement.tags}</td>
<td><BsFillTrashFill onClick={handleDelete} /></td>
</tr>
{expand &&
arrangement.parts.map((part) => (
<PartItem part={part} key={part.name} />
))
}
</>
);
};

export { ChosenArrangementItem };
Loading

0 comments on commit ddac4e4

Please sign in to comment.