feat: немного поменял маппинг, сделал отображение нормальное без View модели с фио, для кладовщика основные формы и создание готово, осталось редактирования
This commit is contained in:
@@ -4,14 +4,9 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
namespace BankDatabase;
|
namespace BankDatabase;
|
||||||
|
|
||||||
public class BankDbContext : DbContext
|
internal class BankDbContext(IConfigurationDatabase configurationDatabase) : DbContext
|
||||||
{
|
{
|
||||||
private readonly IConfigurationDatabase? _configurationDatabase;
|
private readonly IConfigurationDatabase? _configurationDatabase = configurationDatabase;
|
||||||
|
|
||||||
public BankDbContext(IConfigurationDatabase configurationDatabase)
|
|
||||||
{
|
|
||||||
_configurationDatabase = configurationDatabase;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,9 +21,17 @@ public class PeriodAdapter : IPeriodAdapter
|
|||||||
{
|
{
|
||||||
_periodBusinessLogicContract = periodBusinessLogicContract;
|
_periodBusinessLogicContract = periodBusinessLogicContract;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
var config = new MapperConfiguration(cfg =>
|
var config = new MapperConfiguration(cfg =>
|
||||||
{
|
{
|
||||||
cfg.CreateMap<PeriodBindingModel, PeriodDataModel>();
|
cfg.CreateMap<PeriodBindingModel, PeriodDataModel>()
|
||||||
|
.ConstructUsing(src => new PeriodDataModel(
|
||||||
|
src.Id,
|
||||||
|
src.StartTime,
|
||||||
|
src.EndTime,
|
||||||
|
src.StorekeeperId
|
||||||
|
));
|
||||||
|
|
||||||
|
// Маппинг PeriodDataModel -> PeriodViewModel
|
||||||
cfg.CreateMap<PeriodDataModel, PeriodViewModel>();
|
cfg.CreateMap<PeriodDataModel, PeriodViewModel>();
|
||||||
});
|
});
|
||||||
_mapper = new Mapper(config);
|
_mapper = new Mapper(config);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using BankContracts.AdapterContracts;
|
using BankContracts.AdapterContracts;
|
||||||
using BankContracts.BindingModels;
|
using BankContracts.BindingModels;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace BankWebApi.Controllers;
|
namespace BankWebApi.Controllers;
|
||||||
@@ -10,7 +9,7 @@ namespace BankWebApi.Controllers;
|
|||||||
[Route("api/[controller]/[action]")]
|
[Route("api/[controller]/[action]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
public class PeriodController(IPeriodAdapter adapter) : ControllerBase
|
public class PeriodsController(IPeriodAdapter adapter) : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IPeriodAdapter _adapter = adapter;
|
private readonly IPeriodAdapter _adapter = adapter;
|
||||||
|
|
||||||
@@ -68,7 +68,13 @@ public class StorekeepersController(IStorekeeperAdapter adapter) : ControllerBas
|
|||||||
{
|
{
|
||||||
var res = _adapter.Login(model, out string token);
|
var res = _adapter.Login(model, out string token);
|
||||||
|
|
||||||
Response.Cookies.Append(AuthOptions.CookieName, token);
|
Response.Cookies.Append(AuthOptions.CookieName, token, new CookieOptions
|
||||||
|
{
|
||||||
|
HttpOnly = true,
|
||||||
|
SameSite = SameSiteMode.None,
|
||||||
|
Secure = true,
|
||||||
|
Expires = DateTime.UtcNow.AddDays(2)
|
||||||
|
});
|
||||||
|
|
||||||
return res.GetResponse(Request, Response);
|
return res.GetResponse(Request, Response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ app.UseHttpsRedirection();
|
|||||||
app.UseCookiePolicy(new CookiePolicyOptions
|
app.UseCookiePolicy(new CookiePolicyOptions
|
||||||
{
|
{
|
||||||
HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always,
|
HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always,
|
||||||
Secure = CookieSecurePolicy.Always
|
Secure = app.Environment.IsProduction() ? CookieSecurePolicy.Always : CookieSecurePolicy.None
|
||||||
});
|
});
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
VITE_API_URL=https://localhost:7204
|
VITE_API_URL=https://localhost:7204
|
||||||
|
# VITE_API_URL=http://localhost:5189
|
||||||
Binary file not shown.
@@ -17,6 +17,7 @@
|
|||||||
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
||||||
"@radix-ui/react-label": "^2.1.6",
|
"@radix-ui/react-label": "^2.1.6",
|
||||||
"@radix-ui/react-menubar": "^1.1.14",
|
"@radix-ui/react-menubar": "^1.1.14",
|
||||||
|
"@radix-ui/react-popover": "^1.1.13",
|
||||||
"@radix-ui/react-select": "^2.2.4",
|
"@radix-ui/react-select": "^2.2.4",
|
||||||
"@radix-ui/react-separator": "^1.1.6",
|
"@radix-ui/react-separator": "^1.1.6",
|
||||||
"@radix-ui/react-slot": "^1.2.2",
|
"@radix-ui/react-slot": "^1.2.2",
|
||||||
@@ -26,9 +27,11 @@
|
|||||||
"@tanstack/react-query": "^5.76.1",
|
"@tanstack/react-query": "^5.76.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"lucide-react": "^0.511.0",
|
"lucide-react": "^0.511.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
|
"react-day-picker": "8.10.1",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-hook-form": "^7.56.4",
|
"react-hook-form": "^7.56.4",
|
||||||
"react-router-dom": "^7.6.0",
|
"react-router-dom": "^7.6.0",
|
||||||
|
|||||||
@@ -77,15 +77,15 @@ export const depositsApi = {
|
|||||||
|
|
||||||
// Periods API
|
// Periods API
|
||||||
export const periodsApi = {
|
export const periodsApi = {
|
||||||
getAll: () => getData<PeriodBindingModel>('api/Period/GetAllRecords'),
|
getAll: () => getData<PeriodBindingModel>('api/Periods/GetAllRecords'),
|
||||||
getById: (id: string) =>
|
getById: (id: string) =>
|
||||||
getData<PeriodBindingModel>(`api/Period/GetRecord/${id}`),
|
getData<PeriodBindingModel>(`api/Periods/GetRecord/${id}`),
|
||||||
getByStorekeeper: (storekeeperId: string) =>
|
getByStorekeeper: (storekeeperId: string) =>
|
||||||
getData<PeriodBindingModel>(
|
getData<PeriodBindingModel>(
|
||||||
`api/Period/GetRecordByStorekeeper/${storekeeperId}`,
|
`api/Period/GetRecordByStorekeeper/${storekeeperId}`,
|
||||||
),
|
),
|
||||||
create: (data: PeriodBindingModel) => postData('api/Period/Register', data),
|
create: (data: PeriodBindingModel) => postData('api/Periods/Register', data),
|
||||||
update: (data: PeriodBindingModel) => putData('api/Period/ChangeInfo', data),
|
update: (data: PeriodBindingModel) => putData('api/Periods/ChangeInfo', data),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Replenishments API
|
// Replenishments API
|
||||||
@@ -110,8 +110,7 @@ export const replenishmentsApi = {
|
|||||||
|
|
||||||
// Storekeepers API
|
// Storekeepers API
|
||||||
export const storekeepersApi = {
|
export const storekeepersApi = {
|
||||||
getAll: () =>
|
getAll: () => getData<StorekeeperBindingModel>('api/storekeepers'),
|
||||||
getData<StorekeeperBindingModel>('api/Storekeepers/GetAllRecords'),
|
|
||||||
getById: (id: string) =>
|
getById: (id: string) =>
|
||||||
getData<StorekeeperBindingModel>(`api/Storekeepers/GetRecord/${id}`),
|
getData<StorekeeperBindingModel>(`api/Storekeepers/GetRecord/${id}`),
|
||||||
create: (data: StorekeeperBindingModel) =>
|
create: (data: StorekeeperBindingModel) =>
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ const API_URL = ConfigManager.loadUrl();
|
|||||||
|
|
||||||
export async function getData<T>(path: string): Promise<T[]> {
|
export async function getData<T>(path: string): Promise<T[]> {
|
||||||
const res = await fetch(`${API_URL}/${path}`, {
|
const res = await fetch(`${API_URL}/${path}`, {
|
||||||
headers: {
|
|
||||||
mode: 'no-cors',
|
|
||||||
},
|
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@@ -21,7 +18,7 @@ export async function postData<T>(path: string, data: T) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
mode: 'no-cors',
|
// mode: 'no-cors',
|
||||||
},
|
},
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -36,7 +33,7 @@ export async function putData<T>(path: string, data: T) {
|
|||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
mode: 'no-cors',
|
// mode: 'no-cors',
|
||||||
},
|
},
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
|
|||||||
165
TheBank/bankui/src/components/features/CurrencyForm.tsx
Normal file
165
TheBank/bankui/src/components/features/CurrencyForm.tsx
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/components/ui/form';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import type { CurrencyBindingModel } from '@/types/types';
|
||||||
|
import { useStorekeepers } from '@/hooks/useStorekeepers'; // Импорт хука для кладовщиков
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
name: z.string().min(1, 'Укажите название валюты'),
|
||||||
|
abbreviation: z.string().min(1, 'Укажите аббревиатуру'),
|
||||||
|
cost: z.coerce.number().min(0, 'Стоимость не может быть отрицательной'),
|
||||||
|
storekeeperId: z.string().min(1, 'Выберите кладовщика'),
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
type CurrencyFormProps = {
|
||||||
|
onSubmit: (data: CurrencyBindingModel) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CurrencyForm = ({
|
||||||
|
onSubmit,
|
||||||
|
}: CurrencyFormProps): React.JSX.Element => {
|
||||||
|
const form = useForm<FormValues>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
abbreviation: '',
|
||||||
|
cost: 0,
|
||||||
|
storekeeperId: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
storekeepers,
|
||||||
|
isLoading: isLoadingStorekeepers,
|
||||||
|
error: storekeepersError,
|
||||||
|
} = useStorekeepers(); // Получаем данные кладовщиков
|
||||||
|
|
||||||
|
const handleSubmit = (data: FormValues) => {
|
||||||
|
const payload: CurrencyBindingModel = {
|
||||||
|
...data,
|
||||||
|
id: data.id || crypto.randomUUID(),
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(handleSubmit)}
|
||||||
|
className="space-y-4 max-w-md mx-auto p-4"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="id"
|
||||||
|
render={({ field }) => <input type="hidden" {...field} />}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Название</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Например, Доллар США" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="abbreviation"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Аббревиатура</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Например, USD" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="cost"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Стоимость</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="number" placeholder="Например, 1.0" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="storekeeperId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Кладовщик</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} value={field.value}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger disabled={isLoadingStorekeepers}>
|
||||||
|
{' '}
|
||||||
|
{/* Отключаем выбор, пока данные загружаются */}
|
||||||
|
<SelectValue placeholder="Выберите кладовщика" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{isLoadingStorekeepers ? ( // Индикатор загрузки
|
||||||
|
<SelectItem value="loading" disabled>
|
||||||
|
Загрузка...
|
||||||
|
</SelectItem>
|
||||||
|
) : storekeepersError ? ( // Сообщение об ошибке
|
||||||
|
<SelectItem value="error" disabled>
|
||||||
|
Ошибка загрузки
|
||||||
|
</SelectItem>
|
||||||
|
) : (
|
||||||
|
// Реальные данные
|
||||||
|
storekeepers?.map((storekeeper) => (
|
||||||
|
<SelectItem key={storekeeper.id} value={storekeeper.id}>
|
||||||
|
{storekeeper.name} {storekeeper.surname}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{storekeepersError && (
|
||||||
|
<FormMessage>{storekeepersError.message}</FormMessage>
|
||||||
|
)}{' '}
|
||||||
|
{/* Отображаем ошибку под полем */}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
213
TheBank/bankui/src/components/features/PeriodForm.tsx
Normal file
213
TheBank/bankui/src/components/features/PeriodForm.tsx
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/components/ui/form';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import type { PeriodBindingModel } from '@/types/types';
|
||||||
|
import { Calendar } from '@/components/ui/calendar';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
import { CalendarIcon } from 'lucide-react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover';
|
||||||
|
import { useStorekeepers } from '@/hooks/useStorekeepers';
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
id: z.string().optional(),
|
||||||
|
startTime: z.date({
|
||||||
|
required_error: 'Укажите время начала',
|
||||||
|
invalid_type_error: 'Неверный формат даты',
|
||||||
|
}),
|
||||||
|
endTime: z.date({
|
||||||
|
required_error: 'Укажите время окончания',
|
||||||
|
invalid_type_error: 'Неверный формат даты',
|
||||||
|
}),
|
||||||
|
storekeeperId: z.string().min(1, 'Выберите кладовщика'),
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
type PeriodFormProps = {
|
||||||
|
onSubmit: (data: PeriodBindingModel) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PeriodForm = ({
|
||||||
|
onSubmit,
|
||||||
|
}: PeriodFormProps): React.JSX.Element => {
|
||||||
|
const form = useForm<FormValues>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
id: '',
|
||||||
|
storekeeperId: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
storekeepers,
|
||||||
|
isLoading: isLoadingStorekeepers,
|
||||||
|
error: storekeepersError,
|
||||||
|
} = useStorekeepers();
|
||||||
|
|
||||||
|
const handleSubmit = (data: FormValues) => {
|
||||||
|
const payload: PeriodBindingModel = {
|
||||||
|
...data,
|
||||||
|
id: data.id || crypto.randomUUID(),
|
||||||
|
startTime: data.startTime,
|
||||||
|
endTime: data.endTime,
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(handleSubmit)}
|
||||||
|
className="space-y-4 max-w-md mx-auto p-4"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="id"
|
||||||
|
render={({ field }) => <input type="hidden" {...field} />}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="startTime"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel>Время начала</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant={'outline'}
|
||||||
|
className={cn(
|
||||||
|
'w-full pl-3 text-left font-normal',
|
||||||
|
!field.value && 'text-muted-foreground',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{field.value ? (
|
||||||
|
format(field.value, 'PPP')
|
||||||
|
) : (
|
||||||
|
<span>Выберите дату</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={field.value}
|
||||||
|
onSelect={field.onChange}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="endTime"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel>Время окончания</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant={'outline'}
|
||||||
|
className={cn(
|
||||||
|
'w-full pl-3 text-left font-normal',
|
||||||
|
!field.value && 'text-muted-foreground',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{field.value ? (
|
||||||
|
format(field.value, 'PPP')
|
||||||
|
) : (
|
||||||
|
<span>Выберите дату</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={field.value}
|
||||||
|
onSelect={field.onChange}
|
||||||
|
initialFocus
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="storekeeperId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Кладовщик</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) => field.onChange(value)}
|
||||||
|
value={field.value}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger disabled={isLoadingStorekeepers}>
|
||||||
|
<SelectValue placeholder="Выберите кладовщика" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{isLoadingStorekeepers ? (
|
||||||
|
<SelectItem value="loading" disabled>
|
||||||
|
Загрузка...
|
||||||
|
</SelectItem>
|
||||||
|
) : storekeepersError ? (
|
||||||
|
<SelectItem value="error" disabled>
|
||||||
|
Ошибка загрузки
|
||||||
|
</SelectItem>
|
||||||
|
) : (
|
||||||
|
storekeepers?.map((storekeeper) => (
|
||||||
|
<SelectItem key={storekeeper.id} value={storekeeper.id}>
|
||||||
|
{storekeeper.name} {storekeeper.surname}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
{storekeepersError && (
|
||||||
|
<FormMessage>{storekeepersError.message}</FormMessage>
|
||||||
|
)}
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -6,10 +6,17 @@ import type { LoginBindingModel, StorekeeperBindingModel } from '@/types/types';
|
|||||||
import { LoginForm } from '../features/LoginForm';
|
import { LoginForm } from '../features/LoginForm';
|
||||||
import { Toaster } from '../ui/sonner';
|
import { Toaster } from '../ui/sonner';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
export const AuthStorekeeper = (): React.JSX.Element => {
|
export const AuthStorekeeper = (): React.JSX.Element => {
|
||||||
const { createStorekeeper, loginStorekeeper, isLoginError, loginError } =
|
const navigate = useNavigate();
|
||||||
useStorekeepers();
|
const {
|
||||||
|
createStorekeeper,
|
||||||
|
loginStorekeeper,
|
||||||
|
isLoginError,
|
||||||
|
loginError,
|
||||||
|
isLoginSuccess,
|
||||||
|
} = useStorekeepers();
|
||||||
|
|
||||||
const handleRegister = (data: StorekeeperBindingModel) => {
|
const handleRegister = (data: StorekeeperBindingModel) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
@@ -26,6 +33,12 @@ export const AuthStorekeeper = (): React.JSX.Element => {
|
|||||||
}
|
}
|
||||||
}, [isLoginError, loginError]);
|
}, [isLoginError, loginError]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isLoginSuccess) {
|
||||||
|
navigate('/storekeepers');
|
||||||
|
}
|
||||||
|
}, [isLoginSuccess]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main className="flex flex-col justify-center items-center">
|
<main className="flex flex-col justify-center items-center">
|
||||||
|
|||||||
@@ -5,6 +5,34 @@ import { DialogForm } from '../layout/DialogForm';
|
|||||||
import { DataTable } from '../layout/DataTable';
|
import { DataTable } from '../layout/DataTable';
|
||||||
import { CreditProgramForm } from '../features/CreditProgramForm';
|
import { CreditProgramForm } from '../features/CreditProgramForm';
|
||||||
import type { CreditProgramBindingModel } from '@/types/types';
|
import type { CreditProgramBindingModel } from '@/types/types';
|
||||||
|
import type { ColumnDef } from '../layout/DataTable';
|
||||||
|
|
||||||
|
const columns: ColumnDef<CreditProgramBindingModel>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: 'Название',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'cost',
|
||||||
|
header: 'Стоимость',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'maxCost',
|
||||||
|
header: 'Макс. стоимость',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'storekeeperId',
|
||||||
|
header: 'ID Кладовщика',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'periodId',
|
||||||
|
header: 'ID Периода',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const CreditPrograms = (): React.JSX.Element => {
|
export const CreditPrograms = (): React.JSX.Element => {
|
||||||
const {
|
const {
|
||||||
@@ -23,6 +51,18 @@ export const CreditPrograms = (): React.JSX.Element => {
|
|||||||
createCreditProgram(data);
|
createCreditProgram(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <main className="container mx-auto py-10">Загрузка...</main>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<main className="container mx-auto py-10">
|
||||||
|
Ошибка загрузки: {error?.message}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex-1 flex relative">
|
<main className="flex-1 flex relative">
|
||||||
<AppSidebar
|
<AppSidebar
|
||||||
@@ -33,10 +73,6 @@ export const CreditPrograms = (): React.JSX.Element => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex-1 p-4">
|
<div className="flex-1 p-4">
|
||||||
{isError && (
|
|
||||||
<div className="text-red-500">Ошибка загрузки: {error?.message}</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DialogForm<CreditProgramBindingModel>
|
<DialogForm<CreditProgramBindingModel>
|
||||||
title="Форма"
|
title="Форма"
|
||||||
description="Описание"
|
description="Описание"
|
||||||
@@ -46,7 +82,7 @@ export const CreditPrograms = (): React.JSX.Element => {
|
|||||||
children={<CreditProgramForm />}
|
children={<CreditProgramForm />}
|
||||||
/>
|
/>
|
||||||
<div className="">
|
<div className="">
|
||||||
<DataTable data={[]} columns={[]} />
|
<DataTable data={creditPrograms || []} columns={columns} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,5 +1,115 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { AppSidebar } from '../layout/Sidebar';
|
||||||
|
import { DialogForm } from '../layout/DialogForm';
|
||||||
|
import { DataTable } from '../layout/DataTable';
|
||||||
|
import { useCurrencies } from '@/hooks/useCurrencies';
|
||||||
|
import { useStorekeepers } from '@/hooks/useStorekeepers';
|
||||||
|
import type {
|
||||||
|
CurrencyBindingModel,
|
||||||
|
StorekeeperBindingModel,
|
||||||
|
} from '@/types/types';
|
||||||
|
import type { ColumnDef } from '../layout/DataTable';
|
||||||
|
import { CurrencyForm } from '../features/CurrencyForm';
|
||||||
|
|
||||||
|
interface CurrencyTableData extends CurrencyBindingModel {
|
||||||
|
storekeeperName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: ColumnDef<CurrencyTableData>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: 'Название',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'abbreviation',
|
||||||
|
header: 'Аббревиатура',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'cost',
|
||||||
|
header: 'Стоимость',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'storekeeperName',
|
||||||
|
header: 'Кладовщик',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const Currencies = (): React.JSX.Element => {
|
export const Currencies = (): React.JSX.Element => {
|
||||||
return <main className="">a</main>;
|
const {
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
currencies,
|
||||||
|
createCurrency,
|
||||||
|
updateCurrency,
|
||||||
|
} = useCurrencies();
|
||||||
|
const { storekeepers } = useStorekeepers();
|
||||||
|
|
||||||
|
const finalData = React.useMemo(() => {
|
||||||
|
if (!currencies || !storekeepers) return [];
|
||||||
|
|
||||||
|
return currencies.map((currency) => {
|
||||||
|
const storekeeper = storekeepers.find(
|
||||||
|
(s) => s.id === currency.storekeeperId,
|
||||||
|
);
|
||||||
|
const storekeeperName = storekeeper
|
||||||
|
? [storekeeper.surname, storekeeper.name, storekeeper.middleName]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ') || 'Неизвестный кладовщик'
|
||||||
|
: 'Неизвестный кладовщик';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...currency,
|
||||||
|
storekeeperName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [currencies, storekeepers]);
|
||||||
|
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = React.useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleAdd = (data: CurrencyBindingModel) => {
|
||||||
|
console.log(data);
|
||||||
|
createCurrency(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <main className="container mx-auto py-10">Загрузка...</main>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<main className="container mx-auto py-10">
|
||||||
|
Ошибка загрузки: {error?.message}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="flex-1 flex relative">
|
||||||
|
<AppSidebar
|
||||||
|
onAddClick={() => {
|
||||||
|
setIsDialogOpen(true);
|
||||||
|
}}
|
||||||
|
onEditClick={() => {}}
|
||||||
|
/>
|
||||||
|
<div className="flex-1 p-4">
|
||||||
|
<DialogForm<CurrencyBindingModel>
|
||||||
|
title="Форма валюты"
|
||||||
|
description="Добавьте новую валюту"
|
||||||
|
isOpen={isDialogOpen}
|
||||||
|
onClose={() => setIsDialogOpen(false)}
|
||||||
|
onSubmit={handleAdd}
|
||||||
|
>
|
||||||
|
<CurrencyForm />
|
||||||
|
</DialogForm>
|
||||||
|
<div>
|
||||||
|
<DataTable data={finalData} columns={columns} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
106
TheBank/bankui/src/components/pages/Periods.tsx
Normal file
106
TheBank/bankui/src/components/pages/Periods.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { AppSidebar } from '../layout/Sidebar';
|
||||||
|
import { DialogForm } from '../layout/DialogForm';
|
||||||
|
import { DataTable } from '../layout/DataTable';
|
||||||
|
import { usePeriods } from '@/hooks/usePeriods';
|
||||||
|
import { useStorekeepers } from '@/hooks/useStorekeepers';
|
||||||
|
import type {
|
||||||
|
PeriodBindingModel,
|
||||||
|
StorekeeperBindingModel,
|
||||||
|
} from '@/types/types';
|
||||||
|
import type { ColumnDef } from '../layout/DataTable';
|
||||||
|
import { PeriodForm } from '../features/PeriodForm';
|
||||||
|
|
||||||
|
// Определяем расширенный тип для данных таблицы
|
||||||
|
interface PeriodTableData extends PeriodBindingModel {
|
||||||
|
storekeeperName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определяем столбцы
|
||||||
|
const columns: ColumnDef<PeriodTableData>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'startTime',
|
||||||
|
header: 'Время начала',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'endTime',
|
||||||
|
header: 'Время окончания',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'storekeeperName',
|
||||||
|
header: 'Кладовщик',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Periods = (): React.JSX.Element => {
|
||||||
|
const { isLoading, isError, error, periods, createPeriod } = usePeriods();
|
||||||
|
const { storekeepers } = useStorekeepers();
|
||||||
|
|
||||||
|
const finalData = React.useMemo(() => {
|
||||||
|
if (!periods || !storekeepers) return [];
|
||||||
|
|
||||||
|
return periods.map((period) => {
|
||||||
|
const storekeeper = storekeepers.find(
|
||||||
|
(s) => s.id === period.storekeeperId,
|
||||||
|
);
|
||||||
|
const storekeeperName = storekeeper
|
||||||
|
? [storekeeper.surname, storekeeper.name, storekeeper.middleName]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ') || 'Неизвестный кладовщик'
|
||||||
|
: 'Неизвестный кладовщик';
|
||||||
|
|
||||||
|
return {
|
||||||
|
...period,
|
||||||
|
storekeeperName,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [periods, storekeepers]);
|
||||||
|
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = React.useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleAdd = (data: PeriodBindingModel) => {
|
||||||
|
console.log(data);
|
||||||
|
createPeriod(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <main className="container mx-auto py-10">Загрузка...</main>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<main className="container mx-auto py-10">
|
||||||
|
Ошибка загрузки: {error?.message}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="flex-1 flex relative">
|
||||||
|
<AppSidebar
|
||||||
|
onAddClick={() => {
|
||||||
|
setIsDialogOpen(true);
|
||||||
|
}}
|
||||||
|
onEditClick={() => {}}
|
||||||
|
/>
|
||||||
|
<div className="flex-1 p-4">
|
||||||
|
<DialogForm<PeriodBindingModel>
|
||||||
|
title="Форма"
|
||||||
|
description="Описание"
|
||||||
|
isOpen={isDialogOpen}
|
||||||
|
onClose={() => setIsDialogOpen(false)}
|
||||||
|
onSubmit={handleAdd}
|
||||||
|
>
|
||||||
|
<PeriodForm />
|
||||||
|
</DialogForm>
|
||||||
|
<div>
|
||||||
|
<DataTable data={finalData} columns={columns} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
59
TheBank/bankui/src/components/pages/Storekeepers.tsx
Normal file
59
TheBank/bankui/src/components/pages/Storekeepers.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DataTable } from '../layout/DataTable';
|
||||||
|
import type { ColumnDef } from '../layout/DataTable';
|
||||||
|
import { useStorekeepers } from '@/hooks/useStorekeepers';
|
||||||
|
import type { StorekeeperBindingModel } from '@/types/types';
|
||||||
|
|
||||||
|
const columns: ColumnDef<StorekeeperBindingModel>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
header: 'ID',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: 'Имя',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'surname',
|
||||||
|
header: 'Фамилия',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'middleName',
|
||||||
|
header: 'Отчество',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'login',
|
||||||
|
header: 'Логин',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'email',
|
||||||
|
header: 'Email',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'phoneNumber',
|
||||||
|
header: 'Телефон',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Storekeepers = (): React.JSX.Element => {
|
||||||
|
const { storekeepers, isLoading, error } = useStorekeepers();
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <main className="container mx-auto py-10">Загрузка...</main>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<main className="container mx-auto py-10">
|
||||||
|
Ошибка загрузки данных: {error.message}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="container mx-auto py-10">
|
||||||
|
<h1 className="text-2xl font-bold mb-6">Кладовщики</h1>
|
||||||
|
<DataTable data={storekeepers || []} columns={columns} />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
73
TheBank/bankui/src/components/ui/calendar.tsx
Normal file
73
TheBank/bankui/src/components/ui/calendar.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||||
|
import { DayPicker } from "react-day-picker"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
|
function Calendar({
|
||||||
|
className,
|
||||||
|
classNames,
|
||||||
|
showOutsideDays = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DayPicker>) {
|
||||||
|
return (
|
||||||
|
<DayPicker
|
||||||
|
showOutsideDays={showOutsideDays}
|
||||||
|
className={cn("p-3", className)}
|
||||||
|
classNames={{
|
||||||
|
months: "flex flex-col sm:flex-row gap-2",
|
||||||
|
month: "flex flex-col gap-4",
|
||||||
|
caption: "flex justify-center pt-1 relative items-center w-full",
|
||||||
|
caption_label: "text-sm font-medium",
|
||||||
|
nav: "flex items-center gap-1",
|
||||||
|
nav_button: cn(
|
||||||
|
buttonVariants({ variant: "outline" }),
|
||||||
|
"size-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||||
|
),
|
||||||
|
nav_button_previous: "absolute left-1",
|
||||||
|
nav_button_next: "absolute right-1",
|
||||||
|
table: "w-full border-collapse space-x-1",
|
||||||
|
head_row: "flex",
|
||||||
|
head_cell:
|
||||||
|
"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
|
||||||
|
row: "flex w-full mt-2",
|
||||||
|
cell: cn(
|
||||||
|
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-r-md",
|
||||||
|
props.mode === "range"
|
||||||
|
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
|
||||||
|
: "[&:has([aria-selected])]:rounded-md"
|
||||||
|
),
|
||||||
|
day: cn(
|
||||||
|
buttonVariants({ variant: "ghost" }),
|
||||||
|
"size-8 p-0 font-normal aria-selected:opacity-100"
|
||||||
|
),
|
||||||
|
day_range_start:
|
||||||
|
"day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
|
||||||
|
day_range_end:
|
||||||
|
"day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
|
||||||
|
day_selected:
|
||||||
|
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
||||||
|
day_today: "bg-accent text-accent-foreground",
|
||||||
|
day_outside:
|
||||||
|
"day-outside text-muted-foreground aria-selected:text-muted-foreground",
|
||||||
|
day_disabled: "text-muted-foreground opacity-50",
|
||||||
|
day_range_middle:
|
||||||
|
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
||||||
|
day_hidden: "invisible",
|
||||||
|
...classNames,
|
||||||
|
}}
|
||||||
|
components={{
|
||||||
|
IconLeft: ({ className, ...props }) => (
|
||||||
|
<ChevronLeft className={cn("size-4", className)} {...props} />
|
||||||
|
),
|
||||||
|
IconRight: ({ className, ...props }) => (
|
||||||
|
<ChevronRight className={cn("size-4", className)} {...props} />
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Calendar }
|
||||||
46
TheBank/bankui/src/components/ui/popover.tsx
Normal file
46
TheBank/bankui/src/components/ui/popover.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Popover({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||||
|
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||||
|
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverContent({
|
||||||
|
className,
|
||||||
|
align = "center",
|
||||||
|
sideOffset = 4,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
data-slot="popover-content"
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverAnchor({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||||
|
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||||
@@ -31,6 +31,7 @@ export const useStorekeepers = () => {
|
|||||||
const {
|
const {
|
||||||
mutate: loginStorekeeper,
|
mutate: loginStorekeeper,
|
||||||
isError: isLoginError,
|
isError: isLoginError,
|
||||||
|
isSuccess: isLoginSuccess,
|
||||||
error: loginError,
|
error: loginError,
|
||||||
} = useMutation({
|
} = useMutation({
|
||||||
mutationFn: storekeepersApi.login,
|
mutationFn: storekeepersApi.login,
|
||||||
@@ -45,6 +46,8 @@ export const useStorekeepers = () => {
|
|||||||
isGetAllError,
|
isGetAllError,
|
||||||
isCreateError,
|
isCreateError,
|
||||||
isLoginError,
|
isLoginError,
|
||||||
|
isUpdateError,
|
||||||
|
isLoginSuccess,
|
||||||
loginError,
|
loginError,
|
||||||
error,
|
error,
|
||||||
createStorekeeper,
|
createStorekeeper,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { Currencies } from './components/pages/Currencies.tsx';
|
|||||||
import { CreditPrograms } from './components/pages/CreditPrograms.tsx';
|
import { CreditPrograms } from './components/pages/CreditPrograms.tsx';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { AuthStorekeeper } from './components/pages/AuthStorekeeper.tsx';
|
import { AuthStorekeeper } from './components/pages/AuthStorekeeper.tsx';
|
||||||
|
import { Storekeepers } from './components/pages/Storekeepers.tsx';
|
||||||
|
import { Periods } from './components/pages/Periods.tsx';
|
||||||
|
|
||||||
const routes = createBrowserRouter([
|
const routes = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -21,6 +23,14 @@ const routes = createBrowserRouter([
|
|||||||
path: '/credit-programs',
|
path: '/credit-programs',
|
||||||
element: <CreditPrograms />,
|
element: <CreditPrograms />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/storekeepers',
|
||||||
|
element: <Storekeepers />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/periods',
|
||||||
|
element: <Periods />,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
errorElement: <p>бля пизда рулям</p>,
|
errorElement: <p>бля пизда рулям</p>,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user