0.1.0 to dev from front-base

This commit is contained in:
mfnefd 2024-12-09 00:03:27 +04:00
commit a9c20be67a
38 changed files with 3509 additions and 20 deletions

View File

@ -5,6 +5,7 @@ public class ChangeRecordDto
public Guid Id { get; set; }
public Guid UserId { get; set; }
public Guid? SpendingGroupId { get; set; }
public string? SpendingGroupName { get; set; }
public decimal Sum { get; set; }
public DateTime ChangedAt { get; set; }
}

View File

@ -1,6 +1,6 @@
namespace Contracts.DTO;
public class UserLoginDTO
public class UserLoginDto
{
public string Name { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;

View File

@ -11,5 +11,6 @@ public static class ChangeRecordMapper
Id = dto.Id,
Sum = dto.Sum,
ChangedAt = dto.ChangedAt.ToString("dd.MM.yyyy"),
SpendingGroupName = dto.SpendingGroupName ?? string.Empty
};
}

View File

@ -6,4 +6,5 @@ public class ChangeRecordSearch
public Guid? SpendingGroupId { get; set; }
public DateTime? From { get; set; }
public DateTime? To { get; set; }
public Guid? UserId { get; set; }
}

View File

@ -4,4 +4,5 @@ public class SpendingGroupSearch
{
public Guid? Id { get; set; }
public string? Name { get; set; }
public Guid? UserId { get; set; }
}

View File

@ -6,6 +6,6 @@ namespace Contracts.Services;
public interface IAuthService
{
public Task<UserViewModel> Login(UserLoginDTO loginData);
public Task<UserViewModel> Login(UserLoginDto loginData);
public Task<UserViewModel> Register(UserDto user);
}

View File

@ -5,4 +5,5 @@ public class ChangeRecordViewModel
public Guid Id { get; set; }
public decimal Sum { get; set; }
public string ChangedAt { get; set; } = null!;
public string SpendingGroupName { get; set; } = string.Empty;
}

View File

@ -18,7 +18,7 @@ public class AuthController : ControllerBase
}
[HttpPost]
public async Task<ActionResult<UserViewModel>> Login([FromBody] UserLoginDTO loginData)
public async Task<ActionResult<UserViewModel>> Login([FromBody] UserLoginDto loginData)
{
try
{

View File

@ -20,9 +20,10 @@ if (app.Environment.IsDevelopment())
app.UseSwagger();
app.UseSwaggerUI();
}
app.MigrateDb();
app.UseCors(builder => builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
app.UseHttpsRedirection();
app.UseAuthorization();

View File

@ -70,8 +70,12 @@ public class ChangeRecordRepo : IChangeRecordRepo
{
query = query.Where(x => x.ChangedAt >= search.From && x.ChangedAt <= search.To);
}
if (search.UserId.HasValue)
{
query = query.Where(x => x.UserId == search.UserId);
}
}
return await query.Select(x => x.ToDto()).ToListAsync();
return await query.Include(x => x.SpendingGroup).Select(x => x.ToDto()).ToListAsync();
}
public async Task<ChangeRecordDto?> Update(ChangeRecordDto changeRecord)

View File

@ -49,7 +49,9 @@ public class SpendingGroupRepo : ISpendingGroupRepo
.Include(x => x.ChangeRecords)
.Include(x => x.SpendingPlans)
.FirstOrDefaultAsync(x => x.Id == search.Id
|| x.Name == search.Name);
|| (!string.IsNullOrWhiteSpace(search.Name)
&& x.Name == search.Name
&& x.UserId == search.UserId));
return group?.ToDto();
}
@ -67,9 +69,10 @@ public class SpendingGroupRepo : ISpendingGroupRepo
query = query.Where(x => x.Id == search.Id);
}
if (!string.IsNullOrWhiteSpace(search.Name))
if (!string.IsNullOrWhiteSpace(search.Name) && search.UserId.HasValue)
{
query = query.Where(x => x.Name.Contains(search.Name, StringComparison.OrdinalIgnoreCase));
query = query.Where(x => x.Name.Contains(search.Name, StringComparison.OrdinalIgnoreCase)
&& x.UserId == search.UserId);
}
}

View File

@ -12,7 +12,8 @@ public static class ChangeRecordMapper
Sum = changeRecord.Sum,
ChangedAt = changeRecord.ChangedAt,
SpendingGroupId = changeRecord.SpendingGroupId,
UserId = changeRecord.UserId
UserId = changeRecord.UserId,
SpendingGroupName = changeRecord.SpendingGroup?.Name
};
public static ChangeRecord ToModel(this ChangeRecordDto changeRecord)

View File

@ -17,7 +17,7 @@ public class AuthService : IAuthService
_userRepo = userRepo;
}
public async Task<UserViewModel> Login(UserLoginDTO loginData)
public async Task<UserViewModel> Login(UserLoginDto loginData)
{
if (loginData == null || string.IsNullOrWhiteSpace(loginData.Name)
|| string.IsNullOrWhiteSpace(loginData.Password))

2
front/.gitignore vendored
View File

@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.env

46
front/components.d.ts vendored Normal file
View File

@ -0,0 +1,46 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AButton: typeof import('ant-design-vue/es')['Button']
ACol: typeof import('ant-design-vue/es')['Col']
ADatePicker: typeof import('ant-design-vue/es')['DatePicker']
AFlex: typeof import('ant-design-vue/es')['Flex']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AInput: typeof import('ant-design-vue/es')['Input']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
ALayout: typeof import('ant-design-vue/es')['Layout']
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
AMenu: typeof import('ant-design-vue/es')['Menu']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
ARow: typeof import('ant-design-vue/es')['Row']
ASelect: typeof import('ant-design-vue/es')['Select']
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
ASpace: typeof import('ant-design-vue/es')['Space']
ASpin: typeof import('ant-design-vue/es')['Spin']
ATable: typeof import('ant-design-vue/es')['Table']
ATypographyText: typeof import('ant-design-vue/es')['TypographyText']
ChangeRecordManager: typeof import('./src/components/support/ChangeRecordManager.vue')['default']
ChangeRecordMenu: typeof import('./src/components/support/ChangeRecordMenu.vue')['default']
Groups: typeof import('./src/components/pages/Groups.vue')['default']
Header: typeof import('./src/components/main/Header.vue')['default']
Home: typeof import('./src/components/pages/Home.vue')['default']
Login: typeof import('./src/components/pages/Login.vue')['default']
Manager: typeof import('./src/components/support/Manager.vue')['default']
PlanManager: typeof import('./src/components/support/PlanManager.vue')['default']
Plans: typeof import('./src/components/pages/Plans.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SignUp: typeof import('./src/components/pages/SignUp.vue')['default']
SpendingGroupManager: typeof import('./src/components/support/SpendingGroupManager.vue')['default']
}
}

1716
front/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,14 +6,22 @@
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview"
"preview": "vite preview",
"gen-api": "swagger-typescript-api -r -o ./src/core/api/ --modular -p "
},
"dependencies": {
"vue": "^3.5.12"
"@vueuse/core": "^12.0.0",
"ant-design-vue": "^4.2.6",
"dayjs": "^1.11.13",
"pinia": "^2.2.8",
"vue": "^3.5.12",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.4",
"swagger-typescript-api": "^13.0.23",
"typescript": "~5.6.2",
"unplugin-vue-components": "^0.27.5",
"vite": "^5.4.10",
"vue-tsc": "^2.1.8"
}

View File

@ -1,9 +1,26 @@
<script setup lang="ts">
import Header from './components/main/Header.vue';
</script>
<template>
<a-layout class="layout">
<Header />
<a-layout-content>
<RouterView />
</a-layout-content>
</a-layout>
</template>
<style scoped>
</style>
main {
display: flex;
justify-content: center;
padding: 5vh;
}
.base-page {
display: flex;
flex-direction: column;
min-width: 80dvw;
}
</style>

View File

@ -0,0 +1,59 @@
<script setup lang="ts">
import { inject } from 'vue';
import { useUserStore } from '../../store';
import { AuthService } from '../../core/services/auth-service';
import router from '../../router';
const store = useUserStore();
const authService = inject(AuthService.name) as AuthService;
function logout() {
authService.logout();
router.push({ name: 'login' });
}
</script>
<template>
<a-layout-header class="header">
<div class="base-nav">
<div>ДомБюдж</div>
<nav>
<RouterLink :to="{ name: 'home' }">Главная</RouterLink>
<RouterLink :to="{ name: 'groups' }">Группы расходов</RouterLink>
</nav>
</div>
<div v-if="!store.user.id">
<RouterLink :to="{ name: 'login' }">Войти</RouterLink>
</div>
<div v-else>
<label for="logout">Привет, {{ store.user.name }}! Ваш текущий баланс: {{ store.user.balance }}</label>
<a-button
name="logout"
@click="logout()"
danger
style="margin-left: 30px"
>
Выйти
</a-button>
</div>
</a-layout-header>
</template>
<style scoped>
.header {
background-color: white;
display: flex;
justify-content: space-between;
align-items: center;
}
.base-nav {
display: inline-flex;
justify-content: left;
}
.base-nav a {
margin-left: 30px;
}
</style>

View File

@ -0,0 +1,72 @@
<script setup lang="ts">
import { useAsyncState } from '@vueuse/core';
import { inject } from 'vue';
import { GroupService } from '../../core/services/group-service';
import SpendingGroupManager from '../support/SpendingGroupManager.vue';
import { DeleteOutlined } from '@ant-design/icons-vue';
const groupService = inject(GroupService.name) as GroupService;
const { state, isReady } = useAsyncState(() => groupService.getList(), []);
const columns = [
{
title: "Название группы",
dataIndex: "name",
key: "name",
},
{
title: "Планы группы",
dataIndex: "plans",
key: "plans",
},
{
title: 'Операция',
dataIndex: 'operation',
key: 'operation',
}
]
const refreshData = () => {
groupService.getList().then(data => {
state.value = data;
isReady.value = true;
});
}
const onDelete = (key: string) => {
groupService.deleteGroup(key)
.then(() => {
refreshData();
})
}
</script>
<template>
<div class="base-page">
<h1>Группы расходов</h1>
<SpendingGroupManager :refreshData="refreshData" />
<a-table :dataSource="state" :columns="columns" v-if="isReady">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'plans'">
<RouterLink :to="{ name: 'plans', params: { groupId: record.id } }" >Планы</RouterLink>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-popconfirm
v-if="state?.length"
title="Точно удалить?"
@confirm="onDelete(record.id)"
>
<a><DeleteOutlined /> Удалить</a>
</a-popconfirm>
</template>
</template>
</a-table>
<div v-else>
<a-spin size="large" />
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,76 @@
<script setup lang="ts">
import { useAsyncState } from '@vueuse/core';
import ChangeRecordMenu from '../support/ChangeRecordManager.vue';
import { ChangeRecordService } from '../../core/services/change-record-service';
import { inject } from 'vue';
import { DeleteOutlined } from '@ant-design/icons-vue';
const changeRecordService = inject(ChangeRecordService.name) as ChangeRecordService;
const { state, isReady } = useAsyncState(() => changeRecordService.getList(), []);
const columns = [
{
title: 'Дата',
dataIndex: 'changedAt',
key: 'changedAt',
},
{
title: 'Сумма',
dataIndex: 'sum',
key: 'sum',
},
{
title: 'Группа расходов',
dataIndex: 'spendingGroupName',
key: 'spendingGroupName',
},
{
title: 'Операция',
dataIndex: 'operation',
key: 'operation',
}
]
const refreshData = () => {
changeRecordService.getList().then(data => {
state.value = data;
isReady.value = true;
});
}
const onDelete = (key: string) => {
changeRecordService.deleteRecord(key)
.then(() => {
refreshData();
})
}
</script>
<template>
<div class="base-page">
<h1>История изменений баланса</h1>
<ChangeRecordMenu :refreshData="refreshData" />
<a-table :dataSource="state" :columns="columns" v-if="isReady" >
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'operation'">
<a-popconfirm
v-if="state?.length"
title="Точно удалить?"
@confirm="onDelete(record.id)"
>
<a><DeleteOutlined /> Удалить</a>
</a-popconfirm>
</template>
</template>
</a-table>
<div v-else>
<a-spin size="large" />
</div>
</div>
</template>
<style scoped>
.layout {
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<a-form
:model="formState"
name="login"
class="login-form"
@finish="onFinish"
@finishFailed="onFinishFailed"
>
<a-form-item
label="Логин"
name="name"
:rules="[{ required: true, message: 'Пожалуйста, введите свой логин' }]"
>
<a-input v-model:value="formState.name">
<template #prefix>
<UserOutlined class="site-form-item-icon" />
</template>
</a-input>
</a-form-item>
<a-form-item
label="Пароль"
name="password"
:rules="[{ required: true, message: 'Пароль тоже нужен!' }]"
>
<a-input-password v-model:value="formState.password">
<template #prefix>
<LockOutlined class="site-form-item-icon" />
</template>
</a-input-password>
</a-form-item>
<a-form-item>
<a-button :disabled="disabled" type="primary" html-type="submit" class="login-form-button">
Войти
</a-button>
Или
<RouterLink :to="{ name: 'signup' }">создать аккаунт</RouterLink>
</a-form-item>
</a-form>
</template>
<script lang="ts" setup>
import { reactive, computed, inject } from 'vue';
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue';
import { UserLoginDto } from '../../core/api/data-contracts';
import { RouterLink } from 'vue-router';
import { AuthService } from '../../core/services/auth-service';
import router from '../../router';
const formState = reactive<UserLoginDto>({
name: '',
password: '',
});
const authService = inject(AuthService.name) as AuthService;
console.log(authService);
// Логика формы
const onFinish = async (values: any) => {
console.log('Success:', values);
await authService.login(formState);
router.push({ name: 'home' });
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
const disabled = computed(() => {
return !(formState.name && formState.password);
});
</script>
<style scoped>
.login-form {
max-width: 300px;
}
.login-form-button {
width: 100%;
}
</style>

View File

@ -0,0 +1,79 @@
<script setup lang="ts">
import { useAsyncState } from '@vueuse/core';
import { inject } from 'vue';
import { PlanService } from '../../core/services/plans-service';
import { useRoute } from 'vue-router';
import PlanManager from '../support/PlanManager.vue';
import { DeleteOutlined } from '@ant-design/icons-vue';
const planService = inject(PlanService.name) as PlanService;
const groupId = useRoute().params.groupId as string;
const { state, isReady } = useAsyncState(() => planService.getList(groupId), []);
const columns = [
{
title: "Планируемые расходы",
dataIndex: "sum",
key: "sum",
},
{
title: "Начало плана",
dataIndex: "startAt",
key: "startAt",
},
{
title: "Конец плана",
dataIndex: "endAt",
key: "endAt",
},
{
title: 'Операция',
dataIndex: 'operation',
key: 'operation',
}
]
const refreshData = () => {
planService.getList(groupId).then(data => {
state.value = data;
isReady.value = true;
});
};
const onDelete = (key: string) => {
planService.deletePlan(key)
.then(() => {
refreshData();
})
}
</script>
<template>
<div class="base-page">
<h1>Планы группы</h1>
<PlanManager :groupId="groupId" :refreshData="refreshData"/>
<a-table :dataSource="state" :columns="columns" v-if="isReady">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'operation'">
<a-popconfirm
v-if="state?.length"
title="Точно удалить?"
@confirm="onDelete(record.id)"
>
<a><DeleteOutlined /> Удалить</a>
</a-popconfirm>
</template>
</template>
</a-table>
<div v-else>
<a-spin size="large" />
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,86 @@
<template>
<a-form
:model="formState"
name="signup"
class="signup-form"
@finish="onFinish"
@finishFailed="onFinishFailed"
>
<a-form-item
label="Логин"
name="name"
:rules="[{ required: true, message: 'Пожалуйста, введите свой логин' }]"
>
<a-input v-model:value="formState.name">
<template #prefix>
<UserOutlined class="site-form-item-icon" />
</template>
</a-input>
</a-form-item>
<a-form-item
label="Пароль"
name="password"
:rules="[{ required: true, message: 'Пароль тоже нужен!' }]"
>
<a-input-password v-model:value="formState.password">
<template #prefix>
<LockOutlined class="site-form-item-icon" />
</template>
</a-input-password>
</a-form-item>
<a-form-item
label="Баланс"
name="balance"
:rules="[{ required: true, message: 'Пожалуйста, введите свой баланс' }]"
>
<a-input-number prefix="₽" v-model:value="formState.balance" min="0"/>
</a-form-item>
<a-form-item>
<a-button :disabled="disabled" type="primary" html-type="submit" class="signup-form-button">
Создать
</a-button>
Или
<RouterLink :to="{ name: 'login' }">войти в свой аккаунт</RouterLink>
</a-form-item>
</a-form>
</template>
<script lang="ts" setup>
import { reactive, computed, inject } from 'vue';
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue';
import { UserDto } from '../../core/api/data-contracts';
import { RouterLink } from 'vue-router';
import { AuthService } from '../../core/services/auth-service';
import router from '../../router';
const formState = reactive<UserDto>({
name: '',
password: '',
balance: 0
});
const authService = inject(typeof(AuthService)) as AuthService;
// Логика формы
const onFinish = async (values: any) => {
console.log('Success:', values);
await authService.register(formState);
router.push({ name: 'home' });
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
const disabled = computed(() => {
return !(formState.name && formState.password && formState.balance);
});
</script>
<style scoped>
.signup-form {
max-width: 300px;
}
.signup-form-button {
width: 100%;
}
</style>

View File

@ -0,0 +1,95 @@
<style scoped>
</style>
<template>
<a-space direction="vertical" :size="10">
<label for="change-record">Записать изменения баланса</label>
<a-form
:model="formState"
name="change-record"
@finish="onFinish"
@finishFailed="onFinishFailed"
>
<a-form-item
label="Сумма"
name="sum"
:rules="[{ required: true, message: 'Пожалуйста, введите сумму' }]"
>
<a-input-number v-model:value="formState.sum" />
</a-form-item>
<a-form-item
label="Группа расходов"
name="spendingGroupId"
v-if="isReady"
:rules="[{ required: true, message: 'Пожалуйста, выберите группу расходов' }]"
>
<a-select v-model:value="formState.spendingGroupId" :disabled="disabled">
<a-select-option v-for="group in groupList" :key="group.id" :value="group.id">
{{ group.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-else>
<a-spin size="small" />
</a-form-item>
<a-form-item
name="changedAt"
label="Дата"
:rules="[{ required: true, message: 'Пожалуйста, выберите дату' }]"
>
<a-date-picker v-model:value="pickedChangedAt" />
</a-form-item>
<a-form-item>
<a-button html-type="submit" :disabled="disabledForm" type="primary">Сохранить</a-button>
</a-form-item>
</a-form>
</a-space>
</template>
<script setup lang="ts">
import { computed, inject, reactive, ref } from 'vue';
import { ChangeRecordDto, SpendingGroupViewModel } from '../../core/api/data-contracts';
import { useUserStore } from '../../store';
import { ChangeRecordService } from '../../core/services/change-record-service';
import { GroupService } from '../../core/services/group-service';
import { useAsyncState } from '@vueuse/core';
import type { Dayjs } from 'dayjs';
interface IProps {
refreshData: () => void
}
const { refreshData } = defineProps<IProps>();
const store = useUserStore();
// Сервисы
const changeRecordService = inject(ChangeRecordService.name) as ChangeRecordService;
const groupService = inject(GroupService.name) as GroupService;
const { state: groupList, isReady } = useAsyncState(() => groupService.getList(), []);
const pickedChangedAt = ref<Dayjs>();
const formState = reactive<ChangeRecordDto>({
userId: store.user.id,
sum: -1,
changedAt: new Date().toISOString(),
spendingGroupId: null
});
// Логика формы
const onFinish = async (values: any) => {
console.log('Success:', values);
formState.changedAt = pickedChangedAt.value?.toISOString();
await changeRecordService.createRecord(formState);
refreshData();
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
const disabled = computed(() => {
return formState.sum && formState.sum >= 0;
});
const disabledForm = computed(() => {
return !(formState.sum && formState.changedAt && (formState.spendingGroupId || formState.sum > 0));
})
</script>

View File

@ -0,0 +1,80 @@
<style scoped>
</style>
<template>
<a-space direction="vertical" :size="10">
<label for="change-record">Добавить план расходов</label>
<a-form
:model="formState"
name="change-record"
@finish="onFinish"
@finishFailed="onFinishFailed"
>
<a-form-item
label="Сумма"
name="sum"
:rules="[{ required: true, message: 'Пожалуйста, введите сумму' }]"
>
<a-input-number v-model:value="formState.sum" />
</a-form-item>
<a-form-item
name="startAt"
label="Дата начала плана"
:rules="[{ required: true, message: 'Пожалуйста, введите дату начала плана' }]"
>
<a-date-picker v-model:value="startAt" />
</a-form-item>
<a-form-item
name="endAt"
label="Дата окончания плана"
:rules="[{ required: true, message: 'Пожалуйста, введите дату окончания плана' }]"
>
<a-date-picker v-model:value="endAt" />
</a-form-item>
<a-form-item>
<a-button :disabled="disabledForm" html-type="submit" type="primary">Добавить</a-button>
</a-form-item>
</a-form>
</a-space>
</template>
<script setup lang="ts">
import { computed, inject, reactive, ref } from 'vue';
import { SpendingPlanDto } from '../../core/api/data-contracts';
import { Dayjs } from 'dayjs';
import { PlanService } from '../../core/services/plans-service';
interface IProps {
groupId: string,
refreshData: () => void
}
const { groupId, refreshData } = defineProps<IProps>();
// Сервисы
const planService = inject(PlanService.name) as PlanService;
const startAt = ref<Dayjs>();
const endAt = ref<Dayjs>();
const formState = reactive<SpendingPlanDto>({
sum: 0,
startAt: new Date().toISOString(),
endAt: new Date().toISOString(),
spendingGroupId: groupId
});
// Логика формы
const onFinish = async (values: any) => {
console.log('Success:', values);
formState.startAt = startAt.value?.toISOString();
formState.endAt = endAt.value?.toISOString();
await planService.createPlan(formState);
refreshData();
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
const disabledForm = computed(() => {
return !(formState.sum && formState.startAt && formState.endAt);
});
</script>

View File

@ -0,0 +1,57 @@
<template>
<a-space direction="vertical" :size="10">
<label for="spending-group">Создать группу расходов</label>
<a-form
:model="formState"
name="spending-group"
@finish="onFinish"
@finishFailed="onFinishFailed"
>
<a-form-item
label="Название"
name="name"
:rules="[{ required: true, message: 'Пожалуйста, введите название' }]"
>
<a-input v-model:value="formState.name" />
</a-form-item>
<a-form-item>
<a-button :disabled="disabledForm" html-type="submit" type="primary">Сохранить</a-button>
</a-form-item>
</a-form>
</a-space>
</template>
<script setup lang="ts">
import { computed, inject, reactive } from 'vue';
import { useUserStore } from '../../store';
import { GroupService } from '../../core/services/group-service';
import { SpendingGroupDto } from '../../core/api/data-contracts';
interface IProps {
refreshData: () => void
}
const { refreshData } = defineProps<IProps>();
const store = useUserStore();
// Сервисы
const groupService = inject(GroupService.name) as GroupService;
const formState = reactive<SpendingGroupDto>({
userId: store.user.id,
name: '',
});
// Логика формы
const onFinish = async (values: any) => {
console.log('Success:', values);
await groupService.createGroup(formState);
refreshData();
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
const disabledForm = computed(() => {
return !formState.name;
});
</script>

470
front/src/core/api/Api.ts Normal file
View File

@ -0,0 +1,470 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: acacode ##
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import {
ChangeRecordDto,
ChangeRecordViewModel,
SpendingGroupDto,
SpendingGroupViewModel,
SpendingPlanDto,
SpendingPlanViewModel,
UserDto,
UserLoginDto,
UserViewModel,
} from "./data-contracts";
import { ContentType, HttpClient, RequestParams } from "./http-client";
export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
/**
* No description
*
* @tags Auth
* @name AuthCreate
* @request POST:/api/Auth
* @response `200` `UserViewModel` Success
*/
auth = (data: UserLoginDto, params: RequestParams = {}) =>
this.request<UserViewModel, any>({
path: `/api/Auth`,
method: "POST",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
/**
* No description
*
* @tags Auth
* @name AuthRegisterCreate
* @request POST:/api/Auth/register
* @response `200` `UserViewModel` Success
*/
authRegister = (data: UserDto, params: RequestParams = {}) =>
this.request<UserViewModel, any>({
path: `/api/Auth/register`,
method: "POST",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
/**
* No description
*
* @tags ChangeRecord
* @name ChangeRecordCreate
* @request POST:/api/ChangeRecord
* @response `200` `ChangeRecordViewModel` Success
*/
changeRecordCreate = (data: ChangeRecordDto, params: RequestParams = {}) =>
this.request<ChangeRecordViewModel, any>({
path: `/api/ChangeRecord`,
method: "POST",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
/**
* No description
*
* @tags ChangeRecord
* @name ChangeRecordList
* @request GET:/api/ChangeRecord
* @response `200` `(ChangeRecordViewModel)[]` Success
*/
changeRecordList = (params: RequestParams = {}) =>
this.request<ChangeRecordViewModel[], any>({
path: `/api/ChangeRecord`,
method: "GET",
format: "json",
...params,
});
/**
* No description
*
* @tags ChangeRecord
* @name ChangeRecordPartialUpdate
* @request PATCH:/api/ChangeRecord
* @response `200` `ChangeRecordViewModel` Success
*/
changeRecordPartialUpdate = (data: ChangeRecordDto, params: RequestParams = {}) =>
this.request<ChangeRecordViewModel, any>({
path: `/api/ChangeRecord`,
method: "PATCH",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
/**
* No description
*
* @tags ChangeRecord
* @name ChangeRecordDelete
* @request DELETE:/api/ChangeRecord
* @response `200` `void` Success
*/
changeRecordDelete = (
query?: {
/** @format uuid */
Id?: string;
/** @format uuid */
SpendingGroupId?: string;
/** @format date-time */
From?: string;
/** @format date-time */
To?: string;
/** @format uuid */
UserId?: string;
},
params: RequestParams = {},
) =>
this.request<void, any>({
path: `/api/ChangeRecord`,
method: "DELETE",
query: query,
...params,
});
/**
* No description
*
* @tags ChangeRecord
* @name ChangeRecordFilterList
* @request GET:/api/ChangeRecord/filter
* @response `200` `(ChangeRecordViewModel)[]` Success
*/
changeRecordFilterList = (
query?: {
/** @format uuid */
Id?: string;
/** @format uuid */
SpendingGroupId?: string;
/** @format date-time */
From?: string;
/** @format date-time */
To?: string;
/** @format uuid */
UserId?: string;
},
params: RequestParams = {},
) =>
this.request<ChangeRecordViewModel[], any>({
path: `/api/ChangeRecord/filter`,
method: "GET",
query: query,
format: "json",
...params,
});
/**
* No description
*
* @tags SpendingGroup
* @name SpendingGroupDetail
* @request GET:/api/SpendingGroup/{id}
* @response `200` `SpendingGroupViewModel` Success
*/
spendingGroupDetail = (
id: string,
query?: {
/** @format uuid */
Id?: string;
Name?: string;
/** @format uuid */
UserId?: string;
},
params: RequestParams = {},
) =>
this.request<SpendingGroupViewModel, any>({
path: `/api/SpendingGroup/${id}`,
method: "GET",
query: query,
format: "json",
...params,
});
/**
* No description
*
* @tags SpendingGroup
* @name SpendingGroupList
* @request GET:/api/SpendingGroup
* @response `200` `(SpendingGroupViewModel)[]` Success
*/
spendingGroupList = (params: RequestParams = {}) =>
this.request<SpendingGroupViewModel[], any>({
path: `/api/SpendingGroup`,
method: "GET",
format: "json",
...params,
});
/**
* No description
*
* @tags SpendingGroup
* @name SpendingGroupCreate
* @request POST:/api/SpendingGroup
* @response `200` `SpendingGroupViewModel` Success
*/
spendingGroupCreate = (data: SpendingGroupDto, params: RequestParams = {}) =>
this.request<SpendingGroupViewModel, any>({
path: `/api/SpendingGroup`,
method: "POST",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
/**
* No description
*
* @tags SpendingGroup
* @name SpendingGroupPartialUpdate
* @request PATCH:/api/SpendingGroup
* @response `200` `SpendingGroupViewModel` Success
*/
spendingGroupPartialUpdate = (data: SpendingGroupDto, params: RequestParams = {}) =>
this.request<SpendingGroupViewModel, any>({
path: `/api/SpendingGroup`,
method: "PATCH",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
/**
* No description
*
* @tags SpendingGroup
* @name SpendingGroupDelete
* @request DELETE:/api/SpendingGroup
* @response `200` `void` Success
*/
spendingGroupDelete = (
query?: {
/** @format uuid */
Id?: string;
Name?: string;
/** @format uuid */
UserId?: string;
},
params: RequestParams = {},
) =>
this.request<void, any>({
path: `/api/SpendingGroup`,
method: "DELETE",
query: query,
...params,
});
/**
* No description
*
* @tags SpendingGroup
* @name SpendingGroupFilterList
* @request GET:/api/SpendingGroup/filter
* @response `200` `(SpendingGroupViewModel)[]` Success
*/
spendingGroupFilterList = (
query?: {
/** @format uuid */
Id?: string;
Name?: string;
/** @format uuid */
UserId?: string;
},
params: RequestParams = {},
) =>
this.request<SpendingGroupViewModel[], any>({
path: `/api/SpendingGroup/filter`,
method: "GET",
query: query,
format: "json",
...params,
});
/**
* No description
*
* @tags SpendingPlan
* @name SpendingPlanDetail
* @request GET:/api/SpendingPlan/{id}
* @response `200` `SpendingPlanViewModel` Success
*/
spendingPlanDetail = (
id: string,
query?: {
/** @format uuid */
Id?: string;
},
params: RequestParams = {},
) =>
this.request<SpendingPlanViewModel, any>({
path: `/api/SpendingPlan/${id}`,
method: "GET",
query: query,
format: "json",
...params,
});
/**
* No description
*
* @tags SpendingPlan
* @name SpendingPlanList
* @request GET:/api/SpendingPlan
* @response `200` `(SpendingPlanViewModel)[]` Success
*/
spendingPlanList = (params: RequestParams = {}) =>
this.request<SpendingPlanViewModel[], any>({
path: `/api/SpendingPlan`,
method: "GET",
format: "json",
...params,
});
/**
* No description
*
* @tags SpendingPlan
* @name SpendingPlanCreate
* @request POST:/api/SpendingPlan
* @response `200` `SpendingPlanViewModel` Success
*/
spendingPlanCreate = (data: SpendingPlanDto, params: RequestParams = {}) =>
this.request<SpendingPlanViewModel, any>({
path: `/api/SpendingPlan`,
method: "POST",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
/**
* No description
*
* @tags SpendingPlan
* @name SpendingPlanPartialUpdate
* @request PATCH:/api/SpendingPlan
* @response `200` `SpendingPlanViewModel` Success
*/
spendingPlanPartialUpdate = (data: SpendingPlanDto, params: RequestParams = {}) =>
this.request<SpendingPlanViewModel, any>({
path: `/api/SpendingPlan`,
method: "PATCH",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
/**
* No description
*
* @tags SpendingPlan
* @name SpendingPlanDelete
* @request DELETE:/api/SpendingPlan
* @response `200` `void` Success
*/
spendingPlanDelete = (
query?: {
/** @format uuid */
Id?: string;
},
params: RequestParams = {},
) =>
this.request<void, any>({
path: `/api/SpendingPlan`,
method: "DELETE",
query: query,
...params,
});
/**
* No description
*
* @tags SpendingPlan
* @name SpendingPlanFilterList
* @request GET:/api/SpendingPlan/filter
* @response `200` `(SpendingPlanViewModel)[]` Success
*/
spendingPlanFilterList = (
query?: {
/** @format uuid */
Id?: string;
},
params: RequestParams = {},
) =>
this.request<SpendingPlanViewModel[], any>({
path: `/api/SpendingPlan/filter`,
method: "GET",
query: query,
format: "json",
...params,
});
/**
* No description
*
* @tags User
* @name UserList
* @request GET:/api/User
* @response `200` `UserViewModel` Success
*/
userGet = (
query?: {
/** @format uuid */
Id?: string;
Name?: string;
},
params: RequestParams = {},
) =>
this.request<UserViewModel, any>({
path: `/api/User`,
method: "GET",
query: query,
format: "json",
...params,
});
/**
* No description
*
* @tags User
* @name UserPartialUpdate
* @request PATCH:/api/User
* @response `200` `UserViewModel` Success
*/
userPartialUpdate = (data: UserDto, params: RequestParams = {}) =>
this.request<UserViewModel, any>({
path: `/api/User`,
method: "PATCH",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
/**
* No description
*
* @tags User
* @name UserDelete
* @request DELETE:/api/User
* @response `200` `UserViewModel` Success
*/
userDelete = (
query?: {
/** @format uuid */
Id?: string;
Name?: string;
},
params: RequestParams = {},
) =>
this.request<UserViewModel, any>({
path: `/api/User`,
method: "DELETE",
query: query,
format: "json",
...params,
});
}

View File

@ -0,0 +1,98 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: acacode ##
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
export interface ChangeRecordDto {
/** @format uuid */
id?: string;
/** @format uuid */
userId?: string;
/** @format uuid */
spendingGroupId?: string | null;
spendingGroupName?: string | null;
/** @format double */
sum?: number;
/** @format date-time */
changedAt?: string;
}
export interface ChangeRecordViewModel {
/** @format uuid */
id?: string;
/** @format double */
sum?: number;
/** @format date-time */
changedAt?: string;
spendingGroupName?: string | null;
}
export interface SpendingGroupDto {
/** @format uuid */
id?: string;
name?: string | null;
/** @format uuid */
userId?: string;
changeRecords?: ChangeRecordDto[] | null;
spendingPlans?: SpendingPlanDto[] | null;
}
export interface SpendingGroupViewModel {
/** @format uuid */
id?: string;
name?: string | null;
changeRecords?: ChangeRecordViewModel[] | null;
spendingPlans?: SpendingPlanViewModel[] | null;
}
export interface SpendingPlanDto {
/** @format uuid */
id?: string;
/** @format uuid */
spendingGroupId?: string;
/** @format double */
sum?: number;
/** @format date-time */
startAt?: string;
/** @format date-time */
endAt?: string;
}
export interface SpendingPlanViewModel {
/** @format uuid */
id?: string;
/** @format date-time */
startAt?: string;
/** @format date-time */
endAt?: string;
/** @format double */
sum?: number;
}
export interface UserDto {
/** @format uuid */
id?: string;
name?: string | null;
password?: string | null;
/** @format double */
balance?: number;
}
export interface UserLoginDto {
name?: string | null;
password?: string | null;
}
export interface UserViewModel {
/** @format uuid */
id?: string;
name?: string | null;
/** @format double */
balance?: number;
}

View File

@ -0,0 +1,220 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: acacode ##
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
export type QueryParamsType = Record<string | number, any>;
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
export interface FullRequestParams extends Omit<RequestInit, "body"> {
/** set parameter to `true` for call `securityWorker` for this request */
secure?: boolean;
/** request path */
path: string;
/** content type of request body */
type?: ContentType;
/** query params */
query?: QueryParamsType;
/** format of response (i.e. response.json() -> format: "json") */
format?: ResponseFormat;
/** request body */
body?: unknown;
/** base url */
baseUrl?: string;
/** request cancellation token */
cancelToken?: CancelToken;
}
export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">;
export interface ApiConfig<SecurityDataType = unknown> {
baseUrl?: string;
baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void;
customFetch?: typeof fetch;
}
export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
data: D;
error: E;
}
type CancelToken = Symbol | string | number;
export enum ContentType {
Json = "application/json",
FormData = "multipart/form-data",
UrlEncoded = "application/x-www-form-urlencoded",
Text = "text/plain",
}
export class HttpClient<SecurityDataType = unknown> {
public baseUrl: string = "http://localhost:5125";
private securityData: SecurityDataType | null = null;
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
private abortControllers = new Map<CancelToken, AbortController>();
private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams);
private baseApiParams: RequestParams = {
credentials: "same-origin",
headers: {},
redirect: "follow",
referrerPolicy: "no-referrer",
};
constructor(apiConfig: ApiConfig<SecurityDataType> = {}) {
Object.assign(this, apiConfig);
}
public setSecurityData = (data: SecurityDataType | null) => {
this.securityData = data;
};
protected encodeQueryParam(key: string, value: any) {
const encodedKey = encodeURIComponent(key);
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
}
protected addQueryParam(query: QueryParamsType, key: string) {
return this.encodeQueryParam(key, query[key]);
}
protected addArrayQueryParam(query: QueryParamsType, key: string) {
const value = query[key];
return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
}
protected toQueryString(rawQuery?: QueryParamsType): string {
const query = rawQuery || {};
const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]);
return keys
.map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))
.join("&");
}
protected addQueryParams(rawQuery?: QueryParamsType): string {
const queryString = this.toQueryString(rawQuery);
return queryString ? `?${queryString}` : "";
}
private contentFormatters: Record<ContentType, (input: any) => any> = {
[ContentType.Json]: (input: any) =>
input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input,
[ContentType.Text]: (input: any) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input),
[ContentType.FormData]: (input: any) =>
Object.keys(input || {}).reduce((formData, key) => {
const property = input[key];
formData.append(
key,
property instanceof Blob
? property
: typeof property === "object" && property !== null
? JSON.stringify(property)
: `${property}`,
);
return formData;
}, new FormData()),
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
};
protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
return {
...this.baseApiParams,
...params1,
...(params2 || {}),
headers: {
...(this.baseApiParams.headers || {}),
...(params1.headers || {}),
...((params2 && params2.headers) || {}),
},
};
}
protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {
if (this.abortControllers.has(cancelToken)) {
const abortController = this.abortControllers.get(cancelToken);
if (abortController) {
return abortController.signal;
}
return void 0;
}
const abortController = new AbortController();
this.abortControllers.set(cancelToken, abortController);
return abortController.signal;
};
public abortRequest = (cancelToken: CancelToken) => {
const abortController = this.abortControllers.get(cancelToken);
if (abortController) {
abortController.abort();
this.abortControllers.delete(cancelToken);
}
};
public request = async <T = any, E = any>({
body,
secure,
path,
type,
query,
format,
baseUrl,
cancelToken,
...params
}: FullRequestParams): Promise<HttpResponse<T, E>> => {
const secureParams =
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
this.securityWorker &&
(await this.securityWorker(this.securityData))) ||
{};
const requestParams = this.mergeRequestParams(params, secureParams);
const queryString = query && this.toQueryString(query);
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
const responseFormat = format || requestParams.format;
return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, {
...requestParams,
headers: {
...(requestParams.headers || {}),
...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}),
},
signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,
body: typeof body === "undefined" || body === null ? null : payloadFormatter(body),
}).then(async (response) => {
const r = response.clone() as HttpResponse<T, E>;
r.data = null as unknown as T;
r.error = null as unknown as E;
const data = !responseFormat
? r
: await response[responseFormat]()
.then((data) => {
if (r.ok) {
r.data = data;
} else {
r.error = data;
}
return r;
})
.catch((e) => {
r.error = e;
return r;
});
if (cancelToken) {
this.abortControllers.delete(cancelToken);
}
if (!response.ok) throw data;
return data;
});
};
}

View File

@ -0,0 +1,30 @@
import { useUserStore } from "../../store";
import { Api } from "../api/Api";
import { UserDto, UserLoginDto, UserViewModel } from "../api/data-contracts";
export class AuthService {
private readonly _api: Api;
constructor(api: Api) {
this._api = api;
}
async login(data: UserLoginDto) {
let result = await this._api.auth(data);
console.log(result);
const store = useUserStore();
store.updateUser(result.data);
}
logout() {
const store = useUserStore();
store.updateUser({} as UserViewModel);
}
async register(data: UserDto) {
let result = await this._api.authRegister(data);
const store = useUserStore();
store.updateUser(result.data);
}
}

View File

@ -0,0 +1,42 @@
import { useUserStore } from "../../store";
import { Api } from "../api/Api";
import { ChangeRecordDto, ChangeRecordViewModel } from "../api/data-contracts";
export class ChangeRecordService {
private readonly _api: Api
constructor(api: Api) {
this._api = api;
}
private async _updateUser() {
const store = useUserStore();
let updatedUser = await this._api.userGet({ Id: store.user.id });
store.updateUser(updatedUser.data);
}
async createRecord(data: ChangeRecordDto) {
let result = await this._api.changeRecordCreate(data);
this._updateUser();
console.log(result);
}
async deleteRecord(Id: string) {
console.log(Id);
let result = await this._api.changeRecordDelete({ Id });
this._updateUser();
console.log("delete");
console.log(result);
}
async getList(): Promise<ChangeRecordViewModel[] | null> {
const store = useUserStore();
if (!store.user.id) return null;
let UserId = store.user.id;
let result = await this._api.changeRecordFilterList({ UserId });
console.log(result);
return result.data;
}
}

View File

@ -0,0 +1,33 @@
import { useUserStore } from "../../store";
import { Api } from "../api/Api";
import { SpendingGroupDto, SpendingGroupViewModel } from "../api/data-contracts";
export class GroupService {
private readonly _api: Api;
constructor(api: Api) {
this._api = api;
}
async getList(): Promise<SpendingGroupViewModel[] | null> {
const store = useUserStore();
console.log("get list " + store.user.id);
if (!store.user.id) return null;
let UserId = store.user.id;
let result = await this._api.spendingGroupFilterList({ UserId });
console.log(result);
return result.data;
}
async deleteGroup(Id: string) {
let result = await this._api.spendingGroupDelete({ Id });
console.log("delete");
console.log(result);
}
async createGroup(data: SpendingGroupDto) {
let result = await this._api.spendingGroupCreate(data);
console.log(result);
}
}

View File

@ -0,0 +1,26 @@
import { Api } from "../api/Api";
import { SpendingPlanDto, SpendingPlanViewModel } from "../api/data-contracts";
export class PlanService {
private readonly _api: Api
constructor(api: Api) {
this._api = api;
}
async deletePlan(Id: string) {
let result = await this._api.spendingPlanDelete({ Id });
console.log("delete");
console.log(result);
}
async getList(groupId: string): Promise<SpendingPlanViewModel[] | null> {
const result = await this._api.spendingGroupDetail(groupId);
return result.data.spendingPlans || null;
}
async createPlan(data: SpendingPlanDto) {
const result = await this._api.spendingPlanCreate(data);
console.log(result);
}
}

View File

@ -1,5 +1,24 @@
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
import { Api } from './core/api/Api'
import { createPinia } from 'pinia'
import { AuthService } from './core/services/auth-service'
import { ChangeRecordService } from './core/services/change-record-service'
import { GroupService } from './core/services/group-service'
import { PlanService } from './core/services/plans-service'
createApp(App).mount('#app')
const app = createApp(App)
app.use(router)
app.use(createPinia())
// Di
const api = new Api();
app.provide(AuthService.name, new AuthService(api));
app.provide(ChangeRecordService.name, new ChangeRecordService(api));
app.provide(GroupService.name, new GroupService(api));
app.provide(PlanService.name, new PlanService(api));
app.mount('#app')

46
front/src/router.ts Normal file
View File

@ -0,0 +1,46 @@
import { createRouter, createWebHistory } from 'vue-router';
import { useUserStore } from './store';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'home',
component: () => import('./components/pages/Home.vue'),
},
{
path: '/login',
name: 'login',
component: () => import('./components/pages/Login.vue'),
meta: { notRequiresAuth: true },
},
{
path: '/signup',
name: 'signup',
component: () => import('./components/pages/SignUp.vue'),
meta: { notRequiresAuth: true },
},
{
path: '/groups',
name: 'groups',
component: () => import('./components/pages/Groups.vue'),
},
{
path: '/plans/:groupId',
name: 'plans',
component: () => import('./components/pages/Plans.vue'),
}
],
});
router.beforeEach((to, from, next) => {
const store = useUserStore();
if (!to.meta.notRequiresAuth && !store.user.id) {
next({ name: 'login' });
} else {
next();
}
});
export default router;

19
front/src/store.ts Normal file
View File

@ -0,0 +1,19 @@
import { UserViewModel } from './core/api/data-contracts'
import { defineStore } from 'pinia'
export interface State {
user: UserViewModel
}
export const useUserStore = defineStore('user', {
state: (): State => ({
user: {} as UserViewModel
}),
actions: {
updateUser(payload: UserViewModel) {
if (!payload) return;
this.user = payload;
},
}
})

View File

@ -1,7 +1,18 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
plugins: [
vue(),
Components({
resolvers: [
AntDesignVueResolver({
importStyle: false, // css in js
}),
],
}),
],
})