Фото Roger Ce на Unsplash
Час додати кольору до цього проєкту і почати втілювати деякі з концептів, обговорених у попередньому пості. Ми створимо нашу аплікацію, яка зможе візуалізувати та використовувати ресурси з нашого API. Почнемо!
Для фронтенд-технологій ми вибрали React, оскільки він залишається однією з найпоширеніших технологій у галузі і надає нам велику гнучкість. Як фреймворк ми використовуватимемо NextJS; ідея полягає в тому, щоб почати створювати нашу панель управління простим способом, а згодом подивимося, чи є це найбільш підходящим підходом для нашої аплікації.
Для початку ми створимо нашу аплікацію за допомогою команди npx create-next-app@latest
npx create-next-app@latest
Необхідно встановити наступні пакети:
[email protected]
Продовжити? (y) y
✔ Як називатиметься ваш проєкт? … app
✔ Бажаєте використовувати TypeScript? … Ні* / Так
✔ Бажаєте використовувати ESLint? … Ні / Так*
✔ Бажаєте використовувати Tailwind CSS? … Ні / Так*
✔ Бажаєте розмістити код в директорії `src/`? … Ні* / Так
✔ Бажаєте використовувати App Router? (рекомендовано) … Ні / Так*
✔ Бажаєте використовувати Turbopack для next dev? … Ні / Так*
✔ Бажаєте налаштувати псевдоніми імпортів (@/* за замовчуванням)? … Ні* / Так
Наразі ми використовуватимемо JavaScript замість TypeScript, хоча в майбутньому це може змінитись. Для стилізації ми працюватимемо з Tailwind, щоб спростити розробку компонентів. Тепер, коли у нас є базова NextJS аплікація, наступний крок — додати її до нашого docker-compose, щоб вона запускалася разом з усією інфраструктурою.
app:
container_name: app
build:
context: ./app
dockerfile: Dockerfile
env_file: app/.env
volumes:
- ./app:/home/app
- /app/node_modules
command: "npm run dev"
environment:
- NODE_ENV=development
ports:
- '3000:3000'
networks:
- kt3-net
Давайте трохи очистимо проєкт, щоб почати з макету у стилі Dashboard. Для цього ми створимо папку Dashboard та компонент для Sidebar.
Для Sidebar ми використаємо наступний код:
// app/app/components/Sidebar.js
import Link from "next/link";
export default function Sidebar() {
return (
KT3
Entries
Accounts
Account Groups
) } ``` Тепер, в папці **_dashboard_**, ми створимо головну сторінку з її макетом.
// app/app/(dashboard)/layout.js
import "../globals.css";
import Sidebar from "@/app/components/Sidebar";
export const metadata = {
title: "Kt3 App",
description: "KT3 App Dashboard",
};
export default function RootLayout({children}) {
return (
{children}
);
}
```
Це наша початкова сторінка, простий компонент, який відображає заголовок Dashboard зі стилями Tailwind.
// app/app/(dashboard)/page.js
export default function Home() {
return (
Dashboard
);
}
Тепер нам потрібні сторінки для наших перших моделей (Account Group, Account, Entry). Наразі ми додамо лише заголовки моделей, щоб перевірити, чи правильно працюють маршрути. До цього моменту ми створили наступні файли:
.
├── components
│ └── sidebar.js
├── (dashboard)
│ ├── account-groups
│ │ └── page.js
│ ├── accounts
│ │ └── page.js
│ ├── entries
│ │ └── page.js
│ ├── layout.js
│ └── page.js
├── favicon.ico
└── globals.css
Щоб додати кольору та зв'язати з попереднім дописом, ми протестуємо графік, що підсумовує кожну з моделей. Для цього використаємо одну з моїх улюблених бібліотек: Highcharts.
npm install highcharts --save
Після того, як бібліотеку встановлено, ми створимо перший запит для отримання загальної кількості Account Groups.
Застереження
Тільки зараз я зрозумів, що не оновив попередній пост з цим кінцевим пунктом.
Тепер, для нашого запиту, ми спробуємо більш комплексний підхід до здійснення запитів, подібно до того, як це працює в SWR. Мета тут — зрозуміти, як обробляти обіцянки та помилки.
Для цього ми використаємо функцію для управління нашими запитами:
// app/app/utils/fetchData.js
function getSuspender(promise) {
let status = 'pending';
let result;
let suspender = promise.then(
(response) => {
status = 'success';
result = response;
},
(error) => {
status = 'error';
result = error;
}
);
const read = () => {
switch (status) {
case 'pending':
throw suspender;
case 'error':
throw result;
case 'success':
return result;
}
}
return { read };
}
export default function fetchData(url) {
const promise = fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => data);
return getSuspender(promise);
}
Тепер ми будемо використовувати нашу функцію для отримання даних і передавати їх до компонентів, які оброблятимуть логіку відображення наших графіків.
Перший графік, який ми створимо, буде стовпчиковим графіком для кожної групи рахунків:
// app/app/components/BarChart.js
"use client";
import React from "react";
import Highcharts from "highcharts";
import {useEffect} from "react";
export default function BarChart({data}) {
let categories = [];
let series = [];
for (let key in data) {
categories.push(data[key].name);
series.push(data[key].amount);
}
useEffect(() => {
Highcharts.chart("basic-bar-chart", {
chart: {
type: 'column'
},
title: {
text: 'Accrued expenses by Account Group'
},
xAxis: {
categories: categories
},
yAxis: [{
min: 0,
title: {
text: 'Expends (MXN)'
}
}],
legend: {
shadow: false
},
tooltip: {
shared: true
},
plotOptions: {
column: {
grouping: false,
shadow: false,
borderWidth: 0
}
},
series: [{
name: 'Amount',
color: 'rgba(165,170,217,1)',
data: series,
pointPadding: 0.3,
pointPlacement: -0.2
}]
});
}, []);
return
; }
Тепер давайте створимо Кругову діаграму для відображення розподілу Account Groups (груп рахунків) відносно їх загальних витрат.
// app/app/components/PieChart.js
"use client";
import React from 'react';
import Highcharts from 'highcharts';
import {useEffect} from "react";
export default function PieChart({data}) {
let series = []
for (let key in data) {
series.push([data[key].name, data[key].amount])
}
useEffect(() => {
Highcharts.chart('pie-chart', {
chart: {
type: 'pie',
},
title: {
text: 'Distribution of expenses by account group'
},
series: [
{
name: 'Account Groups',
data: series
}
]
})
}, [])
return (
)
}
Для завершення цієї сторінки ми додамо таблицю, в якій будуть перелічені Account Groups (групи рахунків) з можливістю взаємодії з кожною з них (Оновлення та Видалення).
// app/components/BasicTable.js
"use client";
export default function BasicTable({columns, dataColumns, data, actions}) {
return (
{columns.map((col, index) => ( ))}
{data ? data.map((row, rowIndex) => (
{dataColumns.map((col, colIndex) => ( ))}
{actions &&
{col}
{row[col]}
{actions.update &&
}
{actions.delete &&
}
}
)) :
Empty
}
);
}
На даний момент у нас є головна сторінка для Account Groups (груп рахунків), яка виглядає ось так:
// app/app/(dashboard)/account-groups/page.js
"use client";
import BarChart from "@/app/components/BarChart";
import PieChart from "@/app/components/PieChart";
import BasicTable from "@/app/components/BasicTable";
import {Suspense} from "react";
import fetchData from "@/app/utils/fetchData";
import Link from "next/link";
import MainTitle from "@/app/components/MainTitle";
const apiDataTotal = fetchData("http://localhost:7000/api/analytics/account_groups_total");
const apiData = fetchData("http://localhost:7000/api/account_groups");
export default function Page() {
const dataTotal = apiDataTotal.read();
const data = apiData.read();
return (
<>
Loading...
}>
Loading...
}>
);
}
Для завершення цієї статті, ми додамо кнопку, яка дозволить нам додавати Account Groups (групи рахунків).
Create Account Group
Тепер у нас є щось функціональне, що починає набувати форми та кольору.
Наступним кроком ми створимо форму для обробки запиту на створення Account Groups (груп рахунків).
// app/app/components/AccountGroupForm.js
"use client";
import {useState, useRef} from "react";
export default function AccountGroupForm() {
const [accountGroup, setAccountGroup] = useState({
name: "",
});
const form = useRef(null);
function handleChange(event) {
setAccountGroup({
...accountGroup,
});
}
async function handleSubmit(event) {
event.preventDefault();
fetch(
"http://localhost:7000/api/account_groups", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(accountGroup),
}).then((res) => res.json()).then(() => {
setAccountGroup({
name: "",
})
window.location.reload();
});
}
return (
Name
Create
); } ``` Власне, ми маємо реактивну форму, яка надсилає запит після оцінки її вмісту.
На даний момент ми не реалізовуватимемо валідацію полів; це стане частиною подальшої роботи над проєктом.
Далі ми створимо папку з назвою **_create_**, де розмістимо нашу сторінку для додавання нових **_Account Groups_**. На цій сторінці ми викличемо нашу форму.
// app/app/(dashboard)/account-groups/create/page.js
import AccountGroupForm from "@/app/components/AccountGroupForm";
import MainTitle from "@/app/components/MainTitle";
export default function Page() {
return (
) } ``` 
Висновок
Ми почали інтеграцію API з клієнтською частиною нашого проєкту. Ще багато роботи попереду: додавання нових функціональностей, покращення користувацького досвіду та реалізація валідацій між бекендом і фронтендом.
Наразі ми зосередимося на наступних кроках і продовжимо ділитися вдосконаленнями в міру розвитку проєкту. Якщо у вас є пропозиції чи покращення для коду, не соромтесь залишити їх у коментарях.
Домашнє завдання
- Створити форми для Accounts і Entries.
- Реалізувати Breadcrumb для покращення навігації.
- Додати Toast для повідомлень і сповіщень.
- Покращити стилі та дизайн.
Наступні кроки
- Модель користувача та аутентифікація
- Логи та помилки
- Метрики (Prometheus, Grafana, Kibana)
- Кешування та пошук (Redis і Elastic)
Джерело коду
Посилання
Перекладено з: KT3 — Part II (Init Dashboard)