Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 60 additions & 37 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import {Route, Routes} from "react-router-dom";
import './App.css'
import AppTheme from './theme/AppTheme';
import TripsTape from './components/TripsTape.tsx';
import {TripsTape} from './components/TripsTape.tsx';
import {Container, CssBaseline} from "@mui/material";
import AppAppBar from "./components/AppAppBar.tsx";
import {useEffect, useState} from "react";
import {AuthForm} from "./components/AuthForm.tsx";
import {SuccessLoginToast} from "./components/SuccessLoginToast.tsx";
import {Modal, Box} from '@mui/material';
import {MeService} from "./api/generated";
import MyTripsTape from "./components/MyTripsTape.tsx";
import {MeService, TripsService} from "./api/generated";
import {MyTripsTape} from "./components/MyTripsTape.tsx";
import AppBarProvider from "./AppBarContext";
import type {ITrip} from "./models/ITrip.ts";
import Utils from "./services/Utils.ts";

function checkAuthFromStorage(): boolean {
const idToken = localStorage.getItem('idToken');
Expand All @@ -23,8 +25,22 @@ function App() {
const [, setIsProfileComplete] = useState(false);
const [toastMessage, setToastMessage] = useState('');
const [user, setUser] = useState<{ studentId?: string } | null>(null);
const [userId, setUserId] = useState("");
const [globalTrips, setGlobalTrips] = useState<ITrip[]>([]);

const getTrips = async () => {
try {
const response = await TripsService.getApiV1Trips();
const mapped = response.map(Utils.mapTripResponseToITrip);

setGlobalTrips(mapped);
} catch (e) {
console.error("Ошибка:", e);
}
};

useEffect(() => {
getTrips();
if (checkAuthFromStorage()) {
setIsAuthenticated(true);
}
Expand All @@ -36,6 +52,7 @@ function App() {
const profile = await MeService.getApiV1Me();

setUser({studentId: `${profile.first_name} ${profile.last_name} (${profile.student_id})`});
setUserId(profile.id);

const isComplete =
!!profile.bio && !!profile.social_network_username;
Expand Down Expand Up @@ -67,42 +84,42 @@ function App() {
// @ts-ignore
return (
<>
<AppBarProvider>
<Modal
open={!isAuthenticated}
aria-labelledby="auth-form-modal"
aria-describedby="auth-form-to-login"
sx={{
backdropFilter: 'blur(8px)',
backgroundColor: 'rgba(0, 0, 0, 0.4)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Box
<AppBarProvider>
<Modal
open={!isAuthenticated}
aria-labelledby="auth-form-modal"
aria-describedby="auth-form-to-login"
sx={{
bgcolor: 'background.grey',
boxShadow: 24,
borderRadius: 10,
p: 4,
minWidth: 360,
maxWidth: '90%',
backdropFilter: 'blur(8px)',
backgroundColor: 'rgba(0, 0, 0, 0.4)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<AuthForm
onSuccess={handleLoginSuccess}
onProfileComplete={handleProfileCompleted}
/>
</Box>
</Modal>
<Box
sx={{
bgcolor: 'background.grey',
boxShadow: 24,
borderRadius: 10,
p: 4,
minWidth: 360,
maxWidth: '90%',
}}
>
<AuthForm
onSuccess={handleLoginSuccess}
onProfileComplete={handleProfileCompleted}
/>
</Box>
</Modal>

{toastMessage && (
<SuccessLoginToast
message={toastMessage}
onClose={() => setToastMessage('')}
/>
)}
{toastMessage && (
<SuccessLoginToast
message={toastMessage}
onClose={() => setToastMessage('')}
/>
)}

<AppTheme>
<CssBaseline enableColorScheme/>
Expand All @@ -124,8 +141,14 @@ function App() {
}}
>
<Routes>
<Route path="/" element={<TripsTape/>}/>
<Route path="my_trips" element={<MyTripsTape/>}/>
<Route path="/" element={<TripsTape trips={globalTrips}/>}/>
<Route path="my_trips" element={
<MyTripsTape
trips={globalTrips}
userId={userId}
onNewTrip={async () => await getTrips()}
/>}
/>
</Routes>
</Container>
</AppTheme>
Expand Down
40 changes: 26 additions & 14 deletions frontend/src/TestData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,43 +61,53 @@ export const trips: ITrip[] = [
arrival_time: new Date('2025-11-25T15:30:00Z'),
transport_type: 'Общественный транспорт',
trip_frequency: 'Каждую неделю',
comment: 'Быстрее всего на метро, согласны?'
comment: 'Быстрее всего на метро, согласны?',
firstAddr: 'Площадь Восстания',
lastAddr: 'ИТМО на Кронверском'
},
{
author: authors[1],
departure_coords: [59.92768158712746,30.36048332235471],
arrival_coords: [59.95718670847085,30.308284464092527],
departure_coords: [59.9369, 30.4801],
arrival_coords: [59.94403, 30.2952],
arrival_time: new Date('2025-11-25T20:30:00Z'),
transport_type: 'Такси',
trip_frequency: 'Разовая',
comment: ''
comment: '',
firstAddr: 'Метро Проспект Большевиков',
lastAddr: 'ИТМО на Биржевой'
},
{
author: authors[2],
departure_coords: [59.92768158712746,30.36048332235471],
arrival_coords: [59.95718670847085,30.308284464092527],
departure_coords: [59.936562064167354,30.499978000000002],
arrival_coords: [59.92351402504148,30.376095926709972],
arrival_time: new Date('2025-11-20T15:30:00Z'),
transport_type: 'Общественный транспорт',
trip_frequency: 'Каждую неделю',
comment: 'Люблю автобусы'
comment: 'Люблю автобусы',
firstAddr: 'Общежитие на Белорусской',
lastAddr: 'Миргородская 3'
},
{
author: authors[3],
departure_coords: [59.92768158712746,30.36048332235471],
arrival_coords: [59.95718670847085,30.308284464092527],
departure_coords: [59.984031419684804,30.35699900879305],
arrival_coords: [59.92646000495477,30.339519547126066],
arrival_time: new Date('2025-11-20T20:30:00Z'),
transport_type: 'Своя машина',
trip_frequency: 'Каждую неделю',
comment: 'Готов подвезти'
comment: 'Готов подвезти',
firstAddr: 'Станция метро Лесная',
lastAddr: 'ИТМО на Ломоносова'
},
{
author: authors[4],
departure_coords: [59.92768158712746,30.36048332235471],
arrival_coords: [59.95718670847085,30.308284464092527],
departure_coords: [59.98401017798144,30.35052918892963],
arrival_coords: [59.92655346472663,30.33910851753994],
arrival_time: new Date('2025-11-20T12:00:00Z'),
transport_type: 'Общественный транспорт',
trip_frequency: 'Каждую неделю',
comment: ''
comment: '',
firstAddr: 'Станция метро Лесная',
lastAddr: 'ИТМО на Ломоносова'
},
{
author: authors[2],
Expand All @@ -106,6 +116,8 @@ export const trips: ITrip[] = [
arrival_time: new Date('2025-11-20T12:00:00Z'),
transport_type: 'Каршеринг',
trip_frequency: 'Каждую неделю',
comment: ''
comment: '',
firstAddr: 'Станция метро Лесная',
lastAddr: 'ИТМО на Ломоносова'
},
];
2 changes: 1 addition & 1 deletion frontend/src/components/AuthForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function AuthForm({ onSuccess, onProfileComplete }: AuthFormProps) {
{step === "login" ? (
<>
<Typography variant="h5" align="center">
ITMO.TRIP ID
ITMO ID
</Typography>

<TextField
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/MyTrip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ const MyTrip: FC<TripProps> = (props) => {
>

{/* Трип */}
<CardContent sx={{flexGrow: 1, p: 2, pb: 0, mb: 0}}>
<CardContent sx={{flexGrow: 1, p: 2, pb: 0, mb: 3}}>
{/* Заголовок маршрута */}
<Box sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 0.5}}>
<Box sx={{display: 'flex', alignItems: 'flex-start', ml: -0.3}}>
<LocationOn sx={{color: 'primary.main', mr: 0.6, fontSize: 20}}/>
<Typography variant="h6" sx={{fontWeight: 'bold', lineHeight: 1, mt: 0.3, textAlign: "left"}}>
{'Площадь восстания'} → {'ИТМО на Кронверском'}
{props.tripData.firstAddr} {} → {props.tripData.lastAddr}
</Typography>
</Box>
<Grid style={{marginRight: -6}}>
Expand Down Expand Up @@ -173,6 +173,7 @@ const MyTrip: FC<TripProps> = (props) => {
textAlign: 'left',
mx: 0,
alignSelf: 'flex-start',
mb: -1,
'&::before': {
content: '""',
position: 'absolute',
Expand Down
55 changes: 39 additions & 16 deletions frontend/src/components/MyTripsTape.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,20 @@ import {UserInfo} from "./UserInfo.tsx";
import Grid from "@mui/material/Grid";
import {Button} from "@mui/material";
import NewTripModal from "./NewTripModal.tsx";
import type {ITrip} from "../models/ITrip.ts";
import {MeService} from "../api/generated";
import {SuccessLoginToast} from "./SuccessLoginToast.tsx";

export default function MyTripsTape() {
interface MyTripsTapeProps {
trips: ITrip[]
userId: string
onNewTrip: () => Promise<void>;
}

export const MyTripsTape: React.FC<MyTripsTapeProps> = (props) => {
const [newTripOpen, setNewTripOpen] = useState(false);
const [toastMessage, setToastMessage] = useState('');
const [userId, setUserId] = useState("");

const {setAction, reset} = useAppBarAction();
useEffect(() => {
Expand All @@ -22,6 +33,16 @@ export default function MyTripsTape() {
return () => reset();
}, [setAction, reset]);

useEffect(() => {
fetchAndSetUserId()
}, []);

const fetchAndSetUserId = async () => {
const meResponse = await MeService.getApiV1Me();
setUserId(meResponse.id)
return;
}

return (
<Box sx={{display: 'flex', flexDirection: 'column', gap: 2, width: '100%'}}>
<Grid container spacing={2} justifyContent="space-between" marginRight={2}>
Expand All @@ -44,33 +65,35 @@ export default function MyTripsTape() {
</Grid>
</Grid>

{toastMessage && (
<SuccessLoginToast
message={toastMessage}
onClose={() => setToastMessage('')}
/>
)}


<Box sx={{width: '100%', marginRight: 12}}>
<Masonry
columns={{xs: 1, md: 2}}
spacing={2}
>
<div key={0}>
<MyTrip tripData={trips[0]}/>
</div>
<div key={1}>
<MyTrip tripData={trips[0]}/>
</div>
<div key={2}>
<MyTrip tripData={trips[0]}/>
</div>
{/*{trips.map((tr, index) => (*/}
{/* <div key={index}>*/}
{/* <Trip tripData={tr} />*/}
{/* </div>*/}
{/*))}*/}
{props.trips.filter(tr => tr.author.id === props.userId || tr.author.id === userId).map((tr, index) => (
<div key={index}>
<MyTrip tripData={tr}/>
</div>
))}
</Masonry>
</Box>

{newTripOpen &&
<NewTripModal
isOpen={newTripOpen}
close={() => setNewTripOpen(false)}
close={() => {
setNewTripOpen(false)
setToastMessage("Новая поездка успешно добавлена!")
}}
onNewTrip={props.onNewTrip}
/>}
</Box>
);
Expand Down
11 changes: 5 additions & 6 deletions frontend/src/components/NewTripModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Utils from "../services/Utils.ts";
interface NewTripProps {
isOpen: boolean;
close: any;
onNewTrip: () => Promise<void>;
}

interface NewTripState {
Expand Down Expand Up @@ -54,14 +55,14 @@ const steps = ["Отправление", "Прибытие", "Дополните

const NewTripModal: FC<NewTripProps> = (props) => {
const [newTripState, setNewTripState] = useState<NewTripState>({
arrival_time: undefined,
arrival_time: new Date(),
departure_time: new Date(),
transport_type_id: undefined,
comment: "",
})

const [activeStep, setActiveStep] = useState(0);
const [errors, setErrors] = useState<string[]>();
const [_, setErrors] = useState<string[]>();

const [transportTypes, setTransportTypes] = useState<TransportType[]>();
const [locationTypes, setLocationTypes] = useState<LocationType[]>();
Expand Down Expand Up @@ -142,6 +143,7 @@ const NewTripModal: FC<NewTripProps> = (props) => {
}

await TripsService.postApiV1Trips(tripRequest)
props.onNewTrip()
clearModal()
props.close()
} catch (e) {
Expand All @@ -167,9 +169,6 @@ const NewTripModal: FC<NewTripProps> = (props) => {
Новая поездка
</DialogTitle>
<DialogContent>
{errors && (
<p style={{color: "red", marginBottom: "0"}}>{errors}</p>
)}
<Stepper activeStep={activeStep} alternativeLabel sx={{mb: 3}}>
{steps.map((label) => (
<Step key={label}>
Expand Down Expand Up @@ -246,7 +245,7 @@ const NewTripModal: FC<NewTripProps> = (props) => {
label="Время прибытия"
type="datetime-local"
variant='standard'
value={DateTimeUtils.toISOString(newTripState.departure_time) ?? ''}
value={DateTimeUtils.toISOString(newTripState.arrival_time) ?? ''}
onChange={(e) => {
setNewTripState(prevState => ({
...prevState,
Expand Down
Loading
Loading