Skip to content

Commit 1f26f5b

Browse files
vikaivVictoria Ivanova
andauthored
Skeleton for Report page + Compress ava img (#21)
* add skeleton for report page; decompose Bug component * update readme * add RequestStates * compress default avatar image; small skeleton fixes * Dropdown refactoring * update vite to ^6.2.6 * remove console.log * fix endless skeleton for new report; add button spinners * refactoring of home page; remove constants, leave const folder * add skeleton for report page; decompose Bug component * update readme * add RequestStates * compress default avatar image; small skeleton fixes * Dropdown refactoring * update vite to ^6.2.6 * remove console.log * fix endless skeleton for new report; add button spinners * refactoring of home page; remove constants, leave const folder * fix skeleton for bug * fix attachment type when uploading attachment * fix textarea styles: text * fix textarea expanding --------- Co-authored-by: Victoria Ivanova <victoria.i@ati.su>
1 parent 31a7228 commit 1f26f5b

File tree

34 files changed

+594
-433
lines changed

34 files changed

+594
-433
lines changed

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"type": "module",
66
"scripts": {
77
"prepare": "npx simple-git-hooks",
8+
"start": "npm run start-backend && npm run dev",
9+
"start-backend": "docker compose -f ../devops/components/postgres/docker-compose.yml -f ../devops/components/backend/docker-compose.yml up -d",
810
"dev": "vite",
911
"build": "tsc && vite build",
1012
"lint": "eslint .",

frontend/readme.md

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,30 @@
22

33
## Стэк
44

5-
разработка - vite + ts + effector
6-
иконки - lucide (выбирал еще heroicons, но он менее строгий)
7-
компоненты - tailwind
8-
стили - daisyui
5+
- vite + ts + effector
6+
- lucide – иконки
7+
- tailwind – css-framework
8+
- [daisyui](https://daisyui.com/) – plugin для tailwind готовыми компонентами
99

1010
TODO писать сюда все новые пакеты что добавляем и что они делают, почему добавляем, чтобы не было дубликатов
1111

12-
date-fns - для работы с датами
12+
- date-fns
13+
- simple-git hooks – прекоммит хуки
14+
- lint-staged – запуск комманд только для "staged" файлов
15+
- eslint+prettier – линтер+форматтер
16+
17+
## Локальная разработка
18+
19+
### Перед началом работы
20+
21+
- npm ci – установка зависимостей и активация прекоммит-хуков
22+
23+
### Команды для локального запуска
24+
25+
- npm run dev – запуск фронтового приложения на дев-сервере
26+
- npm run start-backend – запуск локальной БД и локального бэкенда
27+
- npm start – запуск двух предыдущих команд последовательно
28+
29+
### Precommit-хуки
30+
31+
- если не работают прекоммит хуки (должны активироваться командой npm ci), то нужно вручную запустить команду `npx simple-git-hooks`

frontend/src/components/Avatar/Avatar.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
1+
import { useState } from "react";
12
import defaultAvaSrc from "./default-ava.png";
3+
import { RoundedSkeleton } from "../RoundedSkeleton/RoundedSkeleton";
24

35
type Props = {
46
src?: string;
57
width?: number;
68
};
79

810
const Avatar = ({ src = defaultAvaSrc, width = 8 }: Props) => {
11+
const [isLoaded, setIsLoaded] = useState(false);
12+
913
return (
1014
<div className="avatar">
15+
{!isLoaded && (
16+
<div className="absolute inset-0">
17+
<RoundedSkeleton />
18+
</div>
19+
)}
1120
<div className={`w-${width} rounded-full image-wrapper`}>
1221
<img
1322
src={src}
1423
alt="ava"
1524
style={{ width: `${2}em` }}
16-
className="avatar-img"
25+
onLoad={() => setIsLoaded(true)}
1726
/>
1827
</div>
1928
</div>
-281 KB
Loading

frontend/src/components/Breadcrumbs/Breadcrumbs.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import { Breadcrumb } from "@/types/ui";
22

33
type Props = {
44
breadcrumbs: Breadcrumb[];
5-
className?: string;
65
};
76

8-
const Breadcrumbs = ({ breadcrumbs, className }: Props) => (
9-
<div className={`breadcrumbs text-sm mx-4 ${className}`}>
7+
const Breadcrumbs = ({ breadcrumbs }: Props) => (
8+
<div className={`breadcrumbs text-sm mx-4 min-h-[52px] py-4`}>
109
<ul>
1110
{breadcrumbs.map((breadcrumb) => {
1211
return (

frontend/src/components/Dropdown/Dropdown.tsx

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ChevronDown, X } from "lucide-react";
2+
import { useRef } from "react";
23

34
function getSingleSelectedLabel<T>(
45
options: DropdownOption<T>[],
@@ -39,7 +40,7 @@ const Dropdown = <T,>(props: DropdownProps<T>) => {
3940
className = "",
4041
onResetValue,
4142
} = props;
42-
43+
const dropdownRef = useRef<HTMLDivElement>(null);
4344
const hasCustomResetValue = "onResetValue" in props;
4445

4546
const isSelected = (val: T) =>
@@ -53,8 +54,31 @@ const Dropdown = <T,>(props: DropdownProps<T>) => {
5354
? Array.isArray(value) && value.length > 0
5455
: value !== null && value !== undefined;
5556

57+
const handleDropdownClick = (event: React.MouseEvent<HTMLButtonElement>) => {
58+
event.stopPropagation();
59+
onChange(onResetValue ?? (multiple ? [] : null));
60+
dropdownRef.current?.blur();
61+
};
62+
63+
const handleOptionClick = (
64+
event: React.MouseEvent<HTMLButtonElement>,
65+
option: DropdownOption<T>
66+
) => {
67+
if (multiple) {
68+
const current = Array.isArray(value) ? value : [];
69+
const exists = current.includes(option.value);
70+
const updated = exists
71+
? current.filter((v) => v !== option.value)
72+
: [...current, option.value];
73+
onChange(updated);
74+
} else {
75+
onChange(option.value);
76+
dropdownRef.current?.blur();
77+
}
78+
};
79+
5680
return (
57-
<div className={`dropdown ${className}`} tabIndex={0}>
81+
<div className={`dropdown ${className}`} tabIndex={0} ref={dropdownRef}>
5882
{label && <div className="mb-1 text-xs font-semibold">{label}</div>}
5983
<div className="btn bg-base-100 w-full justify-between">
6084
<span className="flex gap-1 flex-wrap items-center font-normal">
@@ -82,11 +106,7 @@ const Dropdown = <T,>(props: DropdownProps<T>) => {
82106
<button
83107
type="button"
84108
className="inline-flex p-2"
85-
onClick={(e) => {
86-
e.stopPropagation();
87-
onChange(onResetValue ?? (multiple ? [] : null));
88-
(e.currentTarget.closest(".dropdown") as HTMLElement)?.blur();
89-
}}
109+
onClick={handleDropdownClick}
90110
>
91111
<X className="w-4 h-4 text-gray-400 hover:text-neutral cursor-pointer" />
92112
</button>
@@ -99,9 +119,9 @@ const Dropdown = <T,>(props: DropdownProps<T>) => {
99119
{!multiple && hasCustomResetValue && (
100120
<li key="none">
101121
<button
102-
onClick={(e) => {
122+
onClick={() => {
103123
onChange(null);
104-
(e.currentTarget.closest(".dropdown") as HTMLElement)?.blur();
124+
dropdownRef.current?.blur();
105125
}}
106126
className={value === null || value === undefined ? "active" : ""}
107127
>
@@ -111,29 +131,19 @@ const Dropdown = <T,>(props: DropdownProps<T>) => {
111131
</button>
112132
</li>
113133
)}
114-
{options.map((opt) => (
115-
<li key={String(opt.value)}>
134+
{options.map((option) => (
135+
<li key={String(option.value)}>
116136
<button
117-
onClick={(e) => {
118-
if (multiple) {
119-
const current = Array.isArray(value) ? value : [];
120-
const exists = current.includes(opt.value);
121-
const updated = exists
122-
? current.filter((v) => v !== opt.value)
123-
: [...current, opt.value];
124-
onChange(updated);
125-
} else {
126-
onChange(opt.value);
127-
(e.currentTarget.closest(".dropdown") as HTMLElement)?.blur();
128-
}
129-
}}
137+
onClick={(event: React.MouseEvent<HTMLButtonElement>) =>
138+
handleOptionClick(event, option)
139+
}
130140
className={`hover:bg-base-200 ${
131-
isSelected(opt.value) ? "active bg-base-300" : ""
141+
isSelected(option.value) ? "active bg-base-300" : ""
132142
}`}
133143
>
134144
<div className="flex justify-between items-center">
135-
{opt.label}
136-
{isSelected(opt.value) && (
145+
{option.label}
146+
{isSelected(option.value) && (
137147
<span className="text-success text-xs ml-2"></span>
138148
)}
139149
</div>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
main {
22
max-width: 1440px;
3-
margin: 20px auto 60px;
3+
margin: 0 auto 60px;
44
}
55

66
@media (max-width: 1460px) {
77
main {
8-
margin: 20px 10px 60px;
8+
margin: 0 10px 60px;
99
}
1010
}

frontend/src/components/Layout/Layout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
2626
<input id="my-drawer" type="checkbox" className="drawer-toggle" />
2727
<div className="drawer-content">
2828
<Header />
29-
<Breadcrumbs breadcrumbs={breadcrumbs} className="py-4" />
30-
<main>{children}</main>
29+
<main className="flex flex-col gap-4">
30+
<Breadcrumbs breadcrumbs={breadcrumbs} />
31+
{children}
32+
</main>
3133
</div>
3234
<Sidebar isOpen={isSidebarOpen} />
3335
</div>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const RoundedSkeleton = () => (
2+
<div className="skeleton rounded-full w-8 h-8 shrink-0" />
3+
);

frontend/src/components/SaveButton/SaveButton.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
import React from "react";
2-
3-
interface SaveButtonProps {
1+
type Props = {
42
isChanged: boolean;
53
onSave: () => void;
6-
}
4+
isLoading: boolean;
5+
};
76

8-
const SaveButton: React.FC<SaveButtonProps> = ({ isChanged, onSave }) => {
7+
const SaveButton = ({ isChanged, onSave, isLoading }: Props) => {
98
return (
109
<button
1110
onClick={onSave}
1211
className={`btn btn-info px-4 py-2`}
1312
disabled={!isChanged}
1413
>
15-
Сохранить
14+
{isLoading ? (
15+
<span className="loading loading-spinner"></span>
16+
) : (
17+
"Сохранить"
18+
)}
1619
</button>
1720
);
1821
};

0 commit comments

Comments
 (0)