├── .prettierrc.json ├── .eslintrc.json ├── dashboard.png ├── src ├── app │ ├── favicon.ico │ ├── fonts │ │ ├── GeistVF.woff │ │ └── GeistMonoVF.woff │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── components │ ├── Footer.tsx │ ├── ThemeToggle.tsx │ ├── SearchBar.tsx │ ├── MetricBox.tsx │ ├── PriceTable.tsx │ ├── DataTable.tsx │ ├── Sidebar.tsx │ ├── Pagination.tsx │ ├── DonutChart.tsx │ └── BarChart.tsx ├── hooks │ ├── useIndustryComparison.ts │ ├── useFetchData.ts │ ├── useTopAndLowestPrices.ts │ ├── usePaginatedData.ts │ └── useIndustryMetrics.ts ├── styles │ └── font.css ├── services │ └── dataService.ts ├── context │ └── themeContext.tsx └── server.ts ├── next.config.mjs ├── public └── fonts │ ├── Vazir-Bold-FD-WOL.eot │ ├── Vazir-Bold-FD-WOL.ttf │ ├── Vazir-Bold-FD-WOL.woff │ └── Vazir-Bold-FD-WOL.woff2 ├── .github ├── ISSUE_TEMPLATE │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md ├── workflows │ └── lint.yml └── PULL_REQUEST_TEMPLATE.md ├── postcss.config.mjs ├── .gitignore ├── tailwind.config.ts ├── SECURITY.md ├── LICENSE ├── tsconfig.json ├── package.json ├── CONTRIBUTING.md ├── README.md └── CODE_OF_CONDUCT.md /.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/finance-bourse/HEAD/dashboard.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/finance-bourse/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/finance-bourse/HEAD/src/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /src/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/finance-bourse/HEAD/src/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /public/fonts/Vazir-Bold-FD-WOL.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/finance-bourse/HEAD/public/fonts/Vazir-Bold-FD-WOL.eot -------------------------------------------------------------------------------- /public/fonts/Vazir-Bold-FD-WOL.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/finance-bourse/HEAD/public/fonts/Vazir-Bold-FD-WOL.ttf -------------------------------------------------------------------------------- /public/fonts/Vazir-Bold-FD-WOL.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/finance-bourse/HEAD/public/fonts/Vazir-Bold-FD-WOL.woff -------------------------------------------------------------------------------- /public/fonts/Vazir-Bold-FD-WOL.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/finance-bourse/HEAD/public/fonts/Vazir-Bold-FD-WOL.woff2 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Footer: React.FC = () => { 4 | return ( 5 | 8 | ); 9 | }; 10 | 11 | export default Footer; 12 | -------------------------------------------------------------------------------- /src/components/ThemeToggle.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTheme } from "../context/themeContext"; 3 | 4 | const ThemeToggle: React.FC = () => { 5 | const { theme, toggleTheme } = useTheme(); 6 | 7 | return ( 8 | 14 | ); 15 | }; 16 | 17 | export default ThemeToggle; 18 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background: #ffffff; 7 | --foreground: #171717; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | 17 | body { 18 | color: var(--foreground); 19 | background: var(--background); 20 | @apply font-vazir; 21 | } 22 | 23 | @layer utilities { 24 | .text-balance { 25 | text-wrap: balance; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | branches: ["master"] 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4.2.1 13 | 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: 20 17 | 18 | - name: Install Dependencies 19 | run: npm i 20 | 21 | - name: Prettier 22 | run: npm run prettier:check 23 | 24 | - name: Lint 25 | run: npm run lint 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please provide a brief description of the changes you made. 4 | 5 | ## Related Issue 6 | 7 | If this pull request is related to an issue, please link it here. 8 | 9 | ## Checklist 10 | 11 | - [ ] My code follows the style guidelines of this project. 12 | - [ ] I have performed a self-review of my code. 13 | - [ ] I have commented my code, particularly in hard-to-understand areas. 14 | - [ ] I have made corresponding changes to the documentation. 15 | - [ ] My changes generate no new warnings. 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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /src/hooks/useIndustryComparison.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { DataItem } from "../services/dataService"; 3 | 4 | const useIndustryComparison = (data: DataItem[]) => { 5 | const industryData = useMemo(() => { 6 | const distribution: { [key: string]: number } = {}; 7 | data.forEach((item) => { 8 | distribution[item.Industry] = (distribution[item.Industry] || 0) + 1; // شمارش تعداد 9 | }); 10 | return distribution; 11 | }, [data]); 12 | 13 | return industryData; 14 | }; 15 | 16 | export default useIndustryComparison; 17 | -------------------------------------------------------------------------------- /src/styles/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Vazir"; 3 | src: url("../../public/fonts/Vazir-Bold-FD-WOL.woff2") format("woff2"); 4 | font-weight: 400; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: "Vazir"; 10 | src: url("../../public/fonts/Vazir-Bold-FD-WOL.woff2") format("woff2"); 11 | font-weight: 700; 12 | font-style: normal; 13 | } 14 | 15 | @font-face { 16 | font-family: "Vazir"; 17 | src: url("../../public/fonts/Vazir-Bold-FD-WOL.woff2") format("woff2"); 18 | font-weight: 300; 19 | font-style: normal; 20 | } 21 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | darkMode: "class", // فعال کردن حالت تیره با استفاده از کلاس 5 | content: [ 6 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 9 | ], 10 | theme: { 11 | extend: { 12 | colors: { 13 | background: "var(--background)", 14 | foreground: "var(--foreground)", 15 | }, 16 | fontFamily: { 17 | vazir: ["Vazir"], 18 | }, 19 | }, 20 | }, 21 | plugins: [], 22 | }; 23 | export default config; 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import localFont from "next/font/local"; 3 | import { ThemeProvider } from "../context/themeContext"; 4 | import "./globals.css"; 5 | import "../styles/font.css"; 6 | 7 | export const metadata: Metadata = { 8 | title: "داشبورد معاملات بورس", 9 | description: "داشبورد مدیریتی معاملات بورس کشور", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 | 20 | 21 |
{children}
22 | 23 | 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/services/dataService.ts: -------------------------------------------------------------------------------- 1 | export interface DataItem { 2 | name: string; 3 | Full_Name: string; 4 | eps: number; 5 | Industry: string; 6 | [key: string]: any; // برای انعطاف‌پذیری با سایر فیلدها 7 | } 8 | 9 | export const fetchAllData = async (): Promise => { 10 | try { 11 | const response = await fetch(`http://localhost:5000/data`); 12 | 13 | if (!response.ok) { 14 | throw new Error(`Failed to fetch data: ${response.statusText}`); 15 | } 16 | 17 | const json: { ok: boolean; data: DataItem[] } = await response.json(); 18 | 19 | if (json.ok && Array.isArray(json.data)) { 20 | return json.data; 21 | } else { 22 | throw new Error("Invalid data format"); 23 | } 24 | } catch (error: any) { 25 | console.error("Fetch error:", error.message); 26 | throw error; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/SearchBar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface SearchBarProps { 4 | value: string; 5 | onChange: (value: string) => void; 6 | } 7 | 8 | const SearchBar: React.FC = ({ value, onChange }) => { 9 | return ( 10 |
11 |
12 | onChange(e.target.value)} 18 | /> 19 |
20 |
21 | ); 22 | }; 23 | 24 | export default SearchBar; 25 | -------------------------------------------------------------------------------- /src/hooks/useFetchData.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { fetchAllData, DataItem } from "../services/dataService"; 3 | 4 | const useFetchData = () => { 5 | const [data, setData] = useState([]); 6 | const [isLoading, setIsLoading] = useState(true); 7 | const [error, setError] = useState(null); 8 | 9 | useEffect(() => { 10 | const fetchData = async () => { 11 | try { 12 | setIsLoading(true); 13 | setError(null); 14 | const result = await fetchAllData(); 15 | console.log("Fetched Data:", result); // بررسی داده‌ها 16 | setData(result); 17 | } catch (err: any) { 18 | console.error("Error fetching data:", err.message); 19 | setError(err.message || "Failed to fetch data"); 20 | } finally { 21 | setIsLoading(false); 22 | } 23 | }; 24 | 25 | fetchData(); 26 | }, []); 27 | 28 | return { data, isLoading, error }; 29 | }; 30 | 31 | export default useFetchData; 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Smartphone (please complete the following information):** 33 | 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Azadeh Sharifi Soltani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/hooks/useTopAndLowestPrices.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { DataItem } from "../services/dataService"; 3 | 4 | const useTopAndLowestPrices = (data: DataItem[]) => { 5 | const top5 = useMemo(() => { 6 | return [...data] 7 | .sort((a, b) => b.LastTradedPrice - a.LastTradedPrice) // مرتب‌سازی نزولی 8 | .slice(0, 5) 9 | .map((item) => ({ 10 | name: item.name || "N/A", 11 | Industry: item.Industry || "N/A", 12 | LastTradedPrice: item.LastTradedPrice || 0, 13 | LastTradedPrice_change: item.LastTradedPrice_change || 0, 14 | })); // تبدیل به قالب موردنظر 15 | }, [data]); 16 | 17 | const lowest5 = useMemo(() => { 18 | return [...data] 19 | .sort((a, b) => a.LastTradedPrice - b.LastTradedPrice) // مرتب‌سازی صعودی 20 | .slice(0, 5) 21 | .map((item) => ({ 22 | name: item.name || "N/A", 23 | Industry: item.Industry || "N/A", 24 | LastTradedPrice: item.LastTradedPrice || 0, 25 | LastTradedPrice_change: item.LastTradedPrice_change || 0, 26 | })); // تبدیل به قالب موردنظر 27 | }, [data]); 28 | 29 | return { top5, lowest5 }; 30 | }; 31 | 32 | export default useTopAndLowestPrices; 33 | -------------------------------------------------------------------------------- /src/context/themeContext.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { createContext, useContext, useState, useEffect } from "react"; 3 | 4 | interface ThemeContextType { 5 | theme: "light" | "dark"; 6 | toggleTheme: () => void; 7 | } 8 | 9 | const ThemeContext = createContext(undefined); 10 | 11 | export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ 12 | children, 13 | }) => { 14 | const [theme, setTheme] = useState<"light" | "dark">("light"); 15 | 16 | useEffect(() => { 17 | // تغییر کلاس بر روی HTML 18 | const root = window.document.documentElement; 19 | root.classList.remove(theme === "light" ? "dark" : "light"); 20 | root.classList.add(theme); 21 | }, [theme]); 22 | 23 | const toggleTheme = () => { 24 | setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light")); 25 | }; 26 | 27 | return ( 28 | 29 | {children} 30 | 31 | ); 32 | }; 33 | 34 | export const useTheme = (): ThemeContextType => { 35 | const context = useContext(ThemeContext); 36 | if (!context) { 37 | throw new Error("useTheme must be used within a ThemeProvider"); 38 | } 39 | return context; 40 | }; 41 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", // تنظیم نسخه خروجی جاوااسکریپت 4 | "lib": ["dom", "dom.iterable", "esnext"], // اضافه کردن ویژگی‌های DOM و ESNext 5 | "allowJs": true, // اجازه به استفاده از فایل‌های JS 6 | "skipLibCheck": true, // عدم بررسی فایل‌های کتابخانه برای سرعت بیشتر 7 | "strict": true, // فعال کردن حالت Strict TypeScript 8 | "noEmit": true, // عدم تولید فایل‌های خروجی جاوااسکریپت 9 | "esModuleInterop": true, // سازگاری با ماژول‌های CommonJS 10 | "module": "esnext", // تنظیم نوع ماژول برای ESNext 11 | "moduleResolution": "node", // استفاده از رزولوشن ماژول Node.js 12 | "resolveJsonModule": true, // اجازه به وارد کردن فایل‌های JSON 13 | "isolatedModules": true, // فعال‌سازی ماژول‌های مستقل برای Next.js 14 | "jsx": "preserve", // حفظ JSX برای Next.js 15 | "incremental": true, // سرعت بیشتر در کامپایل مجدد 16 | "types": ["node"], // اضافه کردن تایپ‌های Node.js 17 | "plugins": [ 18 | { 19 | "name": "next" // استفاده از پلاگین Next.js 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] // تنظیم مسیرهای آلیاس 24 | } 25 | }, 26 | "include": [ 27 | "next-env.d.ts", // فایل محیط Next.js 28 | "**/*.ts", // شامل کردن تمام فایل‌های TS 29 | "**/*.tsx", // شامل کردن تمام فایل‌های TSX 30 | ".next/types/**/*.ts", // فایل‌های مرتبط با Next.js 31 | "server.ts" // اضافه کردن سرور Node.js 32 | ], 33 | "exclude": ["node_modules", "dist"] // حذف فایل‌های غیرضروری 34 | } 35 | -------------------------------------------------------------------------------- /src/hooks/usePaginatedData.ts: -------------------------------------------------------------------------------- 1 | import { useState, useMemo } from "react"; 2 | import { DataItem } from "../services/dataService"; 3 | 4 | interface UsePaginatedDataProps { 5 | data: DataItem[]; 6 | itemsPerPage: number; 7 | } 8 | 9 | const usePaginatedData = ({ data, itemsPerPage }: UsePaginatedDataProps) => { 10 | const [searchQuery, setSearchQuery] = useState(""); 11 | const [currentPage, setCurrentPage] = useState(1); 12 | 13 | // فیلتر کردن داده‌ها بر اساس جستجو 14 | const filteredData = useMemo( 15 | () => 16 | data.filter((item) => 17 | item.Industry.toLowerCase().includes(searchQuery.toLowerCase()), 18 | ), 19 | [data, searchQuery], 20 | ); 21 | 22 | // محاسبه داده‌های صفحه فعلی 23 | const indexOfLastItem = currentPage * itemsPerPage; 24 | const indexOfFirstItem = indexOfLastItem - itemsPerPage; 25 | const currentData = useMemo( 26 | () => filteredData.slice(indexOfFirstItem, indexOfLastItem), 27 | [filteredData, indexOfFirstItem, indexOfLastItem], 28 | ); 29 | 30 | // تعداد صفحات 31 | const totalPages = Math.ceil(filteredData.length / itemsPerPage); 32 | 33 | // تغییر صفحه 34 | const handlePageChange = (page: number) => { 35 | if (page >= 1 && page <= totalPages) { 36 | setCurrentPage(page); 37 | } 38 | }; 39 | 40 | return { 41 | currentData, 42 | searchQuery, 43 | setSearchQuery, 44 | currentPage, 45 | totalPages, 46 | handlePageChange, 47 | }; 48 | }; 49 | 50 | export default usePaginatedData; 51 | -------------------------------------------------------------------------------- /src/components/MetricBox.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface MetricBoxProps { 4 | title: string; 5 | data: { label: string; value: string | number }[]; 6 | color: string; 7 | } 8 | 9 | const MetricBox: React.FC = ({ title, data, color }) => { 10 | return ( 11 |
14 |

17 | {title} 18 |

19 |
20 | {data.map((item, index) => ( 21 |
25 | 26 | {item.label} 27 | 28 | 0 31 | ? "text-green-500" 32 | : typeof item.value === "number" && item.value < 0 33 | ? "text-red-500" 34 | : "text-gray-800 dark:text-white" 35 | }`} 36 | > 37 | {item.value} 38 | 39 |
40 | ))} 41 |
42 |
43 | ); 44 | }; 45 | 46 | export default MetricBox; 47 | -------------------------------------------------------------------------------- /src/hooks/useIndustryMetrics.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { DataItem } from "../services/dataService"; 3 | 4 | const useIndustryMetrics = (data: DataItem[]) => { 5 | // محاسبه توزیع داده‌ها بر اساس صنعت 6 | const industryDistribution = useMemo(() => { 7 | const distribution: { [key: string]: number } = {}; 8 | data.forEach((item) => { 9 | distribution[item.Industry] = (distribution[item.Industry] || 0) + 1; 10 | }); 11 | return distribution; 12 | }, [data]); 13 | 14 | // محاسبه بالاترین تغییر قیمت صعودی 15 | const highestPositiveChangeItem = useMemo(() => { 16 | const positiveChanges = data.filter( 17 | (item) => item.LastTradedPrice_change > 0, 18 | ); 19 | if (positiveChanges.length === 0) { 20 | return { name: "N/A", LastTradedPrice_change: 0, LastTradedPrice: 0 }; 21 | } 22 | return positiveChanges.reduce((prev, curr) => 23 | curr.LastTradedPrice_change > prev.LastTradedPrice_change ? curr : prev, 24 | ); 25 | }, [data]); 26 | 27 | // محاسبه بالاترین تغییر قیمت نزولی 28 | const highestNegativeChangeItem = useMemo(() => { 29 | const negativeChanges = data.filter( 30 | (item) => item.LastTradedPrice_change < 0, 31 | ); 32 | if (negativeChanges.length === 0) { 33 | return { name: "N/A", LastTradedPrice_change: 0, LastTradedPrice: 0 }; 34 | } 35 | return negativeChanges.reduce((prev, curr) => 36 | curr.LastTradedPrice_change < prev.LastTradedPrice_change ? curr : prev, 37 | ); 38 | }, [data]); 39 | 40 | return { 41 | industryDistribution, 42 | highestPositiveChangeItem, 43 | highestNegativeChangeItem, 44 | }; 45 | }; 46 | 47 | export default useIndustryMetrics; 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "finance-bourse", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "next dev", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "next lint", 11 | "prettier:check": "prettier --check .", 12 | "prettier:fix": "prettier --write .", 13 | "server": "ts-node src/server.ts" 14 | }, 15 | "dependencies": { 16 | "@reduxjs/toolkit": "^2.5.0", 17 | "axios": "^1.7.9", 18 | "chart.js": "^4.4.7", 19 | "chartjs-chart-financial": "^0.2.1", 20 | "cors": "^2.8.5", 21 | "express": "^4.21.2", 22 | "lightweight-charts": "^4.2.2", 23 | "next": "14.2.20", 24 | "prettier": "^3.4.2", 25 | "react": "^18", 26 | "react-chartjs-2": "^5.2.0", 27 | "react-dom": "^18", 28 | "react-icons": "^5.4.0", 29 | "react-redux": "^9.2.0", 30 | "redux": "^5.0.1" 31 | }, 32 | "devDependencies": { 33 | "@types/cors": "^2.8.17", 34 | "@types/express": "^5.0.0", 35 | "@types/node": "^20.17.10", 36 | "@types/react": "^18", 37 | "@types/react-dom": "^18", 38 | "eslint": "^8", 39 | "eslint-config-next": "14.2.20", 40 | "postcss": "^8", 41 | "tailwindcss": "^3.4.1", 42 | "ts-node": "^10.9.2", 43 | "typescript": "^5.7.2" 44 | }, 45 | "description": "<<<<<<< HEAD #Finance Bourse =======", 46 | "main": "index.js", 47 | "repository": { 48 | "type": "git", 49 | "url": "git+https://github.com/frau-azadeh/finance-bourse.git" 50 | }, 51 | "keywords": [], 52 | "author": "", 53 | "license": "ISC", 54 | "bugs": { 55 | "url": "https://github.com/frau-azadeh/finance-bourse/issues" 56 | }, 57 | "homepage": "https://github.com/frau-azadeh/finance-bourse#readme" 58 | } 59 | -------------------------------------------------------------------------------- /src/components/PriceTable.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface PriceTableProps { 4 | title: string; 5 | data: { 6 | name: string; 7 | Industry: string; 8 | LastTradedPrice: number; 9 | LastTradedPrice_change: number; 10 | }[]; 11 | } 12 | 13 | const PriceTable: React.FC = ({ title, data }) => { 14 | return ( 15 |
16 |

17 | {title} 18 |

19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | {data.map((item, index) => ( 30 | 38 | 39 | 40 | 41 | 42 | ))} 43 | 44 |
برندصنعتقیمت
{item.name}{item.Industry}{item.LastTradedPrice}
45 |
46 |
47 | ); 48 | }; 49 | 50 | export default PriceTable; 51 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 🚀 Contributing to Our Project 2 | 3 | We are thrilled that you are interested in contributing to our project. Your support and involvement are what make our community strong and our project better. Whether you're fixing a bug, adding a feature, or simply improving documentation, every contribution is valuable. 💪 4 | 5 | ## 📝 How to Contribute 6 | 7 | ### 1. Fork the Repository 8 | 9 | Click the _Fork_ button at the top right of this page to create a copy of the repository under your GitHub account. 10 | 11 | ### 2. Clone Your Fork 12 | 13 | bash 14 | git clone https://github.com/frau-azadeh/finance-bourse.git 15 | cd REPOSITORY_NAME 16 | 17 | ### 3. Create a Branch 18 | 19 | Create a new branch for your changes: 20 | 21 | git checkout -b your-feature-name 22 | 23 | ### 4. Make Your Changes 24 | 25 | Edit the code, fix bugs, or improve the documentation. Remember to write clean and well-documented code. 26 | 27 | ### 5. Commit Your Changes 28 | 29 | git add . 30 | git commit -m "Brief description of your changes" 31 | 32 | ### 6. Push to Your Fork 33 | 34 | git push origin your-feature-name 35 | 36 | ### 7. Create a Pull Request 37 | 38 | Go to the original repository, click on _Pull Requests, and select \*\*New Pull Request_. Choose your branch and submit the PR for review. 39 | 40 | ## ✅ Contribution Guidelines 41 | 42 | - Ensure your code follows the existing style and conventions. 43 | - Write clear, concise commit messages. 44 | - Provide a detailed description of your changes in the pull request. 45 | - Test your changes before submitting. 46 | 47 | ## 🌟 Additional Ways to Contribute 48 | 49 | - Report issues 🐛 50 | - Suggest new features ✨ 51 | - Improve documentation 📖 52 | - Share the project with others 💬 53 | 54 | ## 🤝 Code of Conduct 55 | 56 | Please note that we follow a [Code of Conduct](CODE_OF_CONDUCT.md) to ensure a welcoming and inclusive environment. 57 | 58 | ## 💬 Need Help? 59 | 60 | If you need any help or have questions, feel free to open an issue or reach out to us. We’re happy to help! 🚀 61 | 62 | Thank you for helping us make this project better! ❤️ 63 | -------------------------------------------------------------------------------- /src/components/DataTable.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { DataItem } from "../services/dataService"; 3 | 4 | interface DataTableProps { 5 | data: DataItem[]; 6 | } 7 | 8 | const DataTable: React.FC = ({ data }) => { 9 | return ( 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | {data.map((item, index) => ( 23 | 31 | 34 | 37 | 40 | 41 | 50 | 53 | 54 | ))} 55 | 56 |
برندنام شرکتقیمتتغییرصنعت
32 | {item.name ?? "N/A"} 33 | 35 | {item.Full_Name ?? "Unknown"} 36 | 38 | {item.LastTradedPrice ?? "N/A"} 39 | = 0 44 | ? "text-green-500" 45 | : "text-red-500" 46 | }`} 47 | > 48 | {item.LastTradedPrice_change} 49 | 51 | {item.Industry ?? "N/A"} 52 |
57 |
58 | ); 59 | }; 60 | 61 | export default DataTable; 62 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from "express"; 2 | import cors from "cors"; 3 | import axios from "axios"; 4 | import fs from "fs"; 5 | import path from "path"; 6 | import { fileURLToPath } from "url"; 7 | 8 | // تنظیم __dirname برای ESM 9 | const __filename = fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(__filename); 11 | 12 | const app = express(); 13 | app.use(cors()); 14 | 15 | // مسیر فایل JSON 16 | const filePath = path.join(__dirname, "data.json"); 17 | 18 | // تابع برای دریافت داده‌ها از API و ذخیره در فایل JSON 19 | const fetchAndSaveData = async () => { 20 | try { 21 | const apiUrl = 22 | "https://bourse.chartapi.ir/ickvopfg9pb5dno6kqmnpg7o7dvd4sx1/alldata"; 23 | const response = await axios.get(apiUrl); 24 | 25 | // بررسی ساختار داده‌ها و ذخیره در فایل 26 | if (response.data && Array.isArray(response.data.data)) { 27 | const cleanedData = response.data.data; 28 | 29 | fs.writeFileSync(filePath, JSON.stringify(cleanedData, null, 2), "utf-8"); 30 | console.log("Data successfully fetched and saved to data.json"); 31 | } else { 32 | console.error("Invalid data format from API"); 33 | } 34 | } catch (error: any) { 35 | console.error("Error fetching data from API:", error.message || error); 36 | } 37 | }; 38 | 39 | // مسیر برای ارائه داده‌ها از فایل JSON 40 | app.get("/data", (req: Request, res: Response): void => { 41 | try { 42 | if (!fs.existsSync(filePath)) { 43 | res.status(404).json({ ok: false, error: "Data file not found" }); 44 | return; 45 | } 46 | 47 | const fileContent = fs.readFileSync(filePath, "utf-8"); 48 | 49 | if (!fileContent || fileContent.trim() === "") { 50 | res 51 | .status(500) 52 | .json({ ok: false, error: "Data file is empty or invalid" }); 53 | return; 54 | } 55 | 56 | const data = JSON.parse(fileContent); 57 | res.json({ ok: true, data }); 58 | } catch (error: any) { 59 | console.error("Error reading data file:", error.message || error); 60 | res.status(500).json({ ok: false, error: "Failed to read data file" }); 61 | } 62 | }); 63 | 64 | // شروع سرور 65 | const PORT = 5000; 66 | app.listen(PORT, async () => { 67 | console.log(`Server is running on http://localhost:${PORT}`); 68 | 69 | // دریافت داده‌ها از API هنگام شروع سرور 70 | await fetchAndSaveData(); 71 | 72 | // به‌روزرسانی داده‌ها هر 5 دقیقه 73 | setInterval(fetchAndSaveData, 5 * 60 * 1000); 74 | }); 75 | -------------------------------------------------------------------------------- /src/components/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { 3 | FaHome, 4 | FaChartBar, 5 | FaCog, 6 | FaQuestionCircle, 7 | FaBars, 8 | } from "react-icons/fa"; 9 | import ThemeToggle from "./ThemeToggle"; 10 | 11 | const Sidebar: React.FC = () => { 12 | const [isMenuOpen, setIsMenuOpen] = useState(false); 13 | 14 | const menuItems = [ 15 | { label: "داشبورد ", icon: }, 16 | { label: "گزارش‌ها ", icon: }, 17 | { label: "تنظیمات ", icon: }, 18 | ]; 19 | 20 | return ( 21 | <> 22 | {/* منوی کناری برای دسکتاپ */} 23 |
24 |

25 | 26 |

27 |
    28 | {menuItems.map((item, index) => ( 29 |
  • 33 | {item.icon} 34 | {item.label} 35 |
  • 36 | ))} 37 |
38 |
39 | 40 | {/* منوی همبرگری برای موبایل */} 41 |
42 |
43 |

44 | 45 |

46 | 52 |
53 | {isMenuOpen && ( 54 |
55 |
    56 | {menuItems.map((item, index) => ( 57 |
  • 61 | {item.icon} 62 | {item.label} 63 |
  • 64 | ))} 65 |
66 |
67 | )} 68 |
69 | 70 | ); 71 | }; 72 | 73 | export default Sidebar; 74 | -------------------------------------------------------------------------------- /src/components/Pagination.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MdChevronLeft, MdChevronRight } from "react-icons/md"; // آیکون‌ها 3 | 4 | interface PaginationProps { 5 | totalItems: number; 6 | itemsPerPage: number; 7 | currentPage: number; 8 | onPageChange: (page: number) => void; 9 | } 10 | 11 | const Pagination: React.FC = ({ 12 | totalItems, 13 | itemsPerPage, 14 | currentPage, 15 | onPageChange, 16 | }) => { 17 | const totalPages = Math.ceil(totalItems / itemsPerPage); 18 | 19 | if (totalPages === 1) return null; 20 | 21 | const handlePageClick = (page: number) => { 22 | if (page >= 1 && page <= totalPages) { 23 | onPageChange(page); 24 | } 25 | }; 26 | 27 | return ( 28 |
29 |
30 | {/* دکمه قبلی */} 31 | 42 | 43 | {/* شماره صفحات */} 44 |
45 | {Array.from({ length: totalPages }, (_, i) => ( 46 | 57 | ))} 58 |
59 | 60 | {/* دکمه بعدی */} 61 | 72 |
73 |
74 | ); 75 | }; 76 | 77 | export default Pagination; 78 | -------------------------------------------------------------------------------- /src/components/DonutChart.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Doughnut } from "react-chartjs-2"; 3 | import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js"; 4 | 5 | // ثبت پلاگین‌های Chart.js 6 | ChartJS.register(ArcElement, Tooltip, Legend); 7 | 8 | interface DonutChartProps { 9 | data: { [key: string]: number }; // داده‌هایی که درصد سهم هر صنعت را نشان می‌دهند 10 | } 11 | 12 | const DonutChart: React.FC = ({ data }) => { 13 | const [isDarkMode, setIsDarkMode] = useState(false); 14 | 15 | useEffect(() => { 16 | const updateDarkMode = () => { 17 | const darkModeEnabled = 18 | document.documentElement.classList.contains("dark"); 19 | setIsDarkMode(darkModeEnabled); 20 | }; 21 | 22 | // فراخوانی اولیه و همچنین شنیدن تغییرات دارک مد 23 | updateDarkMode(); 24 | const observer = new MutationObserver(updateDarkMode); 25 | observer.observe(document.documentElement, { attributes: true }); 26 | 27 | return () => observer.disconnect(); 28 | }, []); 29 | 30 | // گرفتن 10 صنعت برتر 31 | const sortedData = Object.entries(data) 32 | .sort(([, a], [, b]) => b - a) 33 | .slice(0, 10); 34 | 35 | const labels = sortedData.map(([key]) => key); // برچسب‌ها 36 | const values = sortedData.map(([, value]) => value); // مقادیر 37 | 38 | const total = values.reduce((sum, val) => sum + val, 0); // مجموع برای درصدها 39 | 40 | const chartData = { 41 | labels, 42 | datasets: [ 43 | { 44 | label: "Top 10 Industries", 45 | data: values, 46 | backgroundColor: [ 47 | "#FF6384", 48 | "#36A2EB", 49 | "#FFCE56", 50 | "#4BC0C0", 51 | "#9966FF", 52 | "#FF9F40", 53 | "#FFD700", 54 | "#40E0D0", 55 | "#8A2BE2", 56 | "#FF4500", 57 | ], 58 | borderColor: "#FFFFFF", 59 | borderWidth: 2, 60 | hoverOffset: 10, 61 | }, 62 | ], 63 | }; 64 | 65 | const options = { 66 | responsive: true, 67 | plugins: { 68 | legend: { 69 | display: false, // حذف نوشته‌های پایین نمودار 70 | }, 71 | tooltip: { 72 | callbacks: { 73 | label: function (context: any) { 74 | const percentage = ((context.raw as number) / total) * 100; 75 | return `${context.label}: ${context.raw} (${percentage.toFixed(2)}%)`; 76 | }, 77 | }, 78 | }, 79 | }, 80 | }; 81 | 82 | return ( 83 |
84 |

85 | 10 صنعت برتر در معاملات بورس امروز 86 |

87 | 88 |
89 | ); 90 | }; 91 | 92 | export default DonutChart; 93 | -------------------------------------------------------------------------------- /src/components/BarChart.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Bar } from "react-chartjs-2"; 3 | import { 4 | Chart as ChartJS, 5 | BarElement, 6 | Tooltip, 7 | Legend, 8 | CategoryScale, 9 | LinearScale, 10 | ChartData, 11 | ChartOptions, 12 | } from "chart.js"; 13 | 14 | ChartJS.register(BarElement, Tooltip, Legend, CategoryScale, LinearScale); 15 | 16 | interface BarChartProps { 17 | data: { [key: string]: number }; 18 | } 19 | 20 | const BarChart: React.FC = ({ data }) => { 21 | const labels = Object.keys(data); 22 | const values = Object.values(data); 23 | const chartData: ChartData<"bar"> = { 24 | labels, 25 | datasets: [ 26 | { 27 | label: "Industry Comparison", 28 | data: values, 29 | backgroundColor: labels.map( 30 | (_, index) => 31 | `rgba(${100 + index * 15}, ${150 - index * 10}, ${200 - index * 10}, 0.8)`, 32 | ), // رنگ‌های گرادیانت برای هر میله 33 | borderColor: "rgba(0,0,0,0.1)", 34 | borderWidth: 1, 35 | borderRadius: 5, // گوشه‌های گرد 36 | }, 37 | ], 38 | }; 39 | 40 | const options: ChartOptions<"bar"> = { 41 | responsive: true, 42 | plugins: { 43 | legend: { 44 | display: false, // عدم نمایش توضیحات 45 | }, 46 | tooltip: { 47 | callbacks: { 48 | label: (context) => `Value: ${context.raw}`, // نمایش مقدار در Tooltip 49 | }, 50 | }, 51 | }, 52 | scales: { 53 | x: { 54 | ticks: { 55 | font: { 56 | size: 12, 57 | }, 58 | color: "#555", 59 | }, 60 | grid: { 61 | display: false, // حذف خطوط عمودی 62 | }, 63 | title: { 64 | display: true, 65 | text: "Industries", 66 | font: { 67 | size: 14, 68 | weight: "bold", 69 | }, 70 | color: "#333", 71 | }, 72 | }, 73 | y: { 74 | ticks: { 75 | font: { 76 | size: 12, 77 | }, 78 | color: "#555", 79 | }, 80 | grid: { 81 | color: "rgba(200, 200, 200, 0.3)", // خطوط افقی کم‌رنگ 82 | }, 83 | title: { 84 | display: true, 85 | text: "Values", 86 | font: { 87 | size: 14, 88 | weight: "bold", 89 | }, 90 | color: "#333", 91 | }, 92 | }, 93 | }, 94 | }; 95 | 96 | return ( 97 |
98 |

99 | مقایسه صنعت ها در نمودار امروز 100 |

101 | 102 |
103 | ); 104 | }; 105 | 106 | export default BarChart; 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Finance Dashboard 2 | 3 | A responsive and visually appealing Finance Dashboard to display financial data, industry trends, and dynamic charts. Built using modern web technologies, this project is optimized for both functionality and user experience. 4 | 5 | ## 📸 Screenshot 6 | 7 | ### 💻 Dashboard View 8 | 9 | A clean and detailed dashboard to monitor financial data and market trends: 10 | 11 | ![Dashboard View](https://github.com/frau-azadeh/finance-bourse/blob/master/dashboard.png) 12 | 13 | --- 14 | 15 | ## Features 16 | 17 | 📊 Dynamic Charts: Interactive bar and donut charts using Chart.js. 18 | 19 | 📋 Data Table: Searchable, paginated, and responsive. 20 | 21 | 🌗 Theme Switcher: Toggle between light and dark mode. 22 | 23 | 📱 Responsive Design: Optimized for all screen sizes. 24 | 25 | 🧭 Sidebar Navigation: Hamburger menu for mobile and a full-height sidebar for desktop. 26 | 27 | 📈 Industry Metrics: Highlight key data with highest positive and negative changes. 28 | 29 | ## Frontend 30 | 31 | ⚛️ React: JavaScript library for building user interfaces. 32 | 33 | 🛠️ TypeScript: Typed superset of JavaScript. 34 | 35 | 🎨 Tailwind CSS: Utility-first CSS framework. 36 | 37 | 📊 Chart.js: For visualizing data. 38 | 39 | 🌟 React Icons: Icon library for financial icons. 40 | 41 | ## Backend 42 | 43 | 🚀 Express.js: Node.js web framework. 44 | 45 | 🗄️ JSON Data: Simulates API responses. 46 | 47 | ## Installation 48 | 49 | Clone the repository: 50 | 51 | git clone https://github.com/frau-azadeh/finance-dashboard.git 52 | cd finance-dashboard 53 | 54 | Install dependencies: 55 | 56 | npm install 57 | 58 | Start the development server: 59 | 60 | npm run dev 61 | 62 | Start the backend server: 63 | 64 | npm run server 65 | 66 | Open the app: 67 | Navigate to http://localhost:3000 in your browser. 68 | 69 | ## Folder Structure 70 | 71 | finance-dashboard/ 72 | ├── public/ 73 | ├── src/ 74 | │ ├── components/ 75 | │ │ ├── BarChart.tsx 76 | │ │ ├── DataTable.tsx 77 | │ │ ├── DonutChart.tsx 78 | │ │ ├── Footer.tsx 79 | │ │ ├── MetricBox.tsx 80 | │ │ ├── Pagination.tsx 81 | │ │ ├── PriceTable.tsx 82 | │ │ ├── SearchBar.tsx 83 | │ │ ├── Sidebar.tsx 84 | │ ├── context/ 85 | │ │ ├── ThemeContext.tsx 86 | │ ├── hooks/ 87 | │ │ ├── useFetchData.ts 88 | │ │ ├── usePaginatedData.ts 89 | │ │ ├── useIndustryMetrics.ts 90 | │ │ ├── useTopAndLowestPrices.ts 91 | │ ├── pages/ 92 | │ │ ├── Dashboard.tsx 93 | │ ├── services/ 94 | │ │ ├── dataService.ts 95 | │ ├── styles/ 96 | │ │ ├── globals.css 97 | │ ├── tailwind.config.js 98 | │ 99 | ├── package.json 100 | ├── tsconfig.json 101 | ├── README.md 102 | 103 | ## Environment Variables 104 | 105 | Set the following environment variables in a .env file: 106 | 107 | REACT_APP_API_URL=http://localhost:5000/data 108 | 109 | Key Components 110 | 111 | Sidebar 112 | 113 | 🧭 Full-height sidebar for desktop with a responsive hamburger menu for mobile. 114 | 115 | MetricBox 116 | 117 | 📊 Displays key metrics such as highest positive and negative changes. 118 | 119 | Charts 120 | 121 | 📈 Interactive visualizations for data insights using Chart.js. 122 | 123 | Pagination 124 | 125 | 🔄 Smooth and responsive pagination for data navigation. 126 | 127 | ## Contributors 128 | 129 | 🌻 Azadeh Sharifi Soltani 130 | 131 | Feel free to contribute to this project by submitting a pull request or opening an issue! 132 | 133 | Made with 💻, ☕, and 🌻 by Azadeh Sharifi Soltani 134 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import useFetchData from "../hooks/useFetchData"; 5 | import usePaginatedData from "../hooks/usePaginatedData"; 6 | import useIndustryMetrics from "../hooks/useIndustryMetrics"; 7 | import useTopAndLowestPrices from "../hooks/useTopAndLowestPrices"; 8 | import DataTable from "../components/DataTable"; 9 | import Pagination from "../components/Pagination"; 10 | import SearchBar from "../components/SearchBar"; 11 | import MetricBox from "../components/MetricBox"; 12 | import BarChart from "../components/BarChart"; 13 | import PriceTable from "../components/PriceTable"; 14 | import DonutChart from "../components/DonutChart"; 15 | import Sidebar from "../components/Sidebar"; 16 | import Footer from "../components/Footer"; 17 | 18 | const Dashboard: React.FC = () => { 19 | const { data, isLoading, error } = useFetchData(); 20 | const itemsPerPage = 15; 21 | 22 | const { 23 | currentData, 24 | searchQuery, 25 | setSearchQuery, 26 | currentPage, 27 | totalPages, 28 | handlePageChange, 29 | } = usePaginatedData({ data, itemsPerPage }); 30 | 31 | const { 32 | industryDistribution, 33 | highestPositiveChangeItem, 34 | highestNegativeChangeItem, 35 | } = useIndustryMetrics(data); 36 | 37 | const { top5, lowest5 } = useTopAndLowestPrices(data); 38 | 39 | if (isLoading) { 40 | return ( 41 |
42 |

Loading...

43 |
44 | ); 45 | } 46 | 47 | if (error) { 48 | return ( 49 |
50 |

{error}

51 |
52 | ); 53 | } 54 | 55 | return ( 56 |
57 |
58 | {/* سایدبار */} 59 | 60 | 61 | {/* محتوای اصلی */} 62 |
63 | {/* ستون وسط */} 64 |
65 |
66 | 81 | 96 |
97 | 98 | 99 | 105 | 106 |
107 | {/* ستون چپ */} 108 |
109 | 110 | 111 | 112 |
113 |
114 |
115 | 116 | {/* فوتر */} 117 |
119 | ); 120 | }; 121 | 122 | export default Dashboard; 123 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | designweb.azadeh@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | --------------------------------------------------------------------------------