1
0
Fork 0

Merge branch 'develop' of https://git.disroot.org/minicx/hackaton_2023 into develop

This commit is contained in:
minicx 2023-12-23 14:41:24 +03:00
commit 6ce72e23c0
17 changed files with 254 additions and 94 deletions

View File

@ -89,7 +89,7 @@
},
{
"id": 3,
"inventoryNumber": "4523122",
"inventoryNumber": "4523123",
"position": 3,
"type": "HalfCarriage",
"isSick": false,
@ -283,7 +283,7 @@
},
{
"station": {
"id": 2,
"id": 3,
"name": "Томусинская"
},
"park": {

View File

@ -25,6 +25,7 @@
"@remix-run/dev": "^2.4.1",
"@types/bcrypt": "^5.0.2",
"@types/eslint": "^8.56.0",
"@types/lodash": "^4.14.202",
"@types/node": "^18.19.3",
"@types/react": "^18.2.45",
"@typescript-eslint/eslint-plugin": "^6.15.0",
@ -45,6 +46,7 @@
},
"dependencies": {
"@nextui-org/react": "^2.2.9",
"@reduxjs/toolkit": "^2.0.1",
"@remix-run/node": "^2.4.1",
"@remix-run/react": "^2.4.1",
"@remix-run/serve": "^2.4.1",
@ -55,6 +57,8 @@
"node-fetch": "^3.3.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^9.0.4",
"redux": "^5.0.1",
"remix-auth": "^3.6.0",
"remix-auth-form": "^1.4.0"
}

View File

@ -2,21 +2,17 @@ import type { IStation } from 'app/services/api/interfaces.server';
import React from 'react';
import { Select, SelectSection, SelectItem } from '@nextui-org/react';
export default function ControlFormHeader({
Stations,
}: {
Stations: IStation[];
}) {
return (
export default function ControlFormHeader({stations}: {stations: IStation[]}) {
return(
<div id="control-form-header" className="flex gap-4">
<Select
label="Station filter"
placeholder="Select a station"
selectionMode="multiple"
className="w-[250px]"
size="sm"
>
{Stations.map((station) => (
size='sm'
>
{stations.map((station) => (
<SelectItem key={station.title} value={station.title}>
{station.title}
</SelectItem>

View File

@ -1,5 +0,0 @@
import React from 'react';
export default function controlTable() {
return <div id="control-table"></div>;
}

View File

@ -214,7 +214,7 @@ export default function ModalFilter({ db }: { db: Tdb }) {
}
return (
<>
<div className="mb-4">
<Button onPress={onOpen}>Фильтр</Button>
<Modal
isOpen={isOpen}
@ -302,6 +302,6 @@ export default function ModalFilter({ db }: { db: Tdb }) {
)}
</ModalContent>
</Modal>
</>
</div>
);
}

View File

@ -0,0 +1,61 @@
import React from 'react';
import {
Modal,
ModalContent,
ModalHeader,
ModalBody,
Button,
useDisclosure,
Listbox,
ListboxItem,
Select,
SelectItem,
ModalFooter,
} from '@nextui-org/react';
import { IStationData } from 'app/services/api/interfaces.server';
import { useSearchParams, useNavigate } from '@remix-run/react';
export default function ModalOperations () {
const {isOpen, onOpen, onOpenChange} = useDisclosure();
return (
<>
<Button onPress={onOpen}>Операции</Button>
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent>
{(onClose) => (
<>
<ModalHeader className="flex flex-col gap-1">Операции</ModalHeader>
<ModalBody className="w-[80%]">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nullam pulvinar risus non risus hendrerit venenatis.
Pellentesque sit amet hendrerit risus, sed porttitor quam.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nullam pulvinar risus non risus hendrerit venenatis.
Pellentesque sit amet hendrerit risus, sed porttitor quam.
</p>
<p>
Magna exercitation reprehenderit magna aute tempor cupidatat consequat elit
dolor adipisicing. Mollit dolor eiusmod sunt ex incididunt cillum quis.
Velit duis sit officia eiusmod Lorem aliqua enim laboris do dolor eiusmod.
Et mollit incididunt nisi consectetur esse laborum eiusmod pariatur
proident Lorem eiusmod et. Culpa deserunt nostrud ad veniam.
</p>
</ModalBody>
<ModalFooter>
<Button color="danger" variant="light" onPress={onClose}>
Close
</Button>
<Button color="primary" onPress={onClose}>
Action
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
</>
);
}

View File

@ -56,32 +56,10 @@ export default function NavbarCom() {
Powered by EVRAZ
</p>
</NavbarBrand>
<NavbarItem className="mx-auto">
<Link color="foreground" to="#">
Features
</Link>
</NavbarItem>
<NavbarItem isActive>
<Link to="#" aria-current="page">
Customers
</Link>
</NavbarItem>
<NavbarItem>
<Link color="foreground" to="#">
Integrations
</Link>
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
<NavbarItem className="hidden lg:flex">
<Link to="#">Login</Link>
</NavbarItem>
<NavbarItem>
<Button as={Link} color="primary" to="#" variant="flat">
Sign Up
</Button>
</NavbarItem>
</NavbarContent>
<NavbarMenu>

36
src/app/coms/ui/Park.tsx Normal file
View File

@ -0,0 +1,36 @@
import { IPark, IStationData } from 'app/services/api/interfaces.server';
import React from 'react';
import Wagon from './wagon';
import Locomotive from './locomotive';
export default function Park({park, railRoads}: {park: IPark, railRoads: IStationData[]}) {
return(
<div id="control-park" className='relative mb-12 py-1 pl-8 pr-2 border-secondary-200 border-[1px]'>
<div className="absolute left-0 top-0 w-6 h-full bg-secondary-200 flex justify-center items-center" style={{writingMode: "vertical-lr"}}>{park.name}</div>
<div className='rounded-none'>
{railRoads.map(railroad => (
<div className='min-h-[62px] my-[1px] p-2 flex gap-[2px] border-b-[2px] border-secondary-200 text-center'>
<div className="w-12 flex justify-center items-center ">
{railroad.way.id}
</div>
<div className="w-[76px] flex justify-center items-center ">
{railroad.wagons.length}
</div>
<div className="w-12 flex justify-center items-center border-[1px] border-secondary-150">
{railroad.locomotives.filter(locomotive => locomotive.direction === "LEFT").length ?
<Locomotive locomotive={railroad.locomotives.filter(locomotive => locomotive.direction === "LEFT")[0]}/>:""}
</div>
<div className="flex grow px-4 py-1 gap-3 border-[1px] border-secondary-150" >
{railroad.wagons.sort().map(wagon => <Wagon wagon={wagon} key={wagon.id}/>)}
</div>
<div className="w-12 flex justify-center items-center border-[1px] border-secondary-150">
{railroad.locomotives.filter(locomotive => locomotive.direction === "RIGHT").length ?
<Locomotive locomotive={railroad.locomotives.filter(locomotive => locomotive.direction === "RIGHT")[0]}/>:""}
</div>
</div>
))}
</div>
</div>
)
}

View File

@ -0,0 +1,19 @@
import type { IStation, IStationData } from 'app/services/api/interfaces.server';
import React from 'react';
import lodash from 'lodash';
import Park from './Park';
const { uniqBy } = lodash;
export default function Station({station, stationData}: {station: IStation, stationData: IStationData[]}) {
const parks = uniqBy(stationData.map((way)=>way.park),function (park) {return park.id});
return(
<div id="control-station" className="flex flex-col">
<h2 className='ml-4 my-2'>{station.title}</h2>
{parks.map(park => {
const railRoads = stationData.filter(railroad => railroad.park.id == park.id);
return(<Park park={park} railRoads={railRoads} key={park.id}/>)
})}
</div>
)
}

View File

@ -0,0 +1,22 @@
import { ILocomotive } from 'app/services/api/interfaces.server';
import React from 'react';
export default function Locomotive({locomotive}: {locomotive: ILocomotive}) {
return(
<div id="locomotive" className='relative'>
<div className="absolute left-[-5px] -top-2 text-[8px]">{locomotive.inventoryNumber}</div>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_212_29288)">
<path d="M15.4281 5.61828C15.3198 5.59023 15.1055 5.53674 15.1055 5.53674L15.1049 3.15164C15.1049 2.64399 14.6879 2.17764 13.9496 1.9886C13.2403 1.80702 12.6737 1.65059 11.6001 1.57974C11.7804 1.42308 11.895 1.19262 11.895 0.934911C11.895 0.463055 11.5124 0.0804443 11.0404 0.0804443C10.5686 0.0804443 10.186 0.463007 10.186 0.934911C10.186 1.17036 10.2812 1.38342 10.4352 1.538H9.56442C9.71838 1.38342 9.81366 1.17036 9.81366 0.934911C9.81366 0.463055 9.4312 0.0804443 8.9592 0.0804443C8.48725 0.0804443 8.10473 0.463007 8.10473 0.934911C8.10473 1.19257 8.21923 1.42308 8.39967 1.57974C7.32596 1.65059 6.75949 1.80702 6.05018 1.9886C5.31192 2.17764 4.89484 2.64399 4.89484 3.15164L4.89417 5.53674C4.89417 5.53674 4.67983 5.59023 4.57166 5.61828C4.31405 5.68509 4.13428 5.9175 4.13428 6.18357V13.4062C4.13428 13.7287 4.39569 13.9902 4.7182 13.9902H15.2813C15.6038 13.9902 15.8652 13.7287 15.8652 13.4062V6.18352C15.8654 5.9175 15.6856 5.68504 15.4281 5.61828ZM6.23856 3.33033C7.21774 3.08784 8.21271 2.93973 9.20216 2.88899V4.75265C8.21304 4.8019 7.21846 4.94573 6.23856 5.18114V3.33033ZM10.0194 8.54176H9.98046C9.29103 8.54176 8.73012 7.98084 8.73012 7.29127C8.73012 6.60179 9.29103 6.04083 9.98046 6.04083H10.0194C10.7089 6.04083 11.2698 6.60179 11.2698 7.29127C11.2698 7.98084 10.7089 8.54176 10.0194 8.54176ZM13.7612 5.18114C12.7814 4.94569 11.7868 4.8019 10.7976 4.75265V2.88899C11.7871 2.93973 12.782 3.08784 13.7612 3.33033V5.18114Z" fill="#B1ADC2"/>
<path d="M11.2917 16.7526H10.0001H8.70846L8.36987 17.7045C8.9073 17.7502 9.44467 17.7737 9.98062 17.7737H10.0195C10.5554 17.7737 11.0929 17.7502 11.6303 17.7045L11.2917 16.7526Z" fill="#B1ADC2"/>
<path d="M15.2873 16.6358V15.1617C15.2873 14.871 15.0517 14.6355 14.7611 14.6355H5.23856C4.948 14.6355 4.7124 14.8711 4.7124 15.1617V16.6358C4.7124 16.8755 4.87454 17.085 5.10652 17.1451C5.25453 17.1834 5.40302 17.2196 5.55151 17.2545L4.91257 18.7028C4.77317 19.0187 4.91619 19.3878 5.23214 19.5271C5.31425 19.5634 5.39993 19.5805 5.48428 19.5805C5.72444 19.5805 5.95343 19.4413 6.0566 19.2075L6.80675 17.5069C6.98719 17.5371 7.16782 17.5649 7.34855 17.59L7.88402 16.0848C7.95487 15.8854 8.14368 15.7522 8.35521 15.7522H11.6444C11.856 15.7522 12.0448 15.8854 12.1157 16.0848L12.6511 17.59C12.8318 17.5649 13.0125 17.5371 13.1929 17.5069L13.943 19.2075C14.0462 19.4413 14.2752 19.5805 14.5154 19.5805C14.5996 19.5805 14.6854 19.5634 14.7675 19.5271C15.0835 19.3877 15.2264 19.0187 15.0872 18.7028L14.4483 17.2544C14.5967 17.2196 14.7451 17.1834 14.8932 17.145C15.1251 17.085 15.2873 16.8755 15.2873 16.6358Z" fill="#B1ADC2"/>
</g>
<defs>
<clipPath id="clip0_212_29288">
<rect width="19.5" height="19.5" fill="white" transform="translate(0.25 0.0804443)"/>
</clipPath>
</defs>
</svg>
</div>
)
}

View File

@ -1,5 +0,0 @@
import React from 'react';
export default function Park() {
return <div id="control-park"></div>;
}

View File

@ -1,10 +0,0 @@
import type { IStationData } from 'app/services/api/interfaces.server';
import React from 'react';
export default function Station({
StationData,
}: {
StationData: IStationData;
}) {
return <div id="control-station"></div>;
}

View File

@ -1,6 +1,7 @@
import React from 'react';
import React, { useContext } from 'react';
import { IWagon } from 'app/services/api/interfaces.server';
import { useSelector, useDispatch } from 'react-redux'
import { selectWagon } from 'app/wagonsSlice';
export default function Wagon({ wagon }: { wagon: IWagon }) {
const colors: Record<string, string[]> = {
НТС: ['#BCF3FF', '#2988AE'],
@ -26,16 +27,25 @@ export default function Wagon({ wagon }: { wagon: IWagon }) {
bottomFillColor = '#EB5835';
bottomStrokeColor = '#E32112';
}
const wagons = useSelector(selectWagon);
const dispatch = useDispatch();
const selectWagonHandle = (e: any) => {
const id = e.currentTarget.getAttribute("wagon-id");
}
return (
<div className="wagon relative flex flex-col items-center">
<div id="wagon" wagon-id={wagon.id} onClick={selectWagonHandle} className=" w-fit relative flex flex-col items-center">
<div className="relative top-[2px]">
{WagonTop(wagon.type, topFillColor, topBorderColor)}
</div>
<div
id="wagon_number"
className={`absolute bottom-4 -left-2 w-4 h-4 bg-[${topFillColor}] border-[${topBorderColor}] border-[1px] text-[12px] text-center`}
style={{backgroundColor: topFillColor, borderColor: topBorderColor, borderWidth: "1px"}}
className="absolute flex justify-center items-center bottom-4 -left-2 w-4 h-4 text-[12px] text-center"
>
1
{wagon.position}
</div>
<svg
width="56"

View File

@ -32,7 +32,7 @@ export default function App() {
<body className="h-screen">
<NextUIProvider>
<NavbarCom />
<main className="h-content bg-background text-foreground flex justify-center items-center">
<main className="h-content bg-background text-foreground flex justify-center items-center p-4">
<Outlet />
</main>
<ScrollRestoration />

View File

@ -1,11 +1,15 @@
import React from 'react';
import React, { useState } from 'react';
import { createContext, useContext } from 'react';
import { json, type LoaderFunctionArgs } from '@remix-run/node';
import { authenticator } from '../services/auth.server';
import { IStation, IStationData } from 'app/services/api/interfaces.server';
import { api } from 'app/services/api/json.api.server';
import { useLoaderData } from '@remix-run/react';
import ControlFormHeader from 'app/coms/controlFormHeader';
import ModalFilter from 'app/coms/modalFilter';
import Station from 'app/coms/ui/Station';
import { Provider } from 'react-redux'
import { store } from 'app/wagonsSlice';
import ModalOperations from 'app/coms/modalOperations';
export async function loader({ request }: LoaderFunctionArgs) {
const authData = await authenticator.isAuthenticated(request, {
@ -28,15 +32,47 @@ export default function Index() {
const { authData, stations, owners, stationsData, wagonTypeList } =
useLoaderData<typeof loader>();
return (
<div id="control-form" className="w-full h-full p-2 flex flex-col">
<ControlFormHeader Stations={stations} />
<ModalFilter
db={{
stationsData: stationsData,
ownersList: owners,
wagonTypeList,
}}
></ModalFilter>
<div id="index" className='w-full h-full'>
<div className="flex justify-between">
<ModalFilter
db={{
stationsData: stationsData,
ownersList: owners,
wagonTypeList,
}}
/>
<ModalOperations />
</div>
<div id="control-form" className='w-full h-full flex flex-col rounded-sm border-secondary-200 border-[1px]'>
<div className="h-9 mb-4 pl-4 bg-secondary-100 text-y-center rounded-t-sm flex items-center">
АРМ дежурного станции
</div>
<div className='py-1 pl-10 pr-4 min-h-[62px] my-[1px] flex gap-[2px] text-center'>
<div className="w-12 flex justify-center items-center ">
Путь
</div>
<div className="w-[76px] flex justify-center items-center ">
Всего
</div>
<div className="w-12 flex justify-center items-center">
Л
</div>
<div className="flex grow justify-between items-center px-4 py-1 gap-3" >
<span>(Чётная)</span>
<span>Вагоны</span>
<span>(Нечётная)</span>
</div>
<div className="w-12 flex justify-center items-center ">
Л
</div>
</div>
<Provider store={store}>
{stations.map(station => {
const stationData = stationsData.filter(el => el.station.id == station.id);
return(<Station stationData={stationData} station={station} key={station.id}/>)
})}
</Provider>
</div>
</div>
);
}

View File

@ -15,13 +15,11 @@ const endpoints = {
getOwnersList: 'ownersList',
getNorms: 'operationsTypesNorms',
getOperationsTypes: 'operationsTypes',
getUsers: 'users',
getUsers:'users'
};
export const api = new (class ApiDB {
private readonly url = `http://localhost:${(
env.DB_PORT ?? 4000
).toString()}/`;
private readonly url = `http://localhost:${(env.DB_PORT ?? 4000).toString()}/`;
constructor() {}
async getStations(): Promise<IStation[]> {
const response = await fetch(this.url + endpoints.getStations, {
@ -69,24 +67,23 @@ export const api = new (class ApiDB {
});
return (await response.json()) as IStationData[];
}
async getUsers() {
const response = await fetch(this.url + endpoints.getUsers, {
method: 'GET',
});
async getStationsData(): Promise<IStationData[]> {
const response = await fetch(
this.url + endpoints.getStationsData,
{
method: 'GET',
},
);
return (await response.json()) as IStationData[];
}
async getUsers(){
const response = await fetch(this.url + endpoints.getUsers,{method:"GET"});
return (await response.json()) as IUser[];
}
async createUser(user: Omit<IUser, 'id'>) {
const response = await fetch(this.url + endpoints.getUsers, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(user),
});
if (response.ok) {
return (await response.json()) as IUser;
} else {
throw new Error('Unknown answer from db');
}
async createUser(user:Omit<IUser,"id">){
const response = await fetch(this.url + endpoints.getUsers,{method:"POST",headers:{
"Content-Type": "application/json",
},body:JSON.stringify(user)});
return response.ok
}
})();

21
src/app/wagonsSlice.ts Normal file
View File

@ -0,0 +1,21 @@
import { createSlice, configureStore } from '@reduxjs/toolkit'
const wagonSlice = createSlice({
name: 'wagonsSlice',
initialState: {
selectedWagons: []
},
reducers: {
selectWagon: (state, action) => {
state.selectedWagons.find(action.payload) ?
state.selectedWagons = state.selectedWagons.filter(el => el == action.payload):
state.selectedWagons = <never[]>[...state.selectedWagons, action.payload]
},
}
})
export const { selectWagon } = wagonSlice.actions
export const store = configureStore({
reducer: wagonSlice.reducer
})