fix bus and translate to eus
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
**Этап:** Фаза 1 (Frontend) завершена
|
**Этап:** Фаза 1 (Frontend) завершена
|
||||||
**Фаза MVP:** Готов к тестированию базового функционала
|
**Фаза MVP:** Готов к тестированию базового функционала
|
||||||
**Последнее обновление:** 2025-12-29
|
**Последнее обновление:** 2025-12-31
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -33,6 +33,8 @@
|
|||||||
| 2025-12-29 | **Фаза 1:** Frontend — Модалка создания идеи |
|
| 2025-12-29 | **Фаза 1:** Frontend — Модалка создания идеи |
|
||||||
| 2025-12-29 | **Фаза 1:** Frontend — Skeleton loader и empty state |
|
| 2025-12-29 | **Фаза 1:** Frontend — Skeleton loader и empty state |
|
||||||
| 2025-12-29 | **Фаза 1:** Frontend — Удаление идей |
|
| 2025-12-29 | **Фаза 1:** Frontend — Удаление идей |
|
||||||
|
| 2025-12-31 | Исправлен баг: Select в inline-редактировании закрывался при клике (MenuProps.disablePortal) |
|
||||||
|
| 2025-12-31 | Локализация интерфейса на русский язык |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ function App() {
|
|||||||
Team Planner
|
Team Planner
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" color="text.secondary">
|
<Typography variant="body1" color="text.secondary">
|
||||||
Backlog management for your team
|
Управление бэклогом идей команды
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<Button
|
||||||
@ -31,7 +31,7 @@ function App() {
|
|||||||
startIcon={<Add />}
|
startIcon={<Add />}
|
||||||
onClick={() => setCreateModalOpen(true)}
|
onClick={() => setCreateModalOpen(true)}
|
||||||
>
|
>
|
||||||
New Idea
|
Новая идея
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@ -18,17 +18,17 @@ import { useCreateIdea } from '../../hooks/useIdeas';
|
|||||||
import type { CreateIdeaDto, IdeaStatus, IdeaPriority } from '../../types/idea';
|
import type { CreateIdeaDto, IdeaStatus, IdeaPriority } from '../../types/idea';
|
||||||
|
|
||||||
const statusOptions: { value: IdeaStatus; label: string }[] = [
|
const statusOptions: { value: IdeaStatus; label: string }[] = [
|
||||||
{ value: 'backlog', label: 'Backlog' },
|
{ value: 'backlog', label: 'Бэклог' },
|
||||||
{ value: 'todo', label: 'To Do' },
|
{ value: 'todo', label: 'К выполнению' },
|
||||||
{ value: 'in_progress', label: 'In Progress' },
|
{ value: 'in_progress', label: 'В работе' },
|
||||||
{ value: 'done', label: 'Done' },
|
{ value: 'done', label: 'Готово' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const priorityOptions: { value: IdeaPriority; label: string }[] = [
|
const priorityOptions: { value: IdeaPriority; label: string }[] = [
|
||||||
{ value: 'low', label: 'Low' },
|
{ value: 'low', label: 'Низкий' },
|
||||||
{ value: 'medium', label: 'Medium' },
|
{ value: 'medium', label: 'Средний' },
|
||||||
{ value: 'high', label: 'High' },
|
{ value: 'high', label: 'Высокий' },
|
||||||
{ value: 'critical', label: 'Critical' },
|
{ value: 'critical', label: 'Критичный' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const initialFormData: CreateIdeaDto = {
|
const initialFormData: CreateIdeaDto = {
|
||||||
@ -76,17 +76,17 @@ export function CreateIdeaModal() {
|
|||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<DialogTitle>Create New Idea</DialogTitle>
|
<DialogTitle>Новая идея</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}>
|
||||||
{createIdea.isError && (
|
{createIdea.isError && (
|
||||||
<Alert severity="error">
|
<Alert severity="error">
|
||||||
Failed to create idea. Please try again.
|
Не удалось создать идею. Попробуйте снова.
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Title"
|
label="Название"
|
||||||
value={formData.title}
|
value={formData.title}
|
||||||
onChange={(e) => handleChange('title', e.target.value)}
|
onChange={(e) => handleChange('title', e.target.value)}
|
||||||
required
|
required
|
||||||
@ -94,7 +94,7 @@ export function CreateIdeaModal() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Description"
|
label="Описание"
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => handleChange('description', e.target.value)}
|
onChange={(e) => handleChange('description', e.target.value)}
|
||||||
multiline
|
multiline
|
||||||
@ -103,10 +103,10 @@ export function CreateIdeaModal() {
|
|||||||
|
|
||||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Status</InputLabel>
|
<InputLabel>Статус</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={formData.status}
|
value={formData.status}
|
||||||
label="Status"
|
label="Статус"
|
||||||
onChange={(e) => handleChange('status', e.target.value)}
|
onChange={(e) => handleChange('status', e.target.value)}
|
||||||
>
|
>
|
||||||
{statusOptions.map((opt) => (
|
{statusOptions.map((opt) => (
|
||||||
@ -118,10 +118,10 @@ export function CreateIdeaModal() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Priority</InputLabel>
|
<InputLabel>Приоритет</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={formData.priority}
|
value={formData.priority}
|
||||||
label="Priority"
|
label="Приоритет"
|
||||||
onChange={(e) => handleChange('priority', e.target.value)}
|
onChange={(e) => handleChange('priority', e.target.value)}
|
||||||
>
|
>
|
||||||
{priorityOptions.map((opt) => (
|
{priorityOptions.map((opt) => (
|
||||||
@ -134,57 +134,57 @@ export function CreateIdeaModal() {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Module"
|
label="Модуль"
|
||||||
value={formData.module}
|
value={formData.module}
|
||||||
onChange={(e) => handleChange('module', e.target.value)}
|
onChange={(e) => handleChange('module', e.target.value)}
|
||||||
placeholder="e.g., Auth, Dashboard, API"
|
placeholder="например: Авторизация, Дашборд, API"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Target Audience"
|
label="Целевая аудитория"
|
||||||
value={formData.targetAudience}
|
value={formData.targetAudience}
|
||||||
onChange={(e) => handleChange('targetAudience', e.target.value)}
|
onChange={(e) => handleChange('targetAudience', e.target.value)}
|
||||||
placeholder="Who is this for?"
|
placeholder="Для кого это?"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Pain Point"
|
label="Боль"
|
||||||
value={formData.pain}
|
value={formData.pain}
|
||||||
onChange={(e) => handleChange('pain', e.target.value)}
|
onChange={(e) => handleChange('pain', e.target.value)}
|
||||||
multiline
|
multiline
|
||||||
rows={2}
|
rows={2}
|
||||||
placeholder="What problem does this solve?"
|
placeholder="Какую проблему это решает?"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="AI Role"
|
label="Роль AI"
|
||||||
value={formData.aiRole}
|
value={formData.aiRole}
|
||||||
onChange={(e) => handleChange('aiRole', e.target.value)}
|
onChange={(e) => handleChange('aiRole', e.target.value)}
|
||||||
multiline
|
multiline
|
||||||
rows={2}
|
rows={2}
|
||||||
placeholder="How can AI help with this?"
|
placeholder="Как AI может помочь с этим?"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Verification Method"
|
label="Способ проверки"
|
||||||
value={formData.verificationMethod}
|
value={formData.verificationMethod}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleChange('verificationMethod', e.target.value)
|
handleChange('verificationMethod', e.target.value)
|
||||||
}
|
}
|
||||||
multiline
|
multiline
|
||||||
rows={2}
|
rows={2}
|
||||||
placeholder="How to verify this is done?"
|
placeholder="Как проверить, что это готово?"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose}>Cancel</Button>
|
<Button onClick={handleClose}>Отмена</Button>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={!formData.title || createIdea.isPending}
|
disabled={!formData.title || createIdea.isPending}
|
||||||
>
|
>
|
||||||
{createIdea.isPending ? 'Creating...' : 'Create'}
|
{createIdea.isPending ? 'Создание...' : 'Создать'}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -15,18 +15,18 @@ import { useModulesQuery } from '../../hooks/useIdeas';
|
|||||||
import type { IdeaStatus, IdeaPriority } from '../../types/idea';
|
import type { IdeaStatus, IdeaPriority } from '../../types/idea';
|
||||||
|
|
||||||
const statusOptions: { value: IdeaStatus; label: string }[] = [
|
const statusOptions: { value: IdeaStatus; label: string }[] = [
|
||||||
{ value: 'backlog', label: 'Backlog' },
|
{ value: 'backlog', label: 'Бэклог' },
|
||||||
{ value: 'todo', label: 'To Do' },
|
{ value: 'todo', label: 'К выполнению' },
|
||||||
{ value: 'in_progress', label: 'In Progress' },
|
{ value: 'in_progress', label: 'В работе' },
|
||||||
{ value: 'done', label: 'Done' },
|
{ value: 'done', label: 'Готово' },
|
||||||
{ value: 'cancelled', label: 'Cancelled' },
|
{ value: 'cancelled', label: 'Отменено' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const priorityOptions: { value: IdeaPriority; label: string }[] = [
|
const priorityOptions: { value: IdeaPriority; label: string }[] = [
|
||||||
{ value: 'low', label: 'Low' },
|
{ value: 'low', label: 'Низкий' },
|
||||||
{ value: 'medium', label: 'Medium' },
|
{ value: 'medium', label: 'Средний' },
|
||||||
{ value: 'high', label: 'High' },
|
{ value: 'high', label: 'Высокий' },
|
||||||
{ value: 'critical', label: 'Critical' },
|
{ value: 'critical', label: 'Критичный' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function IdeasFilters() {
|
export function IdeasFilters() {
|
||||||
@ -52,7 +52,7 @@ export function IdeasFilters() {
|
|||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
size="small"
|
size="small"
|
||||||
placeholder="Search ideas..."
|
placeholder="Поиск идей..."
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
onChange={(e) => setSearchValue(e.target.value)}
|
onChange={(e) => setSearchValue(e.target.value)}
|
||||||
sx={{ minWidth: 200 }}
|
sx={{ minWidth: 200 }}
|
||||||
@ -68,16 +68,16 @@ export function IdeasFilters() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
<FormControl size="small" sx={{ minWidth: 120 }}>
|
||||||
<InputLabel>Status</InputLabel>
|
<InputLabel>Статус</InputLabel>
|
||||||
<Select<IdeaStatus | ''>
|
<Select<IdeaStatus | ''>
|
||||||
value={filters.status ?? ''}
|
value={filters.status ?? ''}
|
||||||
label="Status"
|
label="Статус"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const val = e.target.value;
|
const val = e.target.value;
|
||||||
setFilter('status', val === '' ? undefined : val);
|
setFilter('status', val === '' ? undefined : val);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value="">All</MenuItem>
|
<MenuItem value="">Все</MenuItem>
|
||||||
{statusOptions.map((opt) => (
|
{statusOptions.map((opt) => (
|
||||||
<MenuItem key={opt.value} value={opt.value}>
|
<MenuItem key={opt.value} value={opt.value}>
|
||||||
{opt.label}
|
{opt.label}
|
||||||
@ -87,16 +87,16 @@ export function IdeasFilters() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
<FormControl size="small" sx={{ minWidth: 120 }}>
|
||||||
<InputLabel>Priority</InputLabel>
|
<InputLabel>Приоритет</InputLabel>
|
||||||
<Select<IdeaPriority | ''>
|
<Select<IdeaPriority | ''>
|
||||||
value={filters.priority ?? ''}
|
value={filters.priority ?? ''}
|
||||||
label="Priority"
|
label="Приоритет"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const val = e.target.value;
|
const val = e.target.value;
|
||||||
setFilter('priority', val === '' ? undefined : val);
|
setFilter('priority', val === '' ? undefined : val);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value="">All</MenuItem>
|
<MenuItem value="">Все</MenuItem>
|
||||||
{priorityOptions.map((opt) => (
|
{priorityOptions.map((opt) => (
|
||||||
<MenuItem key={opt.value} value={opt.value}>
|
<MenuItem key={opt.value} value={opt.value}>
|
||||||
{opt.label}
|
{opt.label}
|
||||||
@ -106,13 +106,13 @@ export function IdeasFilters() {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<FormControl size="small" sx={{ minWidth: 120 }}>
|
<FormControl size="small" sx={{ minWidth: 120 }}>
|
||||||
<InputLabel>Module</InputLabel>
|
<InputLabel>Модуль</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={filters.module ?? ''}
|
value={filters.module ?? ''}
|
||||||
label="Module"
|
label="Модуль"
|
||||||
onChange={(e) => setFilter('module', e.target.value || undefined)}
|
onChange={(e) => setFilter('module', e.target.value || undefined)}
|
||||||
>
|
>
|
||||||
<MenuItem value="">All</MenuItem>
|
<MenuItem value="">Все</MenuItem>
|
||||||
{modules.map((module) => (
|
{modules.map((module) => (
|
||||||
<MenuItem key={module} value={module}>
|
<MenuItem key={module} value={module}>
|
||||||
{module}
|
{module}
|
||||||
@ -130,7 +130,7 @@ export function IdeasFilters() {
|
|||||||
setSearchValue('');
|
setSearchValue('');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Clear
|
Сбросить
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -82,6 +82,7 @@ export function EditableCell({
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
autoFocus
|
autoFocus
|
||||||
sx={{ minWidth: 100 }}
|
sx={{ minWidth: 100 }}
|
||||||
|
MenuProps={{ disablePortal: true }}
|
||||||
>
|
>
|
||||||
{options.map((opt) => (
|
{options.map((opt) => (
|
||||||
<MenuItem key={opt.value} value={opt.value}>
|
<MenuItem key={opt.value} value={opt.value}>
|
||||||
|
|||||||
@ -62,7 +62,7 @@ export function IdeasTable() {
|
|||||||
if (isError) {
|
if (isError) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 4, textAlign: 'center' }}>
|
<Box sx={{ p: 4, textAlign: 'center' }}>
|
||||||
<Typography color="error">Failed to load ideas</Typography>
|
<Typography color="error">Не удалось загрузить идеи</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -137,9 +137,9 @@ export function IdeasTable() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Inbox sx={{ fontSize: 48, mb: 2, opacity: 0.5 }} />
|
<Inbox sx={{ fontSize: 48, mb: 2, opacity: 0.5 }} />
|
||||||
<Typography variant="h6">No ideas yet</Typography>
|
<Typography variant="h6">Идей пока нет</Typography>
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
Create your first idea to get started
|
Создайте первую идею, чтобы начать
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const priorityColors: Record<
|
|||||||
|
|
||||||
export const createColumns = (onDelete: (id: string) => void) => [
|
export const createColumns = (onDelete: (id: string) => void) => [
|
||||||
columnHelper.accessor('title', {
|
columnHelper.accessor('title', {
|
||||||
header: 'Title',
|
header: 'Название',
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
idea={info.row.original}
|
idea={info.row.original}
|
||||||
@ -44,7 +44,7 @@ export const createColumns = (onDelete: (id: string) => void) => [
|
|||||||
size: 250,
|
size: 250,
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('status', {
|
columnHelper.accessor('status', {
|
||||||
header: 'Status',
|
header: 'Статус',
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const status = info.getValue();
|
const status = info.getValue();
|
||||||
const label =
|
const label =
|
||||||
@ -65,7 +65,7 @@ export const createColumns = (onDelete: (id: string) => void) => [
|
|||||||
size: 140,
|
size: 140,
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('priority', {
|
columnHelper.accessor('priority', {
|
||||||
header: 'Priority',
|
header: 'Приоритет',
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const priority = info.getValue();
|
const priority = info.getValue();
|
||||||
const label =
|
const label =
|
||||||
@ -91,7 +91,7 @@ export const createColumns = (onDelete: (id: string) => void) => [
|
|||||||
size: 120,
|
size: 120,
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('module', {
|
columnHelper.accessor('module', {
|
||||||
header: 'Module',
|
header: 'Модуль',
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
idea={info.row.original}
|
idea={info.row.original}
|
||||||
@ -103,7 +103,7 @@ export const createColumns = (onDelete: (id: string) => void) => [
|
|||||||
size: 120,
|
size: 120,
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('targetAudience', {
|
columnHelper.accessor('targetAudience', {
|
||||||
header: 'Target Audience',
|
header: 'Целевая аудитория',
|
||||||
cell: (info) => (
|
cell: (info) => (
|
||||||
<EditableCell
|
<EditableCell
|
||||||
idea={info.row.original}
|
idea={info.row.original}
|
||||||
@ -115,7 +115,7 @@ export const createColumns = (onDelete: (id: string) => void) => [
|
|||||||
size: 150,
|
size: 150,
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('description', {
|
columnHelper.accessor('description', {
|
||||||
header: 'Description',
|
header: 'Описание',
|
||||||
cell: (info) => {
|
cell: (info) => {
|
||||||
const value = info.getValue();
|
const value = info.getValue();
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import type { IdeaStatus, IdeaPriority } from '../../types/idea';
|
import type { IdeaStatus, IdeaPriority } from '../../types/idea';
|
||||||
|
|
||||||
export const statusOptions: { value: IdeaStatus; label: string }[] = [
|
export const statusOptions: { value: IdeaStatus; label: string }[] = [
|
||||||
{ value: 'backlog', label: 'Backlog' },
|
{ value: 'backlog', label: 'Бэклог' },
|
||||||
{ value: 'todo', label: 'To Do' },
|
{ value: 'todo', label: 'К выполнению' },
|
||||||
{ value: 'in_progress', label: 'In Progress' },
|
{ value: 'in_progress', label: 'В работе' },
|
||||||
{ value: 'done', label: 'Done' },
|
{ value: 'done', label: 'Готово' },
|
||||||
{ value: 'cancelled', label: 'Cancelled' },
|
{ value: 'cancelled', label: 'Отменено' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const priorityOptions: { value: IdeaPriority; label: string }[] = [
|
export const priorityOptions: { value: IdeaPriority; label: string }[] = [
|
||||||
{ value: 'low', label: 'Low' },
|
{ value: 'low', label: 'Низкий' },
|
||||||
{ value: 'medium', label: 'Medium' },
|
{ value: 'medium', label: 'Средний' },
|
||||||
{ value: 'high', label: 'High' },
|
{ value: 'high', label: 'Высокий' },
|
||||||
{ value: 'critical', label: 'Critical' },
|
{ value: 'critical', label: 'Критичный' },
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user