diff --git a/frontend/compositions/officer-view/index.tsx b/frontend/compositions/officer-view/index.tsx index 7317e2be..ffad1c4f 100644 --- a/frontend/compositions/officer-view/index.tsx +++ b/frontend/compositions/officer-view/index.tsx @@ -3,20 +3,53 @@ import OptionalOfficerInfo from "./optional-officer-info" import OfficerWorkHistory from "./officer-work-history" import OfficerAffiliations from "./officer-affiliations" import { OfficerRecordType } from "../../models/officer" -import { DataTable } from "../../shared-components/data-table/data-table" -import { resultsColumns } from "../search-results/search-results" -import { EXISTING_TEST_INCIDENTS } from "../../helpers/api/mocks/data" +import styles from "./officer-view.module.css" +import { faArrowLeft } from "@fortawesome/free-solid-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { useRouter } from "next/router" +import { OfficerIncidentDataTable } from "../../shared-components/officer-incident-data-table/officer-incident-data-table" +import { incidentResultsColumns } from "../../models/incident" export default function OfficerView(officer: OfficerRecordType) { + const { officerView, profilePicture, officerViewHeader, wrapper, backButton } = styles + const router = useRouter() + + const BackButton = () => { + return ( + router.back()} + /> + ) + } + return ( - <> - -
- -
- - - {/* TODO: */} - +
+ +
+ Profile Picture +
+ + + + {officer.incidents && officer.incidents.length > 0 && ( + + )} +
+
+
) } diff --git a/frontend/compositions/officer-view/officer-view-header/index.tsx b/frontend/compositions/officer-view/officer-view-header/index.tsx index 92dbf4fa..ecaa4f5b 100644 --- a/frontend/compositions/officer-view/officer-view-header/index.tsx +++ b/frontend/compositions/officer-view/officer-view-header/index.tsx @@ -3,21 +3,14 @@ import styles from "./officer-view-header.module.css" export default function OfficerHeader(officer: OfficerRecordType) { const { firstName, lastName, knownEmployers } = officer - const { category, name, titleAndName, otherData, viewWrapper } = styles + const { name, title } = styles return (
-

Officer Record

-
-
-

- {firstName} {lastName} -

-
-
-

Known Employers

-
-
+

Officer Record

+

+ {firstName} {lastName} +

) } diff --git a/frontend/compositions/officer-view/officer-view-header/officer-view-header.module.css b/frontend/compositions/officer-view/officer-view-header/officer-view-header.module.css index bb9add76..cf500232 100644 --- a/frontend/compositions/officer-view/officer-view-header/officer-view-header.module.css +++ b/frontend/compositions/officer-view/officer-view-header/officer-view-header.module.css @@ -1,22 +1,11 @@ -.category { - display: block; - font-size: var(--size10); - color: var(--grey); - margin-left: 0; -} - .name { font-size: var(--size32); + font-weight: 400; + margin-bottom: 0.3rem; } -.titleAndName { - width: 33%; -} - -.otherData { - width: 22%; -} - -.viewWrapper { - display: flex; +.title { + font-size: var(--size16); + font-weight: 700; + margin-top: 0.5rem; } diff --git a/frontend/compositions/officer-view/officer-view.module.css b/frontend/compositions/officer-view/officer-view.module.css new file mode 100644 index 00000000..651c5179 --- /dev/null +++ b/frontend/compositions/officer-view/officer-view.module.css @@ -0,0 +1,56 @@ +.officerView { + display: flex; + flex-direction: row; + padding: 1rem; /* Added padding for mobile devices */ +} + +.officerView > div { + flex: 1; +} + +.profilePicture { + width: 8rem; /* Set your desired width */ + height: 8rem; /* Set your desired height */ + border-radius: 50%; + margin-right: 1rem; +} + +.officerViewHeader { + display: flex; + flex-direction: row; +} + +.wrapper { + display: flex; + flex-direction: column; /* Adjusted flex direction for mobile devices */ + padding: 1rem var(--size100); /* Adjusted padding for mobile devices */ +} + +.backButton { + cursor: pointer; + margin-bottom: var(--size10); /* Adjusted margin for mobile devices */ +} + +/* For devices with screen width less than 600px */ +@media (max-width: 600px) { + .officerView { + padding: 1rem; + display: flex; + flex-direction: column; + } + + .profilePicture { + width: 4rem; /* Set your desired width */ + height: 4rem; /* Set your desired height */ + margin-right: 0.5rem; + } + + .officerViewHeader { + flex-direction: column; + align-items: center; /* Center items for mobile devices */ + } + + .wrapper { + padding: 1rem var(--size20); /* Adjusted padding for mobile devices */ + } +} diff --git a/frontend/compositions/officer-view/officer-work-history/index.tsx b/frontend/compositions/officer-view/officer-work-history/index.tsx index 24957f28..bed6c7c3 100644 --- a/frontend/compositions/officer-view/officer-work-history/index.tsx +++ b/frontend/compositions/officer-view/officer-work-history/index.tsx @@ -4,7 +4,7 @@ import styles from "./officer-work-history.module.css" export default function OfficerWorkHistory(officer: OfficerRecordType) { const { workHistory } = officer - const { category, wrapper } = styles + const { wrapper, title } = styles const result = workHistory.map((item, index) => ( @@ -12,9 +12,7 @@ export default function OfficerWorkHistory(officer: OfficerRecordType) { return (
-
-

Work History Summary:

-
+

Work History:

{result}
) diff --git a/frontend/compositions/officer-view/officer-work-history/officer-work-history.module.css b/frontend/compositions/officer-view/officer-work-history/officer-work-history.module.css index da8c0467..4fc255c2 100644 --- a/frontend/compositions/officer-view/officer-work-history/officer-work-history.module.css +++ b/frontend/compositions/officer-view/officer-work-history/officer-work-history.module.css @@ -1,10 +1,12 @@ -.category { - display: block; - font-size: var(--size10); - color: var(--grey); - margin-left: 0; +.title { + font-size: var(--size14); + color: #666666; + margin: 0.5rem 0; + font-weight: 400; } .wrapper { display: flex; + flex-direction: column; + margin: 0.2rem 0; } diff --git a/frontend/compositions/officer-view/officer-work-history/work-history-instance/index.tsx b/frontend/compositions/officer-view/officer-work-history/work-history-instance/index.tsx index aef63730..367f0be0 100644 --- a/frontend/compositions/officer-view/officer-work-history/work-history-instance/index.tsx +++ b/frontend/compositions/officer-view/officer-work-history/work-history-instance/index.tsx @@ -5,24 +5,26 @@ import Image from "next/image" export default function WorkHistoryInstance(pastWorkplace: EmploymentType) { const { agency, currentlyEmployed, earliestEmployment, latestEmployment } = pastWorkplace const { agencyName, agencyImage, agencyHqAddress, websiteUrl } = agency - const startDateString = new Date(earliestEmployment).toLocaleDateString().split(",")[0] - const endDateString = new Date(latestEmployment).toLocaleDateString().split(",")[0] + const startDateString = earliestEmployment.toLocaleDateString().split("/")[2] + const endDateString = latestEmployment.toLocaleDateString().split("/")[2] - const { patch, wrapper, dates } = styles + const { patch, wrapper, titleAndDate, title, address, content, link } = styles return (
{agencyName.concat(" -
-

- {status} - +

+
+ Detective + {startDateString} - {endDateString} -

- {agencyName} +
+ + {agencyName} + {/*TODO: Get Phone number from officer data, mock data currently does not have*/} -

(123) 456-7890 * {agencyHqAddress}

+

(123) 456-7890 * {agencyHqAddress}

) diff --git a/frontend/compositions/officer-view/officer-work-history/work-history-instance/work-history-instance.module.css b/frontend/compositions/officer-view/officer-work-history/work-history-instance/work-history-instance.module.css index 955d4ab2..8ec2dd54 100644 --- a/frontend/compositions/officer-view/officer-work-history/work-history-instance/work-history-instance.module.css +++ b/frontend/compositions/officer-view/officer-work-history/work-history-instance/work-history-instance.module.css @@ -1,13 +1,35 @@ .patch { width: var(--size64); height: var(--size64); + margin-right: var(--size8); } .wrapper { display: flex; - margin-bottom: var(--size16); } -.dates { - float: "right"; +.titleAndDate { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.title { + font-size: var(--size16); + font-weight: bold; +} + +.address { + font-size: var(--size14); + color: #666666; + font-style: italic; +} + +.content { + display: flex; + flex-direction: column; +} + +.link { + /* margin: var(--size4) 0; */ } diff --git a/frontend/compositions/officer-view/optional-officer-info/index.tsx b/frontend/compositions/officer-view/optional-officer-info/index.tsx index 26fa1e8c..a588d755 100644 --- a/frontend/compositions/officer-view/optional-officer-info/index.tsx +++ b/frontend/compositions/officer-view/optional-officer-info/index.tsx @@ -2,12 +2,26 @@ import { OfficerRecordType } from "../../../models/officer" import styles from "./optional-officer-info.module.css" export default function OptionalOfficerInfo(officer: OfficerRecordType) { - const { dateOfBirth, gender, race } = officer + const { dateOfBirth, gender, race, ethnicity, badgeNo, status, department } = officer const { category, data, viewWrapper } = styles const dateString: string = new Date(dateOfBirth).toLocaleDateString().split(",")[0] return ( -
+ <> +
+
+

Badge Number

+

{badgeNo}

+
+
+

Officer Status

+

{status}

+
+
+

Department

+

{department}

+
+

Date of Birth

@@ -22,6 +36,14 @@ export default function OptionalOfficerInfo(officer: OfficerRecordType) {

{race}

-
+
+
+
+

Ethnicity

+

{ethnicity}

+
+
+
+ ) } diff --git a/frontend/compositions/officer-view/optional-officer-info/optional-officer-info.module.css b/frontend/compositions/officer-view/optional-officer-info/optional-officer-info.module.css index 46a69572..e388bd19 100644 --- a/frontend/compositions/officer-view/optional-officer-info/optional-officer-info.module.css +++ b/frontend/compositions/officer-view/optional-officer-info/optional-officer-info.module.css @@ -1,13 +1,33 @@ .category { - font-size: var(--size10); - color: var(--grey); + font-size: var(--size14); + color: #666666; margin-left: 0; + font-weight: 400; } .data { - width: 20%; + margin-right: 1rem; + margin: 0.4rem 0; + width: 100%; } .viewWrapper { display: flex; + width: 40%; +} + +/* For devices with screen width less than 600px */ +@media (max-width: 600px) { + .data { + margin-right: 0.5rem; + margin: 0.2rem 0; /* Adjusted margin for better spacing on mobile */ + } + + .viewWrapper { + flex-direction: column; + } + + .category { + margin-bottom: 0.2rem; /* Added margin-bottom for better spacing on mobile */ + } } diff --git a/frontend/helpers/mock-to-officer-type.ts b/frontend/helpers/mock-to-officer-type.ts index a10e885c..d16dce0c 100644 --- a/frontend/helpers/mock-to-officer-type.ts +++ b/frontend/helpers/mock-to-officer-type.ts @@ -2,6 +2,7 @@ import { OfficerRecordType, EmploymentType } from "../models/officer" import { PerpetratorRecordType } from "../models/perpetrator" import { Incident, Officer, UseOfForce } from "../helpers/api" import officers from "../models/mock-data/officer.json" +import { IncidentRecordType } from "../models/incident" export function getOfficerFromMockData(officerId: number) { if (officerId >= 0 && officerId < 100) { @@ -40,7 +41,13 @@ export function mockToOfficerType(officer: typeof officers[0]): OfficerRecordTyp dateOfBirth: new Date(officer.birthDate), gender: officer.gender, race: officer.race, - workHistory: mockToWorkHistoryType(officer.workHistory) + badgeNo: officer.badgeNo, + status: officer.status, + department: officer.department, + ethnicity: officer.ethnicity, + affiliations: officer.affiliations, + workHistory: mockToWorkHistoryType(officer.workHistory), + incidents: mockToIncidentType(officer.incidents) } } @@ -53,3 +60,7 @@ export function mockToPerpetratorType(officer: typeof officers[0]): PerpetratorR race: officer.race } } + +export function mockToIncidentType(data: IncidentRecordType[]): IncidentRecordType[] { + return data +} diff --git a/frontend/models/app-routes.tsx b/frontend/models/app-routes.tsx index c5adcca4..322cb527 100644 --- a/frontend/models/app-routes.tsx +++ b/frontend/models/app-routes.tsx @@ -6,5 +6,6 @@ export enum AppRoutes { LOGIN = "/login", PASSPORT = "/passport", PROFILE = "/profile", - REGISTER = "/register" + REGISTER = "/register", + OFFICER = "/officer" } diff --git a/frontend/models/incident.tsx b/frontend/models/incident.tsx new file mode 100644 index 00000000..3d5c1d47 --- /dev/null +++ b/frontend/models/incident.tsx @@ -0,0 +1,39 @@ +import { Column } from "react-table" +import { GreaterThanButton } from "../shared-components/icon-buttons/icon-buttons" + +export interface IncidentRecordType { + officers: string[] + incidentType: string + useOfForce: string[] + source?: string +} + +export const incidentResultsColumns: Column[] = [ + { + Header: "Officers involved", + accessor: (row: any) => (Array.isArray(row["officers"]) ? row["officers"].join(", ") : ""), + id: "officersInvolved" + }, + { + Header: "Incident Type", + accessor: (row: any) => row["incidentType"], + id: "incidentType" + }, + { + Header: "Use of Force", + accessor: (row: any) => (Array.isArray(row["useOfForce"]) ? row["useOfForce"].join(", ") : ""), + id: "useOfForce" + }, + { + Header: "Source", + accessor: "source", + id: "source" + }, + { + Header: "View Record", + Cell: () => { + return console.log("clicked")} /> + }, + id: "record" + } +] diff --git a/frontend/models/mock-data/officer.json b/frontend/models/mock-data/officer.json index 68d67000..a6d0f665 100644 --- a/frontend/models/mock-data/officer.json +++ b/frontend/models/mock-data/officer.json @@ -1,4 +1,48 @@ [ + { + "id": 0, + "firstName": "Timothy", + "lastName": "Young", + "badgeNo": "30420452", + "status": "Chief", + "department": "Houston Police Department", + "birthDate": "12/15/1976", + "gender": "Nonbinary", + "race": "Black or African American", + "ethnicity": "Hispanic or Latino", + "incomeBracket": "Over $523,600", + "workHistory": [ + { + "deptName": "San Jose Police Department", + "status": "Captain", + "dates": "8/7/2014 - 10/15/2001", + "deptImage": "./frontend/models/mock-data/dept-images/Boston_Police_Patch.jpg", + "deptAddress": "1191 Elm Ave, Houston, KY 93452" + }, + { + "deptName": "Pheonix Police Department", + "status": "Sergeant", + "dates": "3/7/1999 - 4/10/2007", + "deptImage": "./frontend/models/mock-data/dept-images/San_Jose_Police_Patch.png", + "deptAddress": "10435 Lemon Ln, San Diego, MN 58574" + } + ], + "affiliations": [ + "Fraternal Order of Police", + "International Union of Police Associations", + "National Association of Police Organizations" + ], + "incidents": [ + { + "id": 54, + "occurred": 1152962172000, + "officers": ["F.Keaves"], + "incidentType": "Robbery", + "useOfForce": ["Armed presence"], + "source": "Police report" + } + ] + }, { "id": 1, "firstName": "Timothy", diff --git a/frontend/models/officer.tsx b/frontend/models/officer.tsx index 53242686..122762a0 100644 --- a/frontend/models/officer.tsx +++ b/frontend/models/officer.tsx @@ -1,8 +1,11 @@ import { Column } from "react-table" -import { Incident, Perpetrator } from "../helpers/api" -import { CirclePlusButton } from "../shared-components/icon-buttons/icon-buttons" +import { Perpetrator } from "../helpers/api" +import { CirclePlusButton, GreaterThanButton } from "../shared-components/icon-buttons/icon-buttons" import { InfoTooltip } from "../shared-components" import { TooltipIcons, TooltipTypes } from "./info-tooltip" +import Link from "next/link" +import { AppRoutes } from "./app-routes" +import { IncidentRecordType } from "./incident" export interface AgencyType { agencyName: string @@ -46,16 +49,24 @@ export interface OfficerRecordType { gender?: string race?: string ethnicity?: string + badgeNo?: string + status?: string + department?: string knownEmployers?: AgencyType[] workHistory?: EmploymentType[] accusations?: Perpetrator[] - affiliations?: OfficerRecordType[] + affiliations?: string[] + incidents?: IncidentRecordType[] } export const officerResultsColumns: Column[] = [ { Header: "Name", - accessor: (row: any) => `${row["first_name"]}, ${row["last_name"].charAt(0)}`, + accessor: (row: any) => ( + +
{`${row["firstName"]}, ${row["lastName"]}`}
+ + ), id: "name" }, { @@ -85,7 +96,7 @@ export const officerResultsColumns: Column[] = [ ), - accessor: "rank", + accessor: "status", id: "rank" }, { diff --git a/frontend/pages/officer/[id].tsx b/frontend/pages/officer/[id].tsx index cb3af851..4f3b6553 100644 --- a/frontend/pages/officer/[id].tsx +++ b/frontend/pages/officer/[id].tsx @@ -7,5 +7,5 @@ import { useRouter } from "next/router" export default requireAuth(function OfficerPage() { const router = useRouter() const id = parseInt(router.query.id as string) - return isNaN(id) ?

Loading

: + return isNaN(id) ? <> : }) diff --git a/frontend/pages/search/index.tsx b/frontend/pages/search/index.tsx index bf40cbe7..a746b2ee 100644 --- a/frontend/pages/search/index.tsx +++ b/frontend/pages/search/index.tsx @@ -16,6 +16,8 @@ import ErrorAlertDialog from "../../shared-components/error-alert-dialog/error-a import { useState } from "react" import { officerResultsColumns } from "../../models/officer" import { SearchResultsTypes, ToggleOptions } from "../../models" +import { mockToOfficerType } from "../../helpers/mock-to-officer-type" +import officer from "../../models/mock-data/officer.json" export default requireAuth(function Dashboard() { const { searchPageContainer } = styles @@ -27,6 +29,10 @@ export default requireAuth(function Dashboard() { const isIncidentView = toggleOptions[0].value const isOfficerView = toggleOptions[1].value + const officerSearchResult = Array.from({ length: 100 }, (_, index) => + mockToOfficerType(officer[index]) + ) + return (
@@ -49,94 +55,3 @@ export default requireAuth(function Dashboard() { ) }) - -const officerSearchResult: Officer[] = [ - { - id: 1, - first_name: "John", - last_name: "Doe", - race: "White", - ethnicity: "Non-Hispanic", - gender: "Male", - rank: Rank.CAPTAIN, - star: "123456", - date_of_birth: new Date("01/01/1980") - }, - { - id: 1, - first_name: "John", - last_name: "Doe", - race: "White", - ethnicity: "Non-Hispanic", - gender: "Male", - rank: Rank.CAPTAIN, - star: "123456", - date_of_birth: new Date("01/01/1980") - }, - { - id: 1, - first_name: "John", - last_name: "Doe", - race: "White", - ethnicity: "Non-Hispanic", - gender: "Male", - rank: Rank.CAPTAIN, - star: "123456", - date_of_birth: new Date("01/01/1980") - }, - { - id: 1, - first_name: "John", - last_name: "Doe", - race: "White", - ethnicity: "Non-Hispanic", - gender: "Male", - rank: Rank.CAPTAIN, - star: "123456", - date_of_birth: new Date("01/01/1980") - }, - { - id: 1, - first_name: "John", - last_name: "Doe", - race: "White", - ethnicity: "Non-Hispanic", - gender: "Male", - rank: Rank.CAPTAIN, - star: "123456", - date_of_birth: new Date("01/01/1980") - }, - { - id: 1, - first_name: "John", - last_name: "Doe", - race: "White", - ethnicity: "Non-Hispanic", - gender: "Male", - rank: Rank.CAPTAIN, - star: "123456", - date_of_birth: new Date("01/01/1980") - }, - { - id: 1, - first_name: "John", - last_name: "Doe", - race: "White", - ethnicity: "Non-Hispanic", - gender: "Male", - rank: Rank.CAPTAIN, - star: "123456", - date_of_birth: new Date("01/01/1980") - }, - { - id: 1, - first_name: "John", - last_name: "Doe", - race: "White", - ethnicity: "Non-Hispanic", - gender: "Male", - rank: Rank.CAPTAIN, - star: "123456", - date_of_birth: new Date("01/01/1980") - } -] diff --git a/frontend/shared-components/officer-incident-data-table/officer-incident-data-table-subcomps.tsx b/frontend/shared-components/officer-incident-data-table/officer-incident-data-table-subcomps.tsx new file mode 100644 index 00000000..03029030 --- /dev/null +++ b/frontend/shared-components/officer-incident-data-table/officer-incident-data-table-subcomps.tsx @@ -0,0 +1,66 @@ +import { faAngleLeft, faAngleRight, IconDefinition } from "@fortawesome/free-solid-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import styles from "./officer-incident-data-table.module.css" + +type PageButtonProps = { + icon: IconDefinition + onclick: () => void + disabled?: boolean +} + +const PageButton = (props: PageButtonProps) => { + const { pageBtn } = styles + const { icon, onclick, disabled } = props + + return ( + + + + ) +} +type PageNavigatorProps = { + data: any[] + gotoPage(n: number): void + previousPage(): void + nextPage(): void + pageIndex: number + pageOptions: number[] + pageCount: number + canPreviousPage: boolean + canNextPage: boolean + pageSize: number + setPageSize(n: number): void +} + +const PageNavigator = (props: PageNavigatorProps) => { + const { dataFooter, dataRowPage, recordCount, pageCnt, goto } = styles + const { + data, + gotoPage, + previousPage, + nextPage, + pageIndex, + pageOptions, + pageCount, + canPreviousPage, + canNextPage, + pageSize, + setPageSize + } = props + + return ( +
+ Number of results: {data.length} + + previousPage()} disabled={!canPreviousPage} /> + + {pageIndex + 1} of {pageOptions.length} + + nextPage()} disabled={!canNextPage} /> + +
+ ) +} + +export type { PageNavigatorProps } +export { PageNavigator } diff --git a/frontend/shared-components/officer-incident-data-table/officer-incident-data-table.module.css b/frontend/shared-components/officer-incident-data-table/officer-incident-data-table.module.css new file mode 100644 index 00000000..2db304e5 --- /dev/null +++ b/frontend/shared-components/officer-incident-data-table/officer-incident-data-table.module.css @@ -0,0 +1,144 @@ +/* General styles */ +.dataTable { + border-collapse: collapse; + margin-top: var(--size36); + width: 100%; +} + +/* Table header, footer, and rows */ +.dataFooter, +.dataHeader, +.dataRows { + height: var(--size36); + text-align: center; +} + +.dataFooter, +.dataHeader { + background-color: white; + font-weight: 700; + color: #716c6c; +} + +.dataFooter { + display: flex; + flex-wrap: wrap; /* Allow items to wrap to the next line on small screens */ + justify-content: space-between; + padding: var(--size4) var(--size20); /* Adjust padding for smaller screens */ + font-size: small; + font-weight: 400; +} + +.dataRows:nth-child(odd) { + background-color: #e7e7e7; +} + +/* Remove number input arrows */ +.dataRowPage { + -moz-appearance: textfield; + padding: var(--size4); + border: none; + width: 100%; /* Make the input take the full width on small screens */ +} + +.dataRowPage::-webkit-outer-spin-button, +.dataRowPage::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Action buttons and sorting */ +.actionBtn, +.sortArrow, +.colFliter, +.pageBtn, +.expandRecordButton { + margin: 0.1rem; +} + +.actionBtn, +.sortArrow, +.pageBtn, +.expandRecordButton { + font-size: var(--size12); /* Adjust font size for small screens */ +} + +/* Table wrapper and header */ +.tableWrapper { + background: #ffffff; + border: 1px solid #000000; + box-sizing: border-box; + box-shadow: -2px 2px 4px 2px rgba(0, 0, 0, 0.25); + border-radius: 10px; + padding: var(--size10); +} + +.tableHeader { + display: flex; + flex-wrap: wrap; /* Allow items to wrap to the next line on small screens */ + justify-content: space-between; +} + +/* Edit buttons and record count */ +.editButton, +.editButtonOn { + box-sizing: border-box; + border-radius: 4px; + border: 2px solid #303463; + font-size: var(--size12); + font-weight: bold; + cursor: pointer; + width: 48%; /* Two buttons side by side on small screens */ + margin-bottom: var(--size4); /* Add some spacing between buttons */ +} + +.editButton, +.editButtonOn { + margin: 0.1rem; +} + +.editButton, +.editButtonOn, +.pageBtn, +.expandRecordButton { + width: 100%; /* Make buttons take the full width on small screens */ +} + +/* Record count */ +.recordCount { + font-weight: bold; + font-size: var(--size18); + margin-top: var(--size10); /* Add some spacing at the top on small screens */ +} + +/* Media Queries for Mobile Responsiveness */ +@media only screen and (max-width: 600px) { + .dataFooter, + .dataHeader, + .dataRows, + .dataRowPage, + .actionBtn, + .sortArrow, + .colFliter, + .pageBtn, + .expandRecordButton, + .editButton, + .editButtonOn, + .recordCount { + font-size: small; + } + + .dataRows { + height: var(--size48); + } + + .editButton, + .editButtonOn { + width: 100%; /* Make buttons take the full width on small screens */ + } + + .tableWrapper, + .tableHeader { + padding: var(--size6); /* Adjust padding for smaller screens */ + } +} diff --git a/frontend/shared-components/officer-incident-data-table/officer-incident-data-table.stories.tsx b/frontend/shared-components/officer-incident-data-table/officer-incident-data-table.stories.tsx new file mode 100644 index 00000000..753df3e3 --- /dev/null +++ b/frontend/shared-components/officer-incident-data-table/officer-incident-data-table.stories.tsx @@ -0,0 +1,21 @@ +import { ComponentMeta, ComponentStory } from "@storybook/react" +import React from "react" +import { OfficerIncidentDataTable } from "./officer-incident-data-table" +import { getOfficerFromMockData } from "../../helpers/mock-to-officer-type" +import { incidentResultsColumns } from "../../models/incident" + +export default { + title: "Shared Components/Officer Incident Data Table", + component: OfficerIncidentDataTable +} as ComponentMeta + +const Template: ComponentStory = (args) => ( + +) + +export const OfficerIncidentResults = Template.bind({}) + +OfficerIncidentResults.args = { + columns: incidentResultsColumns, + data: getOfficerFromMockData(0).incidents +} diff --git a/frontend/shared-components/officer-incident-data-table/officer-incident-data-table.tsx b/frontend/shared-components/officer-incident-data-table/officer-incident-data-table.tsx new file mode 100644 index 00000000..376cc826 --- /dev/null +++ b/frontend/shared-components/officer-incident-data-table/officer-incident-data-table.tsx @@ -0,0 +1,113 @@ +import { faArrowDown, faArrowUp } from "@fortawesome/free-solid-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import React, { useState } from "react" +import { Column, useFilters, usePagination, useSortBy, useTable } from "react-table" +import { PageNavigator } from "./officer-incident-data-table-subcomps" +import styles from "./officer-incident-data-table.module.css" +import { IncidentRecordType } from "../../models/incident" + +interface OfficerIncidentDataTableProps { + tableName: string + columns: Column[] + data: IncidentRecordType[] | undefined +} + +export function OfficerIncidentDataTable(props: OfficerIncidentDataTableProps) { + const { data, columns } = props + const { dataTable, dataHeader, dataRows } = styles + const [showFilters, setShowFilters] = useState(false) + + const { sortArrow, colFilter } = styles + + const { + getTableProps, + getTableBodyProps, + headerGroups, + prepareRow, + page, + canPreviousPage, + canNextPage, + pageOptions, + pageCount, + gotoPage, + nextPage, + previousPage, + setPageSize, + state: { pageIndex, pageSize } + } = useTable( + { + columns, + data, + initialState: { pageIndex: 0 } + }, + useFilters, + useSortBy, + usePagination + ) + + return ( +
+ {/*
*/} + + + {headerGroups.map((headerGroup) => ( + // react-table prop types include keys, but eslint can't tell that + // eslint-disable-next-line react/jsx-key + + {headerGroup.headers.map((column) => ( + // eslint-disable-next-line react/jsx-key + + ))} + + ))} + + + {page.map((row, i) => { + prepareRow(row) + return ( + // eslint-disable-next-line react/jsx-key + + {row.cells.map((cell) => { + // eslint-disable-next-line react/jsx-key + return + })} + + ) + })} + +
+ {column.render("Header")} + + {column.isSorted ? ( + column.isSortedDesc ? ( + + ) : ( + + ) + ) : ( + " " + )} + + {showFilters && ( +
+ {column.canFilter ? column.render("Filter") : null} +
+ )} +
{cell.render("Cell")}
+ + +
+ ) +} diff --git a/frontend/tests/snapshots/__snapshots__/officer.test.tsx.snap b/frontend/tests/snapshots/__snapshots__/officer.test.tsx.snap index 3d07e246..1d7e31bd 100644 --- a/frontend/tests/snapshots/__snapshots__/officer.test.tsx.snap +++ b/frontend/tests/snapshots/__snapshots__/officer.test.tsx.snap @@ -5,315 +5,855 @@ Object { "asFragment": [Function], "baseElement":
-
-

- Officer Record -

-
+ -
-

+ + +

+ Profile Picture +
+
+

+ Officer Record +

+

+ Timothy + + Young +

+
+
- Timothy - - Young -

-
-
-

+

+ Badge Number +

+

+ 30420452 +

+
+
+

+ Officer Status +

+

+ Chief +

+
+
+

+ Department +

+

+ Houston Police Department +

+
+
+
- Known Employers -

+
+

+ Date of Birth +

+

+ 12/15/1976 +

+
+
+

+ Gender +

+

+ Nonbinary +

+
+
+

+ Race +

+

+ Black or African American +

+
+
+
+
+
+

+ Ethnicity +

+

+ Hispanic or Latino +

+
+
+
+
+

+ Work History: +

+
+
+ San Jose Police Department Patch +
+
+ + Detective + + + 2014 + - + 2001 + +
+ + San Jose Police Department + +

+ (123) 456-7890 * + 1191 Elm Ave, Houston, KY 93452 +

+
+
+
+ Pheonix Police Department Patch +
+
+ + Detective + + + 1999 + - + 2007 + +
+ + Pheonix Police Department + +

+ (123) 456-7890 * + 10435 Lemon Ln, San Diego, MN 58574 +

+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + +
+ Officers involved + + + + + Incident Type + + + + + Use of Force + + + + + Source + + + + + View Record + + + +
+ F.Keaves + + Robbery + + Armed presence + + Police report + + + + View Record + + + +
+
+ + Number of results: + 1 + + + + + + + 1 + of + 1 + + + + + +
+
-
-
-
+ , + "container":
+
+ + -
+ Back + + + +
+ Profile Picture +
+

- Date of Birth + Officer Record

-

- 12/15/1976 -

-
-

- Gender -

-

- Nonbinary + Timothy + + Young

-

- Race -

-

- Black or African American -

-
-
-
-
-
-
-

- Work History Summary: -

-
-
-
- San Jose Police Department Patch -
+

+ Badge Number +

- - - 8/7/2014 - - - 10/15/2001 - + 30420452

- +
+

+ Officer Status +

+

+ Chief +

+
+
+

- San Jose Police Department - + Department +

- (123) 456-7890 * - 1191 Elm Ave, Houston, KY 93452 + Houston Police Department

- Pheonix Police Department Patch -
+
+

+ Date of Birth +

- - - 3/7/1999 - - - 4/10/2007 - + 12/15/1976

- +
+

- Pheonix Police Department - + Gender +

- (123) 456-7890 * - 10435 Lemon Ln, San Diego, MN 58574 + Nonbinary +

+
+
+

+ Race +

+

+ Black or African American

-
-
-
-

- Professional Affiliations: -

-
-
- , - "container":
-
-

- Officer Record -

-
-
-

- Timothy - - Young -

-
-
-

- Known Employers -

-
-
-
-
-
-
-
-

- Date of Birth -

-

- 12/15/1976 -

-
-
-

- Gender -

-

- Nonbinary -

-
-
-

- Race -

-

- Black or African American -

-
-
-
-
-
-
-

- Work History Summary: -

-
-
-
- San Jose Police Department Patch
-

- - +

- 8/7/2014 - - - 10/15/2001 - -

- + Ethnicity +

+

+ Hispanic or Latino +

+
+
+
+
+

- San Jose Police Department - -

- (123) 456-7890 * - 1191 Elm Ave, Houston, KY 93452 + Work History:

+
+
+ San Jose Police Department Patch +
+
+ + Detective + + + 2014 + - + 2001 + +
+ + San Jose Police Department + +

+ (123) 456-7890 * + 1191 Elm Ave, Houston, KY 93452 +

+
+
+
+ Pheonix Police Department Patch +
+
+ + Detective + + + 1999 + - + 2007 + +
+ + Pheonix Police Department + +

+ (123) 456-7890 * + 10435 Lemon Ln, San Diego, MN 58574 +

+
+
+
-
-
- Pheonix Police Department Patch
-

- + + + + + + + + + + + + + + + + + + + +
+ Officers involved + + + + + Incident Type + + + + + Use of Force + + + + + Source + + + + + View Record + + + +
+ F.Keaves + + Robbery + + Armed presence + + Police report + + + + View Record + + + +
+

- 3/7/1999 - - - 4/10/2007 + Number of results: + 1 -

- - Pheonix Police Department - -

- (123) 456-7890 * - 10435 Lemon Ln, San Diego, MN 58574 -

+ + + + + + 1 + of + 1 + + + + + +
-
-

- Professional Affiliations: -

-
, "debug": [Function], "findAllByAltText": [Function], diff --git a/frontend/tests/snapshots/officer.test.tsx b/frontend/tests/snapshots/officer.test.tsx index 89e0612e..6516472f 100644 --- a/frontend/tests/snapshots/officer.test.tsx +++ b/frontend/tests/snapshots/officer.test.tsx @@ -1,7 +1,7 @@ import { render } from "../test-utils" import OfficerView from "../../compositions/officer-view" import { getOfficerFromMockData } from "../../helpers/mock-to-officer-type" - +jest.spyOn(global.Math, "random").mockImplementation(() => 0) describe("Officer Page", () => { it("Renders the officer page correctly", () => { const tree = render()