mirror of
https://github.com/rjNemo/ticket_manager
synced 2026-06-06 00:36:39 +00:00
Add input File design, filtering for project page tabs
This commit is contained in:
parent
14e47378dd
commit
2419627400
15 changed files with 156 additions and 59 deletions
|
|
@ -1,18 +1,26 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC, useState, ChangeEvent } from "react";
|
||||||
import { FloatingButton } from "./FloatingButton";
|
|
||||||
import { ActivityCollection } from "./ActivityCollection";
|
import { ActivityCollection } from "./ActivityCollection";
|
||||||
import { Activity } from "../types/Activity";
|
import { Activity } from "../types/Activity";
|
||||||
|
import { FilterBar } from "./FilterBar";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
activities: Activity[];
|
activities: Activity[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ActivityList: FC<IProps> = ({ activities }) => {
|
export const ActivityList: FC<IProps> = ({ activities }) => {
|
||||||
|
const [filterText, setFilterText] = useState<string>("");
|
||||||
|
|
||||||
|
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
|
||||||
|
e: ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
setFilterText(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="row valign-wrapper">
|
<div className="row valign-wrapper">
|
||||||
<h3>Activity</h3>
|
<h3>Activity</h3>
|
||||||
<FloatingButton color=" blue-grey lighten-4" size="" />
|
<FilterBar filterText={filterText} handleChange={handleChange} />
|
||||||
</div>
|
</div>
|
||||||
<ActivityCollection activities={activities} />
|
<ActivityCollection activities={activities} />
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,28 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC, useState, ChangeEvent } from "react";
|
||||||
import { AppFile } from "../types/AppFile";
|
import { AppFile } from "../types/AppFile";
|
||||||
import { FloatingButton } from "./FloatingButton";
|
|
||||||
import { FileCollection } from "./FileCollection";
|
import { FileCollection } from "./FileCollection";
|
||||||
import { DropZone } from "./DropZone";
|
import { InputFile } from "./InputFile";
|
||||||
|
import { FilterBar } from "./FilterBar";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
files: AppFile[];
|
files: AppFile[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FileList: FC<IProps> = ({ files }) => {
|
export const FileList: FC<IProps> = ({ files }) => {
|
||||||
|
const [filterText, setFilterText] = useState<string>("");
|
||||||
|
|
||||||
|
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
|
||||||
|
e: ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
setFilterText(e.target.value);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="row valign-wrapper">
|
<div className="row valign-wrapper">
|
||||||
<h3>Files</h3>
|
<h3>Files</h3>
|
||||||
<FloatingButton color=" blue-grey lighten-4" size="" />
|
<FilterBar filterText={filterText} handleChange={handleChange} />
|
||||||
</div>
|
</div>
|
||||||
<DropZone />
|
<InputFile />
|
||||||
<FileCollection files={files} />
|
<FileCollection files={files} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import React, { FC } from "react";
|
|
||||||
|
|
||||||
type IProps = {};
|
|
||||||
|
|
||||||
export const DropZone: FC<IProps> = () => {
|
|
||||||
return <div className="copy">Drag & Drop your files here.</div>;
|
|
||||||
};
|
|
||||||
39
client/src/components/FilterBar.tsx
Normal file
39
client/src/components/FilterBar.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { useRouteMatch } from "react-router-dom";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
filterText: string;
|
||||||
|
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FilterBar: FC<IProps> = ({ filterText, handleChange }) => {
|
||||||
|
const { url } = useRouteMatch();
|
||||||
|
const placeholder: string = url.split("/")[3];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="nav-wrapper">
|
||||||
|
<div className="input-field">
|
||||||
|
<input
|
||||||
|
// className="validate"
|
||||||
|
id="filter"
|
||||||
|
type="search"
|
||||||
|
required
|
||||||
|
name="filter"
|
||||||
|
value={filterText}
|
||||||
|
placeholder={`Filter ${placeholder}`}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<label className="label-icon" htmlFor="search">
|
||||||
|
<i className="material-icons">filter_list</i>
|
||||||
|
</label>
|
||||||
|
<i
|
||||||
|
className="material-icons" //onClick={clearSearchBar}
|
||||||
|
>
|
||||||
|
close
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div className="col s2 valign-wrapper"></div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import React, { FC, MouseEvent } from "react";
|
import React, { FC, MouseEvent } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { getRemainingdays } from "../utils/methods";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
title: string;
|
title: string;
|
||||||
remainingDays?: number;
|
remainingDays: string;
|
||||||
validateTicket: (event: MouseEvent) => void;
|
validateTicket: (event: MouseEvent) => void;
|
||||||
archiveTicket: (event: MouseEvent) => void;
|
archiveTicket: (event: MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +27,7 @@ export const HorizontalCard: FC<IProps> = ({
|
||||||
</Link>
|
</Link>
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<span>Due {remainingDays} days</span>
|
<span>Due {getRemainingdays(remainingDays)} days</span>
|
||||||
<div className="right">
|
<div className="right">
|
||||||
<Link to="#">
|
<Link to="#">
|
||||||
<i className="material-icons" onClick={validateTicket}>
|
<i className="material-icons" onClick={validateTicket}>
|
||||||
|
|
|
||||||
29
client/src/components/InputFile.tsx
Normal file
29
client/src/components/InputFile.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
|
||||||
|
type IProps = {};
|
||||||
|
|
||||||
|
export const InputFile: FC<IProps> = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form action="/upload">
|
||||||
|
<div className="file-field input-field">
|
||||||
|
<div className="btn">
|
||||||
|
<i className="material-icons ">cloud_upload</i>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
accept=".doc,.docx,.pdf,.md,.gdoc,.zip,image/*"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="file-path-wrapper">
|
||||||
|
<input
|
||||||
|
className="file-path validate"
|
||||||
|
type="text"
|
||||||
|
placeholder="Upload one or more files"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -18,7 +18,6 @@ interface IProps {
|
||||||
|
|
||||||
export const TabRouter: FC<IProps> = ({
|
export const TabRouter: FC<IProps> = ({
|
||||||
tickets,
|
tickets,
|
||||||
remainingDays,
|
|
||||||
tabNames,
|
tabNames,
|
||||||
files,
|
files,
|
||||||
activities
|
activities
|
||||||
|
|
@ -32,7 +31,7 @@ export const TabRouter: FC<IProps> = ({
|
||||||
<Redirect from={url} to={`${url}/tickets`} />
|
<Redirect from={url} to={`${url}/tickets`} />
|
||||||
|
|
||||||
<Route path={`${url}/tickets`}>
|
<Route path={`${url}/tickets`}>
|
||||||
<TicketList tickets={tickets} remainingDays={remainingDays} />
|
<TicketList tickets={tickets} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={`${url}/files`}>
|
<Route path={`${url}/files`}>
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,65 @@
|
||||||
import React, { FC } from "react";
|
import React, { FC, useState, ChangeEvent } from "react";
|
||||||
import { Ticket } from "../types/Ticket";
|
import { Ticket } from "../types/Ticket";
|
||||||
import { FloatingButton } from "./FloatingButton";
|
import { FloatingButton } from "./FloatingButton";
|
||||||
import { HorizontalCard } from "./HorizontalCard";
|
import { HorizontalCard } from "./HorizontalCard";
|
||||||
|
import { FilterBar } from "./FilterBar";
|
||||||
|
|
||||||
type TicketListProps = {
|
type TicketListProps = {
|
||||||
tickets: Ticket[];
|
tickets: Ticket[];
|
||||||
remainingDays?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TicketList: FC<TicketListProps> = ({ tickets, remainingDays }) => {
|
export const TicketList: FC<TicketListProps> = ({ tickets }) => {
|
||||||
const archiveTicket = () => {};
|
const archiveTicket = () => {};
|
||||||
const validateTicket = () => {};
|
const validateTicket = () => {};
|
||||||
|
const [filterText, setFilterText] = useState<string>("");
|
||||||
|
|
||||||
|
const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
|
||||||
|
e: ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
setFilterText(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// const useFilter = (init: string) => {
|
||||||
|
// const [filterText, setFilterText] = useState<string>(init);
|
||||||
|
// // const handleChange: (e: ChangeEvent<HTMLInputElement>) => void = (
|
||||||
|
// // e: ChangeEvent<HTMLInputElement>
|
||||||
|
// // ) => {
|
||||||
|
// // setFilterText(e.target.value);
|
||||||
|
// // };
|
||||||
|
// return {
|
||||||
|
// filterText,
|
||||||
|
// setFilterText,
|
||||||
|
// bind: {
|
||||||
|
// filterText,
|
||||||
|
// handleChange: (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
// setFilterText(e.target.value);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
// const [filterText, handleChange] = useFilter("");
|
||||||
|
// const [filterText, handleChange] = useFilter("");
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="row valign-wrapper">
|
<div className="row valign-wrapper">
|
||||||
<h3>Tickets</h3>
|
<h3>Tickets</h3>
|
||||||
<FloatingButton color=" blue-grey lighten-4" size="big" />
|
<FloatingButton color=" blue-grey lighten-4" size="small" />
|
||||||
|
<FilterBar filterText={filterText} handleChange={handleChange} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col s12 grey">
|
<div className="col s12 grey">
|
||||||
<ul>
|
<ul>
|
||||||
{tickets.map((t: Ticket) => (
|
{tickets
|
||||||
<li key={t.id}>
|
.filter(t => t.title.includes(filterText))
|
||||||
<HorizontalCard
|
.map((t: Ticket) => (
|
||||||
title={t.title}
|
<li key={t.id}>
|
||||||
remainingDays={remainingDays}
|
<HorizontalCard
|
||||||
validateTicket={validateTicket}
|
title={t.title}
|
||||||
archiveTicket={archiveTicket}
|
remainingDays={t.plannedEnding}
|
||||||
/>
|
validateTicket={validateTicket}
|
||||||
</li>
|
archiveTicket={archiveTicket}
|
||||||
))}
|
/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,16 @@ export const ProjectController: FC = () => {
|
||||||
const tickets: Ticket[] = [
|
const tickets: Ticket[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "Ticket #1",
|
title: "Client objective meeting",
|
||||||
|
description: "Client objective meeting",
|
||||||
|
plannedEnding: "2020-02-17 15:51:02.787373",
|
||||||
status: "Done"
|
status: "Done"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: "Ticket #2",
|
title: "Assemble Outcomes Report for client",
|
||||||
|
description: "Assemble Outcomes Report for client",
|
||||||
|
plannedEnding: "2020-02-27 15:51:02.787373",
|
||||||
status: "To Do"
|
status: "To Do"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
||||||
monospace;
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import "./index.css";
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import * as serviceWorker from "./serviceWorker";
|
import * as serviceWorker from "./serviceWorker";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ export const ProjectPage: FC<IProps> = ({ viewModel }) => {
|
||||||
tabNames={tabNames}
|
tabNames={tabNames}
|
||||||
tickets={tickets}
|
tickets={tickets}
|
||||||
files={files}
|
files={files}
|
||||||
remainingDays={remainingDays}
|
|
||||||
activities={activities}
|
activities={activities}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
export interface Ticket {
|
export interface Ticket {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
|
description: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
plannedEnding: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
client/src/utils/methods.ts
Normal file
7
client/src/utils/methods.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const getRemainingdays: (endDate: string) => number = (
|
||||||
|
endDate: string
|
||||||
|
) => {
|
||||||
|
let endingDate: Date = new Date(endDate);
|
||||||
|
let today: Date = new Date();
|
||||||
|
return Math.abs(endingDate.getDate() - today.getDate());
|
||||||
|
};
|
||||||
|
|
@ -5,6 +5,7 @@ import { Project } from "../types/Project";
|
||||||
import { AppFile } from "../types/AppFile";
|
import { AppFile } from "../types/AppFile";
|
||||||
import { Activity } from "../types/Activity";
|
import { Activity } from "../types/Activity";
|
||||||
import { User } from "../types/User";
|
import { User } from "../types/User";
|
||||||
|
import { getRemainingdays } from "../utils/methods";
|
||||||
|
|
||||||
export default class ProjectVM {
|
export default class ProjectVM {
|
||||||
public id: number;
|
public id: number;
|
||||||
|
|
@ -12,7 +13,6 @@ export default class ProjectVM {
|
||||||
public description: string;
|
public description: string;
|
||||||
public value: number;
|
public value: number;
|
||||||
public tickets: Ticket[];
|
public tickets: Ticket[];
|
||||||
// public avatars: string[];
|
|
||||||
public users: User[];
|
public users: User[];
|
||||||
public ticketsTotalCount: number;
|
public ticketsTotalCount: number;
|
||||||
public ticketsDone: number;
|
public ticketsDone: number;
|
||||||
|
|
@ -35,7 +35,6 @@ export default class ProjectVM {
|
||||||
this.id = project.id;
|
this.id = project.id;
|
||||||
this.title = project.title;
|
this.title = project.title;
|
||||||
this.description = project.description;
|
this.description = project.description;
|
||||||
// this.avatars = project.users.map(u => u.picture);
|
|
||||||
this.users = project.users;
|
this.users = project.users;
|
||||||
this.value = project.progression;
|
this.value = project.progression;
|
||||||
this.tickets = project.tickets;
|
this.tickets = project.tickets;
|
||||||
|
|
@ -43,12 +42,6 @@ export default class ProjectVM {
|
||||||
this.ticketsDone = this.tickets.filter(t => t.status === "Done").length;
|
this.ticketsDone = this.tickets.filter(t => t.status === "Done").length;
|
||||||
this.files = project.files;
|
this.files = project.files;
|
||||||
this.activities = project.activities;
|
this.activities = project.activities;
|
||||||
|
this.remainingDays = getRemainingdays(project.plannedEnding);
|
||||||
let endingDate: Date = new Date(project.plannedEnding);
|
|
||||||
let today: Date = new Date();
|
|
||||||
let plannedEnding: number = Math.abs(
|
|
||||||
endingDate.getDate() - today.getDate()
|
|
||||||
);
|
|
||||||
this.remainingDays = plannedEnding;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue