Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
da2d00f
feat(fin-022): add admin sidebar, role guard and lookups tables
IeuanZorander Apr 26, 2026
a0235e8
feat(table): add reusable table components for UI
dmytroreshetylo Apr 25, 2026
deeb9c4
feat(table): add reusable table components for UI
IeuanZorander Apr 24, 2026
9a89f59
fix(fin-022): update user.orm.ts and fix admin folders
IeuanZorander Apr 26, 2026
ad5e093
fix(fin-022): new interface
IeuanZorander Apr 26, 2026
2fd3af0
Merge branch 'refs/heads/main' into feature/fin-022-parts/sidebar-and…
IeuanZorander Apr 26, 2026
9cdc386
refactor(fin-022): add admin sidebar, role guard and lookups tables UI
IeuanZorander Apr 26, 2026
8708515
fix(fin-022): apply code review fixes for accessibility and code quality
IeuanZorander Apr 26, 2026
6c94e6f
Merge branch 'feature/fin-022-completed' into feature/fin-022-parts/s…
dmytroreshetylo Apr 26, 2026
caf7b50
fix(fin-022): apply code review fixes
IeuanZorander Apr 26, 2026
bd74117
Merge remote-tracking branch 'origin/feature/fin-022-parts/sidebar-an…
IeuanZorander Apr 26, 2026
2567604
feat(fin-022): integrate pagination components from fin-021
IeuanZorander Apr 26, 2026
0a3a60d
fix(fin-022): apply review fixes
IeuanZorander Apr 27, 2026
6bca472
feat(FIN-042): replace UiGraphic with LogoSvg component for consisten…
dmytroreshetylo Apr 24, 2026
ee807f8
feat(FIN-042): update SVG components to use camelCase attributes for …
dmytroreshetylo Apr 24, 2026
18e7254
feat(fin-022): add FinListScreenHandler component and associated props
IeuanZorander Apr 27, 2026
33a88b7
Merge remote-tracking branch 'origin/feature/fin-021-parts/form' into…
IeuanZorander Apr 27, 2026
46469ac
fix(fin-022): refine admin sidebar states and lookup UI
IeuanZorander Apr 27, 2026
1614aab
fix(fin-022): fix build
IeuanZorander Apr 27, 2026
917a8b0
Merge branch 'feature/fin-021-parts/cards' into feature/fin-022-parts…
IeuanZorander Apr 27, 2026
b227fd9
Merge branch 'main' into feature/fin-022-parts/sidebar-and-lookups
IeuanZorander Apr 27, 2026
d19c58b
fix(fin-021): resolve build errors (types, schemas, mappings, and sid…
IeuanZorander Apr 27, 2026
6ba045f
fix(fin-022): enhance admin sidebar with new UI components and lookup…
IeuanZorander Apr 27, 2026
f7bf76d
feat(sidebar): enhance admin sidebar with new header components and u…
dmytroreshetylo Apr 27, 2026
f42fe38
fix(fin-022): add admin sidebar navigation for countries and currenci…
IeuanZorander Apr 27, 2026
7d5afae
fix(fin-022): refactor admin sidebar to use new content component and…
IeuanZorander Apr 27, 2026
3909dc3
fix(role-guard): replace loading and error messages with FinLoader co…
IeuanZorander Apr 27, 2026
d3a09fd
feat(sidebar): update admin sidebar to display user name and enhance …
dmytroreshetylo Apr 27, 2026
8eff3a0
Merge branch 'refs/heads/main' into feature/fin-022-parts/sidebar-and…
IeuanZorander Apr 28, 2026
52e6b13
fix(fin-022): implement admin navigation structure with icons and ref…
IeuanZorander Apr 28, 2026
4840656
fix(fin-022): refactor admin sidebar to include user profile link and…
IeuanZorander Apr 28, 2026
a3064e9
feat(lookup): refactor countries and currencies lookups to use Lookup…
IeuanZorander Apr 28, 2026
d47150c
Merge branch 'feature/fin-022-completed' into feature/fin-022-parts/s…
IeuanZorander Apr 28, 2026
6d8fc9a
feat(lookup): update currency lookup headers to Ukrainian and improve…
IeuanZorander Apr 28, 2026
ce63771
fix(fin-022): deleted commits
IeuanZorander Apr 28, 2026
b72bb34
feat(lookup): translate headers in countries lookup and update refres…
IeuanZorander Apr 28, 2026
4113607
feat(lookup): enhance sidebar and lookup components with improved rou…
IeuanZorander Apr 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"build": "next build",
"start": "next start",
"database:migrate": "tsx src/server/database/migrate.ts",
"lint": "next lint && prettier --write 'src/**/*.scss' && stylelint 'src/**/*.scss' --fix",
"lint": "next lint && prettier --write \"src/**/*.scss\" && stylelint \"src/**/*.scss\" --fix",
"test": "vitest",
"database:init": "tsx src/server/database/init.ts -- --synchronize=true",
"docker:run": "docker-compose up -d",
Expand Down
5 changes: 5 additions & 0 deletions src/app/(admin)/admin/lookups/countries/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { CountriesLookup } from '@frontend/features/admin/countries-lookup/countries-lookup';

export default function CountriesPage() {
return <CountriesLookup />;
}
5 changes: 5 additions & 0 deletions src/app/(admin)/admin/lookups/currencies/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { CurrenciesLookup } from '@frontend/features/admin/currencies-lookup/currencies-lookup';

export default function CurrenciesPage() {
return <CurrenciesLookup />;
}
5 changes: 5 additions & 0 deletions src/app/(admin)/admin/lookups/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { redirect } from 'next/navigation';

export default function LookupsPage() {
redirect('/admin/lookups/countries');
}
20 changes: 20 additions & 0 deletions src/app/(admin)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { ChildrenComponentProps } from '@frontend/shared/models/component-with-chilren.model';
import { AuthGuard } from '@frontend/entities/profile/auth-guard';
import { RoleGuard } from '@frontend/entities/profile/role-guard';
import { AdminSidebar } from '@frontend/widgets/admin-sidebar/admin-sidebar';
import { AuthorizedUserProvider } from '@frontend/entities/profile/authorized-user.hook';

export default function AdminLayout({ children }: ChildrenComponentProps) {
return (
<AuthGuard>
<RoleGuard>
<AuthorizedUserProvider>
<div className="flex h-full w-full overflow-hidden">
<AdminSidebar />
<main className="flex-1 overflow-auto bg-background">{children}</main>
</div>
</AuthorizedUserProvider>
</RoleGuard>
</AuthGuard>
);
}
6 changes: 3 additions & 3 deletions src/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import { UiSpinner } from '@frontend/ui/ui-spinner/spinner';
import { FinControlledPassword } from '@frontend/components/controlled-fields/fin-controlled-password';
import { FinControlledInput } from '@frontend/components/controlled-fields/fin-controlled-input';
import { AuthTemplate } from '@frontend/entities/auth/auth-template';
import { useUserInformation } from '@frontend/shared/services/user-information/use-user-information.store';
import { LogoSvg } from '@frontend/shared/svg/logo-svg';
import { useUserInformation } from '@frontend/shared/services/user-information/use-user-information.store';

export default function LoginPage() {
const refreshUser = useUserInformation((state) => state.refresh);

const router = useRouter();
const { methods, submit, isLoading } = useSetupLogin(() => {
refreshUser();
const { methods, submit, isLoading } = useSetupLogin(async () => {
await refreshUser();
router.push('/profile');
});

Expand Down
2 changes: 1 addition & 1 deletion src/app/(auth)/login/shared/login-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { fetchClient } from '@frontend/shared/services/fetch-client/fetch-client
import { type LoginResponse } from '@common/domains/auth/models/responses/login.response';
import { type ApiResultOperation } from '@common/models/api-result-operation.model';

export function useSetupLogin(onSuccessAction: () => void) {
export function useSetupLogin(onSuccessAction: () => void | Promise<void>) {
const { mutate, isPending } = useSendDataFetch(
async (data: LoginDto) =>
await fetchClient.post<ApiResultOperation<LoginResponse>>('/api/auth/login', data, { skipAuth: true }),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { RegularIncomesExpensesProvider } from '@frontend/features/regular-incomes-expenses/card-creation-form/regular-transaction.hook';
import type { ChildrenComponentProps } from '@frontend/shared/models/component-with-chilren.model';
import { RegularIncomesExpensesProvider } from '@frontend/features/regular-incomes-expenses/card-creation-form/regular-transaction.hook';

export default function RegularOperationsLayout({ children }: ChildrenComponentProps) {
return <RegularIncomesExpensesProvider>{children}</RegularIncomesExpensesProvider>;
Comment thread
dmytroreshetylo marked this conversation as resolved.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { type ReactNode } from 'react';

export interface LookupColumnDef<T> {
header: string;
cell: (item: T) => ReactNode;
headerClassName?: string;
cellClassName?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { UiGraphic } from '@frontend/ui/ui-graphic/ui-graphic';
import { type LookupCreatedBy } from './lookup-created-by.model';

interface LookupCreatedByCellProps {
createdBy: LookupCreatedBy;
}

export function LookupCreatedByCell({ createdBy }: LookupCreatedByCellProps) {
if (!createdBy.name) {
return <span className="text-sm text-muted-foreground">—</span>;
}

return (
<div className="flex items-center gap-2">
{createdBy.avatar ? (
<UiGraphic
src={createdBy.avatar}
alt={createdBy.name}
size={20}
className="rounded-full"
/>
) : (
<span className="inline-flex h-5 w-5 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground">
{createdBy.name.charAt(0).toUpperCase()}
</span>
)}
<span className="text-sm text-muted-foreground">{createdBy.name}</span>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type LookupCreatedByFields = {
createdByName?: string | null;
createdByAvatar?: string | null;
createdBy?: { name?: string | null; avatar?: string | null } | string | null;
userName?: string | null;
userAvatar?: string | null;
};

export type LookupCreatedBy = {
name: string | undefined;
avatar: string | undefined;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { type LookupCreatedByFields, type LookupCreatedBy } from './lookup-created-by.model';

export function getCreatedBy<T>(item: T): LookupCreatedBy {
const source = item as T & LookupCreatedByFields;

const nameFromObject = typeof source.createdBy === 'object' && source.createdBy ? source.createdBy.name : undefined;
const avatarFromObject =
typeof source.createdBy === 'object' && source.createdBy ? source.createdBy.avatar : undefined;

const name = source.createdByName ?? source.userName ?? nameFromObject ?? undefined;
const avatar = source.createdByAvatar ?? source.userAvatar ?? avatarFromObject ?? undefined;

return { name, avatar };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Link from 'next/link';

interface LookupPageHeaderProps {
title: string;
hasSelection: boolean;
onAdd?: () => void;
onDelete?: () => void;
}

export function LookupPageHeader({ title, hasSelection, onAdd, onDelete }: LookupPageHeaderProps) {
return (
<div className="flex items-center justify-between border-b border-border/70 bg-card px-4 py-2.5">
<nav aria-label="Breadcrumb">
<ol className="flex items-center gap-2 text-sm text-muted-foreground">
<li>
<Link
href="/admin/lookups"
className="transition-colors hover:text-foreground"
>
Lookups
</Link>
</li>
<li aria-hidden="true">/</li>
<li className="font-semibold text-foreground">{title}</li>
</ol>
</nav>

<div className="flex items-center gap-1.5">
{hasSelection && (
<button
type="button"
aria-label="Delete selected"
onClick={onDelete}
className="flex h-8 w-8 items-center justify-center rounded-full bg-destructive-foreground text-primary-foreground transition-colors hover:bg-destructive/90"
>
<svg
aria-hidden="true"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<polyline points="3 6 5 6 21 6" />
<path d="M19 6l-1 14H6L5 6" />
<path d="M10 11v6M14 11v6" />
<path d="M9 6V4h6v2" />
</svg>
</button>
)}
<button
type="button"
aria-label="Add new"
onClick={onAdd}
className="flex h-8 w-8 items-center justify-center rounded-full bg-primary text-primary-foreground transition-colors hover:bg-primary/90"
>
<svg
aria-hidden="true"
width="15"
height="15"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.25"
>
<line
x1="12"
y1="5"
x2="12"
y2="19"
/>
<line
x1="5"
y1="12"
x2="19"
y2="12"
/>
</svg>
</button>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { UiPopover } from '@frontend/shared/ui/ui-popover/ui-popover';
import { UiPopoverTrigger } from '@frontend/shared/ui/ui-popover/ui-popover-trigger';
import { UiPopoverContent } from '@frontend/shared/ui/ui-popover/ui-popover-content';
import { UiButton } from '@frontend/shared/ui/ui-button/ui-button';
import { UiSvgIcon } from '@frontend/shared/ui/ui-svg-icon/ui-svg-icon';
import { UiConfirmModal } from '@frontend/shared/components/confirm-modal/fin-confirm-modal';

interface LookupRowActionsProps {
onEdit?: () => void;
onDelete?: () => void;
}

export function LookupRowActions({ onEdit, onDelete }: LookupRowActionsProps) {
return (
<UiPopover>
<UiPopoverTrigger asChild>
<UiButton
aria-label="Row actions"
aria-haspopup="menu"
variant="default"
size="sm"
bgNone
>
<UiSvgIcon
name="three-dots-vertical"
size="sm"
/>
</UiButton>
</UiPopoverTrigger>

<UiPopoverContent
align="end"
className="!w-36 p-1"
>
<UiButton
variant="default"
size="sm"
bgNone
className="w-full justify-start gap-2"
onClick={onEdit}
>
<UiSvgIcon
name="pencil"
size="sm"
/>
Редагувати
</UiButton>

<UiConfirmModal
trigger={
<UiButton
variant="destructive"
size="sm"
opacity
className="w-full justify-start gap-2"
>
<UiSvgIcon
name="trash"
size="sm"
/>
Видалити
</UiButton>
}
onConfirm={onDelete ?? (() => {})}
title="Видалити запис?"
description="Цю дію неможливо скасувати."
confirmLabel="Видалити"
/>
</UiPopoverContent>
</UiPopover>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { cn } from '@frontend/shared/utils/cn.util';

interface LookupStatusBadgeProps {
softDeleted: 0 | 1 | undefined;
}

export function LookupStatusBadge({ softDeleted }: LookupStatusBadgeProps) {
const isDeleted = softDeleted === 1;

return (
<span
className={cn(
'inline-flex items-center gap-2 text-sm font-medium',
isDeleted ? 'text-destructive' : 'text-foreground',
)}
>
<span
aria-hidden="true"
className={cn('inline-flex h-2.5 w-2.5 rounded-full', isDeleted ? 'bg-destructive' : 'bg-success')}
/>
{isDeleted ? 'Deleted' : 'Active'}
</span>
);
}
50 changes: 50 additions & 0 deletions src/client/entities/lookups/lookup-table/lookup-row-skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { UiTableCell } from '@frontend/shared/ui/ui-table/ui-table-cell';
import { UiTableRow } from '@frontend/shared/ui/ui-table/ui-table-row';
import { UiSkeleton } from '@frontend/ui/ui-skeleton/ui-skeleton';

interface LookupRowSkeletonProps {
columnWidths: string[];
}

export function LookupRowSkeleton({ columnWidths }: LookupRowSkeletonProps) {
return (
<UiTableRow>
<UiTableCell className="w-10 py-2 pl-4">
<UiSkeleton className="h-4 w-4" />
</UiTableCell>

<UiTableCell className="w-16 py-2">
<UiSkeleton className="h-4 w-12" />
</UiTableCell>

{columnWidths.map((width, index) => (
<UiTableCell
key={index}
className="py-2"
>
<UiSkeleton className={`h-4 ${width}`} />
</UiTableCell>
))}

<UiTableCell className="py-2">
<UiSkeleton className="h-4 w-16" />
</UiTableCell>

<UiTableCell className="py-2">
<UiSkeleton className="h-4 w-28" />
</UiTableCell>

<UiTableCell className="py-2">
<UiSkeleton className="h-8 w-8 rounded-full" />
</UiTableCell>

<UiTableCell className="py-2">
<UiSkeleton className="h-4 w-28" />
</UiTableCell>

<UiTableCell className="w-10 py-2">
<UiSkeleton className="h-8 w-8" />
</UiTableCell>
</UiTableRow>
);
}
Loading