├── .eslintrc.json
├── src
├── pages
│ └── home
│ │ ├── index.ts
│ │ ├── ui
│ │ ├── main-page-info.tsx
│ │ ├── main-actions.tsx
│ │ └── layout.tsx
│ │ ├── home.page.tsx
│ │ └── home.page copy.tsx
├── app
│ ├── globals.css
│ ├── index.ts
│ ├── theme.ts
│ ├── create-emotion-cache.ts
│ └── app.tsx
├── features
│ ├── suggestions-list
│ │ ├── index.ts
│ │ ├── ui
│ │ │ ├── list-layout.tsx
│ │ │ ├── filters-layout.tsx
│ │ │ └── suggestion-card.tsx
│ │ ├── compose
│ │ │ ├── suggestions-provider.tsx
│ │ │ ├── suggestions-list.tsx
│ │ │ └── suggestions-filters.tsx
│ │ ├── model
│ │ │ ├── entities.ts
│ │ │ ├── suggestions-list.model.ts
│ │ │ └── filters.model.tsx
│ │ └── constants.ts
│ └── update-suggestion-rating
│ │ └── compose
│ │ └── update-rating-buttons.tsx
└── shared
│ └── ui
│ └── form
│ ├── ui-text-field.tsx
│ └── ui-select-field.tsx
├── pages
├── _app.tsx
├── index.tsx
├── api
│ └── hello.ts
└── _document.tsx
├── bun.lockb
├── public
├── favicon.ico
├── vercel.svg
└── next.svg
├── next.config.js
├── .gitignore
├── package.json
├── tsconfig.json
└── README.md
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/src/pages/home/index.ts:
--------------------------------------------------------------------------------
1 | export { HomePage } from "./home.page";
2 |
--------------------------------------------------------------------------------
/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { App } from "@/app";
2 |
3 | export default App;
4 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvgenyParomov/suggestions-frontend/HEAD/bun.lockb
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { HomePage } from "@/pages/home";
2 |
3 | export default HomePage;
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvgenyParomov/suggestions-frontend/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | }
5 |
6 | module.exports = nextConfig
7 |
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @import "@fontsource/roboto/300.css";
2 | @import "@fontsource/roboto/400.css";
3 | @import "@fontsource/roboto/500.css";
4 | @import "@fontsource/roboto/700.css";
5 |
--------------------------------------------------------------------------------
/src/app/index.ts:
--------------------------------------------------------------------------------
1 | export { App } from "./app";
2 | export type { AppProps } from "./app";
3 | export { theme, roboto } from "./theme";
4 | export { createEmotionCache } from "./create-emotion-cache";
5 |
--------------------------------------------------------------------------------
/src/features/suggestions-list/index.ts:
--------------------------------------------------------------------------------
1 | export { SuggestionsList } from "./compose/suggestions-list";
2 | export { SuggestionsFilters } from "./compose/suggestions-filters";
3 | export { SuggestionsProvider } from "./compose/suggestions-provider";
4 |
--------------------------------------------------------------------------------
/src/features/suggestions-list/ui/list-layout.tsx:
--------------------------------------------------------------------------------
1 | import { Stack } from "@mui/material";
2 | import { ReactNode } from "react";
3 |
4 | export function ListLayout({children}: { children: ReactNode }) {
5 | return {children};
6 | }
7 |
--------------------------------------------------------------------------------
/src/pages/home/ui/main-page-info.tsx:
--------------------------------------------------------------------------------
1 | import { Typography } from "@mui/material";
2 |
3 | export function MainPageInfo() {
4 | return (
5 |
6 | Список предложений
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/src/features/suggestions-list/ui/filters-layout.tsx:
--------------------------------------------------------------------------------
1 | import { Stack } from "@mui/material";
2 | import { ReactNode } from "react";
3 |
4 | export function FiltersLayout({children}: { children: ReactNode }) {
5 | return {children};
6 | }
7 |
--------------------------------------------------------------------------------
/src/pages/home/ui/main-actions.tsx:
--------------------------------------------------------------------------------
1 | import { Box, Button } from "@mui/material";
2 |
3 | export function MainActions() {
4 | return (
5 |
6 |
7 |
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/src/features/suggestions-list/compose/suggestions-provider.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from "react";
2 | import { FiltersDataProvider } from "../model/filters.model";
3 |
4 | export function SuggestionsProvider({ children }: { children: ReactNode }) {
5 | return {children};
6 | }
7 |
--------------------------------------------------------------------------------
/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | import type { NextApiRequest, NextApiResponse } from 'next'
3 |
4 | type Data = {
5 | name: string
6 | }
7 |
8 | export default function handler(
9 | req: NextApiRequest,
10 | res: NextApiResponse
11 | ) {
12 | res.status(200).json({ name: 'John Doe' })
13 | }
14 |
--------------------------------------------------------------------------------
/src/features/suggestions-list/model/entities.ts:
--------------------------------------------------------------------------------
1 | export type TagId = number;
2 | export type StatusId = number;
3 |
4 | export type TagEntity = {
5 | id: number;
6 | label: string;
7 | };
8 |
9 | export type StatusEntity = {
10 | id: number;
11 | label: string;
12 | };
13 |
14 | export type SuggestionEntity = {
15 | id: number;
16 | title: string;
17 | description: string;
18 | tags: TagEntity[];
19 | status: StatusEntity;
20 | createdAt: Date;
21 | grade: number;
22 | };
23 |
--------------------------------------------------------------------------------
/src/features/suggestions-list/compose/suggestions-list.tsx:
--------------------------------------------------------------------------------
1 | import { useSuggestionsList } from "../model/suggestions-list.model";
2 | import { ListLayout } from "../ui/list-layout";
3 | import { SuggestionCard } from "../ui/suggestion-card";
4 |
5 | export function SuggestionsList() {
6 | const list = useSuggestionsList();
7 |
8 | return (
9 |
10 | {list.map((suggestion) => (
11 |
12 | ))}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/shared/ui/form/ui-text-field.tsx:
--------------------------------------------------------------------------------
1 | import { TextField } from "@mui/material";
2 | import { ChangeEventHandler } from "react";
3 |
4 | export function UiTextField({
5 | label,
6 | onChange,
7 | value,
8 | }: {
9 | label: string;
10 | value: string;
11 | onChange: (value: string) => void;
12 | }) {
13 | const handleChange: ChangeEventHandler = (event) => {
14 | onChange(event.target.value);
15 | };
16 | return (
17 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/src/pages/home/home.page.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | SuggestionsFilters,
3 | SuggestionsList,
4 | SuggestionsProvider,
5 | } from "@/features/suggestions-list";
6 | import { Layout } from "./ui/layout";
7 | import { MainActions } from "./ui/main-actions";
8 | import { MainPageInfo } from "./ui/main-page-info";
9 |
10 | export function HomePage() {
11 | return (
12 |
13 | }
15 | mainPageInfo={}
16 | mainContent={}
17 | sidebar={}
18 | />
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/src/features/suggestions-list/constants.ts:
--------------------------------------------------------------------------------
1 | import { SuggestionOrder } from "./model/filters.model";
2 |
3 | export const tagsOptions = [
4 | { id: undefined, label: "Без тега" },
5 | { id: 1, label: "Тег1" },
6 | { id: 2, label: "Тег2" },
7 | { id: 3, label: "Тег3" },
8 | ];
9 |
10 | export const statusOptions = [
11 | { id: undefined, label: "Любой" },
12 | { id: 1, label: "Открыто" },
13 | { id: 2, label: "Закрыто" },
14 | ];
15 |
16 | export const sortOptions = [
17 | { id: "createAt-desc", label: "По дате создания" },
18 | { id: "grade-desc", label: "По пулярности" },
19 | ] satisfies Array<{ id: SuggestionOrder; label: string }>;
20 |
--------------------------------------------------------------------------------
/src/app/theme.ts:
--------------------------------------------------------------------------------
1 | import { Roboto } from "next/font/google";
2 | import { createTheme } from "@mui/material/styles";
3 | import { red } from "@mui/material/colors";
4 |
5 | export const roboto = Roboto({
6 | weight: ["300", "400", "500", "700"],
7 | subsets: ["cyrillic", "latin"],
8 | display: "swap",
9 | });
10 |
11 | // Create a theme instance.
12 | export const theme = createTheme({
13 | palette: {
14 | primary: {
15 | main: "#9351FF",
16 | },
17 | secondary: {
18 | main: "#FFE93F",
19 | },
20 | error: {
21 | main: red.A400,
22 | },
23 | },
24 | typography: {
25 | fontFamily: roboto.style.fontFamily,
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/create-emotion-cache.ts:
--------------------------------------------------------------------------------
1 | import createCache from "@emotion/cache";
2 |
3 | const isBrowser = typeof document !== "undefined";
4 |
5 | // On the client side, Create a meta tag at the top of the and set it as insertionPoint.
6 | // This assures that MUI styles are loaded first.
7 | // It allows developers to easily override MUI styles with other styling solutions, like CSS modules.
8 | export function createEmotionCache() {
9 | let insertionPoint;
10 |
11 | if (isBrowser) {
12 | const emotionInsertionPoint = document.querySelector(
13 | 'meta[name="emotion-insertion-point"]'
14 | );
15 | insertionPoint = emotionInsertionPoint ?? undefined;
16 | }
17 |
18 | return createCache({ key: "mui-style", insertionPoint });
19 | }
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@emotion/react": "^11.11.1",
13 | "@emotion/server": "^11.11.0",
14 | "@emotion/styled": "^11.11.0",
15 | "@fontsource/roboto": "^5.0.8",
16 | "@mui/icons-material": "^5.14.8",
17 | "@mui/material": "^5.14.8",
18 | "@types/node": "20.6.0",
19 | "@types/react": "18.2.21",
20 | "@types/react-dom": "18.2.7",
21 | "eslint": "8.49.0",
22 | "eslint-config-next": "13.4.19",
23 | "next": "13.4.19",
24 | "react": "18.2.0",
25 | "react-dom": "18.2.0",
26 | "typescript": "5.2.2"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/features/update-suggestion-rating/compose/update-rating-buttons.tsx:
--------------------------------------------------------------------------------
1 | import { Stack, Badge, ButtonGroup, Button } from "@mui/material";
2 | import GradeIcon from "@mui/icons-material/Grade";
3 | import AddIcon from "@mui/icons-material/Add";
4 | import RemoveIcon from "@mui/icons-material/Remove";
5 |
6 | export function UpdateRatingButtons() {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
16 |
19 |
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "noEmit": true,
13 | "esModuleInterop": true,
14 | "module": "esnext",
15 | "moduleResolution": "bundler",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "jsx": "preserve",
19 | "incremental": true,
20 | "paths": {
21 | "@/*": [
22 | "./src/*"
23 | ]
24 | },
25 | "plugins": [
26 | {
27 | "name": "next"
28 | }
29 | ]
30 | },
31 | "include": [
32 | "next-env.d.ts",
33 | "**/*.ts",
34 | "**/*.tsx",
35 | ".next/types/**/*.ts"
36 | ],
37 | "exclude": [
38 | "node_modules"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/src/features/suggestions-list/ui/suggestion-card.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Card,
3 | CardContent,
4 | Typography,
5 | } from "@mui/material";
6 |
7 | type Tag = {
8 | id: number;
9 | label: string;
10 | };
11 |
12 | type Status = {
13 | id: number;
14 | label: string;
15 | };
16 |
17 | export function SuggestionCard({
18 | title,
19 | description,
20 | tags,
21 | status,
22 | }: {
23 | title: string;
24 | description: string;
25 | tags: Tag[];
26 | status: Status;
27 | }) {
28 | return (
29 |
30 |
31 | {title}
32 | {description}
33 | Теги: {tags.map((v) => v.label).join(", ")}
34 | Статус: {status.label}
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/pages/home/ui/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Container, Grid, Box, Button, Typography } from "@mui/material";
2 | import { ReactNode } from "react";
3 |
4 | export function Layout({
5 | actions,
6 | mainPageInfo,
7 | mainContent,
8 | sidebar
9 | }: {
10 | actions: ReactNode;
11 | mainPageInfo: ReactNode;
12 | sidebar: ReactNode;
13 | mainContent: ReactNode;
14 | }) {
15 | return (
16 |
17 |
18 |
19 | {actions}
20 |
21 | {mainPageInfo}
22 |
23 |
24 | {sidebar}
25 | {mainContent}
26 |
27 |
28 | );
29 | }
30 |
31 |
32 |
33 | ;
34 |
35 |
36 | Список предложений
37 | ;
38 |
--------------------------------------------------------------------------------
/src/shared/ui/form/ui-select-field.tsx:
--------------------------------------------------------------------------------
1 | import { FormControl, InputLabel, Select, MenuItem } from "@mui/material";
2 |
3 | export function UiSelectField<
4 | IdKey extends string,
5 | LabelKey extends string,
6 | Option extends Record & Record
7 | >({
8 | idKey,
9 | label,
10 | labelKey,
11 | onChange,
12 | options,
13 | value,
14 | }: {
15 | label: string;
16 | idKey: IdKey;
17 | labelKey: LabelKey;
18 | value: Option[IdKey];
19 | onChange: (value: Option[IdKey]) => void;
20 | options: Option[];
21 | }) {
22 | return (
23 |
24 | {label}
25 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/src/features/suggestions-list/model/suggestions-list.model.ts:
--------------------------------------------------------------------------------
1 | import { SuggestionEntity } from "./entities";
2 | import { useFilters } from "./filters.model";
3 |
4 | export function useSuggestionsList() {
5 | const data: SuggestionEntity[] = [
6 | {
7 | id: 1,
8 | createdAt: new Date(),
9 | description: "Description",
10 | title: "title 1",
11 | grade: 4,
12 | status: { id: 1, label: "Открыто" },
13 | tags: [{ id: 1, label: "Тег1" }],
14 | },
15 | {
16 | id: 2,
17 | createdAt: new Date(Date.now() - 100000000),
18 | description: "Description",
19 | title: "title 2",
20 | grade: 6,
21 | status: { id: 2, label: "Закрыто" },
22 | tags: [{ id: 2, label: "Тег2" }],
23 | },
24 | ];
25 |
26 | const { data: filtersData } = useFilters();
27 |
28 | let preparedData = data;
29 |
30 | preparedData = preparedData.filter((item) => {
31 | if (filtersData.status && filtersData.status !== item.status.id) {
32 | return false;
33 | }
34 |
35 | return true;
36 | });
37 |
38 | return preparedData;
39 | }
40 |
--------------------------------------------------------------------------------
/src/app/app.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Head from "next/head";
3 | import { AppProps as NextAppProps } from "next/app";
4 | import { ThemeProvider } from "@mui/material/styles";
5 | import CssBaseline from "@mui/material/CssBaseline";
6 | import { CacheProvider, EmotionCache } from "@emotion/react";
7 | import { theme } from "./theme";
8 | import { createEmotionCache } from "./create-emotion-cache";
9 |
10 | // Client-side cache, shared for the whole session of the user in the browser.
11 | const clientSideEmotionCache = createEmotionCache();
12 |
13 | export interface AppProps extends NextAppProps {
14 | emotionCache?: EmotionCache;
15 | }
16 |
17 | export function App(props: AppProps) {
18 | const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
19 | return (
20 |
21 |
22 |
23 |
24 |
25 | {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/features/suggestions-list/compose/suggestions-filters.tsx:
--------------------------------------------------------------------------------
1 | import { UiSelectField } from "@/shared/ui/form/ui-select-field";
2 | import { UiTextField } from "@/shared/ui/form/ui-text-field";
3 | import { FiltersLayout } from "../ui/filters-layout";
4 | import { useFilters } from "../model/filters.model";
5 | import { sortOptions, statusOptions, tagsOptions } from "../constants";
6 |
7 | export function SuggestionsFilters() {
8 | const { data, updateOrder, updateQuery, updateStatus, updateTag } =
9 | useFilters();
10 |
11 | return (
12 |
13 |
18 |
26 |
34 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/src/features/suggestions-list/model/filters.model.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Dispatch,
3 | ReactNode,
4 | SetStateAction,
5 | createContext,
6 | useContext,
7 | useMemo,
8 | useState,
9 | } from "react";
10 | import { StatusId, TagId } from "./entities";
11 |
12 | export type SuggestionOrder = "grade-desc" | "createAt-desc";
13 |
14 | export type SuggestionFiltersData = {
15 | query: string;
16 | tag: TagId | undefined;
17 | status: StatusId | undefined;
18 | order: SuggestionOrder;
19 | };
20 |
21 | const filtersContext = createContext<{
22 | data: SuggestionFiltersData;
23 | setData: Dispatch>;
24 | } | null>(null);
25 |
26 | export function FiltersDataProvider({ children }: { children: ReactNode }) {
27 | const [data, setData] = useState({
28 | order: 'createAt-desc',
29 | query: "",
30 | status: undefined,
31 | tag: undefined,
32 | });
33 | return (
34 | ({ data, setData }), [data])}>
35 | {children}
36 |
37 | );
38 | }
39 |
40 | export function useFilters() {
41 | const contextValue = useContext(filtersContext);
42 | if (!contextValue) throw new Error("context not provided");
43 |
44 | const { data, setData } = contextValue;
45 |
46 | const updateQuery = (query: string) => {
47 | setData((d) => ({ ...d, query }));
48 | };
49 | const updateTag = (tag: TagId | undefined) => {
50 | setData((d) => ({ ...d, tag }));
51 | };
52 | const updateStatus = (status: StatusId | undefined) => {
53 | setData((d) => ({ ...d, status }));
54 | };
55 | const updateOrder = (order: SuggestionOrder) => {
56 | setData((d) => ({ ...d, order }));
57 | };
58 |
59 | return {
60 | data,
61 | updateStatus,
62 | updateTag,
63 | updateQuery,
64 | updateOrder,
65 | };
66 | }
67 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | ```
14 |
15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
16 |
17 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
18 |
19 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
20 |
21 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
22 |
23 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
24 |
25 | ## Learn More
26 |
27 | To learn more about Next.js, take a look at the following resources:
28 |
29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
31 |
32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
33 |
34 | ## Deploy on Vercel
35 |
36 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
37 |
38 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
39 |
--------------------------------------------------------------------------------
/src/pages/home/home.page copy.tsx:
--------------------------------------------------------------------------------
1 | import React, { ChangeEventHandler, useState } from "react";
2 | import {
3 | Container,
4 | TextField,
5 | FormControl,
6 | InputLabel,
7 | Select,
8 | MenuItem,
9 | Grid,
10 | Typography,
11 | SelectChangeEvent,
12 | Card,
13 | CardContent,
14 | Box,
15 | Badge,
16 | Button,
17 | ButtonGroup,
18 | Stack,
19 | } from "@mui/material";
20 |
21 | const suggestionsData = [
22 | {
23 | id: 1,
24 | title: "Предложение 1",
25 | description: "Это предложение 1",
26 | tags: [1, 2],
27 | status: 1,
28 | popularity: 10,
29 | },
30 | {
31 | id: 2,
32 | title: "Предложение 2",
33 | description: "Это предложение 2",
34 | tags: [3],
35 | status: 2,
36 | popularity: 5,
37 | },
38 | ];
39 |
40 | const tagsOptions = [
41 | { id: 1, label: "Тег1" },
42 | { id: 2, label: "Тег2" },
43 | { id: 3, label: "Тег3" },
44 | ];
45 |
46 | const statusOptions = [
47 | { id: 1, label: "Открыто" },
48 | { id: 2, label: "Закрыто" },
49 | ];
50 |
51 | const sortOptions = [
52 | { id: "популярности", label: "Популярности" },
53 | { id: "другой-вариант", label: "Другой вариант сортировки" },
54 | ];
55 |
56 | export function HomePage() {
57 | const [filterTags, setFilterTags] = useState("");
58 | const [filterStatus, setFilterStatus] = useState("");
59 | const [sortType, setSortType] = useState("популярности");
60 | const [searchQuery, setSearchQuery] = useState("");
61 |
62 | const handleFilterTagsChange = (event: SelectChangeEvent) => {
63 | setFilterTags(event.target.value);
64 | };
65 |
66 | const handleFilterStatusChange = (event: SelectChangeEvent) => {
67 | setFilterStatus(event.target.value);
68 | };
69 |
70 | const handleSortChange = (event: SelectChangeEvent) => {
71 | setSortType(event.target.value);
72 | };
73 |
74 | const handleSearchChange: ChangeEventHandler = (event) => {
75 | setSearchQuery(event.target.value);
76 | };
77 |
78 | const filteredSuggestions = suggestionsData.filter((suggestion) => {
79 | return true;
80 | });
81 |
82 | filteredSuggestions.sort((a, b) => {
83 | if (sortType === "популярности") {
84 | return b.popularity - a.popularity;
85 | }
86 | return 0;
87 | });
88 |
89 | return (
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | Список предложений
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | );
111 | }
112 |
--------------------------------------------------------------------------------
/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Document, {
3 | Html,
4 | Head,
5 | Main,
6 | NextScript,
7 | DocumentProps,
8 | DocumentContext,
9 | } from "next/document";
10 | import createEmotionServer from "@emotion/server/create-instance";
11 | import { AppType } from "next/app";
12 | import { AppProps, theme, roboto, createEmotionCache } from "@/app";
13 |
14 | interface MyDocumentProps extends DocumentProps {
15 | emotionStyleTags: JSX.Element[];
16 | }
17 |
18 | export default function MyDocument({ emotionStyleTags }: MyDocumentProps) {
19 | return (
20 |
21 |
22 | {/* PWA primary color */}
23 |
24 |
25 |
26 | {emotionStyleTags}
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | // `getInitialProps` belongs to `_document` (instead of `_app`),
37 | // it's compatible with static-site generation (SSG).
38 | MyDocument.getInitialProps = async (ctx: DocumentContext) => {
39 | // Resolution order
40 | //
41 | // On the server:
42 | // 1. app.getInitialProps
43 | // 2. page.getInitialProps
44 | // 3. document.getInitialProps
45 | // 4. app.render
46 | // 5. page.render
47 | // 6. document.render
48 | //
49 | // On the server with error:
50 | // 1. document.getInitialProps
51 | // 2. app.render
52 | // 3. page.render
53 | // 4. document.render
54 | //
55 | // On the client
56 | // 1. app.getInitialProps
57 | // 2. page.getInitialProps
58 | // 3. app.render
59 | // 4. page.render
60 |
61 | const originalRenderPage = ctx.renderPage;
62 |
63 | // You can consider sharing the same Emotion cache between all the SSR requests to speed up performance.
64 | // However, be aware that it can have global side effects.
65 | const cache = createEmotionCache();
66 | const { extractCriticalToChunks } = createEmotionServer(cache);
67 |
68 | ctx.renderPage = () =>
69 | originalRenderPage({
70 | enhanceApp: (
71 | App: React.ComponentType & AppProps>
72 | ) =>
73 | function EnhanceApp(props) {
74 | return ;
75 | },
76 | });
77 |
78 | const initialProps = await Document.getInitialProps(ctx);
79 | // This is important. It prevents Emotion to render invalid HTML.
80 | // See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153
81 | const emotionStyles = extractCriticalToChunks(initialProps.html);
82 | const emotionStyleTags = emotionStyles.styles.map((style: any) => (
83 |
89 | ));
90 |
91 | return {
92 | ...initialProps,
93 | emotionStyleTags,
94 | };
95 | };
96 |
--------------------------------------------------------------------------------