add, fix: Я НЕ ЗНАЮ НАДО СРОЧНО ВСЕ ДЕЛАТЬ
This commit is contained in:
parent
9d2f2be6ee
commit
dffe2a6655
@ -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; }
|
||||
}
|
@ -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;
|
||||
|
@ -10,6 +10,7 @@ public static class ChangeRecordMapper
|
||||
{
|
||||
Id = dto.Id,
|
||||
Sum = dto.Sum,
|
||||
ChangedAt = dto.ChangedAt
|
||||
ChangedAt = dto.ChangedAt,
|
||||
SpendingGroupName = dto.SpendingGroupName ?? string.Empty
|
||||
};
|
||||
}
|
@ -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; }
|
||||
}
|
@ -4,4 +4,5 @@ public class SpendingGroupSearch
|
||||
{
|
||||
public Guid? Id { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
}
|
@ -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);
|
||||
}
|
@ -5,4 +5,5 @@ public class ChangeRecordViewModel
|
||||
public Guid Id { get; set; }
|
||||
public decimal Sum { get; set; }
|
||||
public DateTime ChangedAt { get; set; }
|
||||
public string SpendingGroupName { get; set; } = string.Empty;
|
||||
}
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
14
front/components.d.ts
vendored
14
front/components.d.ts
vendored
@ -9,6 +9,8 @@ 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']
|
||||
@ -20,12 +22,24 @@ declare module 'vue' {
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
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']
|
||||
}
|
||||
}
|
||||
|
44
front/package-lock.json
generated
44
front/package-lock.json
generated
@ -8,7 +8,9 @@
|
||||
"name": "dombudg",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@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"
|
||||
@ -895,6 +897,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
|
||||
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.0.tgz",
|
||||
@ -1080,6 +1088,42 @@
|
||||
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vueuse/core": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.0.0.tgz",
|
||||
"integrity": "sha512-C12RukhXiJCbx4MGhjmd/gH52TjJsc3G0E0kQj/kb19H3Nt6n1CA4DRWuTdWWcaFRdlTe0npWDS942mvacvNBw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.20",
|
||||
"@vueuse/metadata": "12.0.0",
|
||||
"@vueuse/shared": "12.0.0",
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/metadata": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.0.0.tgz",
|
||||
"integrity": "sha512-Yzimd1D3sjxTDOlF05HekU5aSGdKjxhuhRFHA7gDWLn57PRbBIh+SF5NmjhJ0WRgF3my7T8LBucyxdFJjIfRJQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.0.0.tgz",
|
||||
"integrity": "sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vue": "^3.5.13"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
|
@ -10,7 +10,9 @@
|
||||
"gen-api": "swagger-typescript-api -r -o ./src/core/api/ --modular -p "
|
||||
},
|
||||
"dependencies": {
|
||||
"@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"
|
||||
|
@ -6,16 +6,16 @@ import Header from './components/main/Header.vue';
|
||||
<a-layout class="layout">
|
||||
<Header />
|
||||
<a-layout-content>
|
||||
<RouterView />
|
||||
<a-flex gap="middle" vertical>
|
||||
<RouterView />
|
||||
</a-flex>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 80vh;
|
||||
padding: 36px;
|
||||
margin: 14px;
|
||||
}
|
||||
</style>
|
@ -5,7 +5,7 @@ import { AuthService } from '../../core/services/auth-service';
|
||||
import router from '../../router';
|
||||
|
||||
const store = useUserStore();
|
||||
const authService = inject(typeof(AuthService)) as AuthService;
|
||||
const authService = inject(AuthService.name) as AuthService;
|
||||
|
||||
function logout() {
|
||||
authService.logout();
|
||||
@ -20,13 +20,14 @@ function logout() {
|
||||
<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 }}!</label>
|
||||
<label for="logout">Привет, {{ store.user.name }}! Ваш текущий баланс: {{ store.user.balance }}</label>
|
||||
<a-button
|
||||
name="logout"
|
||||
@click="logout()"
|
||||
|
47
front/src/components/pages/Groups.vue
Normal file
47
front/src/components/pages/Groups.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<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';
|
||||
|
||||
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",
|
||||
}
|
||||
]
|
||||
|
||||
const refreshData = () => {
|
||||
groupService.getList().then(data => {
|
||||
state.value = data;
|
||||
isReady.value = true;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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>
|
||||
</a-table>
|
||||
<div v-else>
|
||||
<a-spin size="large" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -1,9 +1,45 @@
|
||||
<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 { ChangeRecordViewModel } from '../../core/api/data-contracts';
|
||||
|
||||
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',
|
||||
}
|
||||
]
|
||||
|
||||
const refreshData = () => {
|
||||
changeRecordService.getList().then(data => {
|
||||
state.value = data;
|
||||
isReady.value = true;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
test!
|
||||
<ChangeRecordMenu :refreshData="refreshData" />
|
||||
<a-table :dataSource="state" :columns="columns" v-if="isReady" />
|
||||
<div v-else>
|
||||
<a-spin size="large" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
@ -50,8 +50,8 @@ const formState = reactive<UserLoginDto>({
|
||||
name: '',
|
||||
password: '',
|
||||
});
|
||||
const authService = inject(typeof(AuthService)) as AuthService;
|
||||
|
||||
const authService = inject(AuthService.name) as AuthService;
|
||||
console.log(authService);
|
||||
// Логика формы
|
||||
const onFinish = async (values: any) => {
|
||||
console.log('Success:', values);
|
||||
|
52
front/src/components/pages/Plans.vue
Normal file
52
front/src/components/pages/Plans.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<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';
|
||||
|
||||
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",
|
||||
}
|
||||
]
|
||||
|
||||
const refreshData = () => {
|
||||
planService.getList(groupId).then(data => {
|
||||
state.value = data;
|
||||
isReady.value = true;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>Планы группы </h1>
|
||||
<PlanManager :groupId="groupId" :refreshData="refreshData"/>
|
||||
<a-table :dataSource="state" :columns="columns" v-if="isReady">
|
||||
</a-table>
|
||||
<div v-else>
|
||||
<a-spin size="large" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -35,7 +35,7 @@
|
||||
name="balance"
|
||||
:rules="[{ required: true, message: 'Пожалуйста, введите свой баланс' }]"
|
||||
>
|
||||
<a-input-number prefix="₽" v-model:value="formState.balance" />
|
||||
<a-input-number prefix="₽" v-model:value="formState.balance" min="0"/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
|
92
front/src/components/support/ChangeRecordManager.vue
Normal file
92
front/src/components/support/ChangeRecordManager.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<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"
|
||||
>
|
||||
|
||||
<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="Дата"
|
||||
>
|
||||
<a-date-picker v-model:value="pickedChangedAt" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-button 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 { ChangeRecordDto, SpendingGroupViewModel } from '../../core/api/data-contracts';
|
||||
import { useUserStore } from '../../store';
|
||||
import router from '../../router';
|
||||
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: 0,
|
||||
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;
|
||||
});
|
||||
|
||||
</script>
|
77
front/src/components/support/PlanManager.vue
Normal file
77
front/src/components/support/PlanManager.vue
Normal file
@ -0,0 +1,77 @@
|
||||
<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 html-type="submit" type="primary">Добавить</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-space>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { inject, reactive, ref } from 'vue';
|
||||
import { SpendingPlanDto } from '../../core/api/data-contracts';
|
||||
import { ChangeRecordService } from '../../core/services/change-record-service';
|
||||
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);
|
||||
};
|
||||
</script>
|
53
front/src/components/support/SpendingGroupManager.vue
Normal file
53
front/src/components/support/SpendingGroupManager.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<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 html-type="submit" type="primary">Сохранить</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-space>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { 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);
|
||||
};
|
||||
</script>
|
@ -124,6 +124,8 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
|
||||
From?: string;
|
||||
/** @format date-time */
|
||||
To?: string;
|
||||
/** @format uuid */
|
||||
UserId?: string;
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
@ -151,6 +153,8 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
|
||||
From?: string;
|
||||
/** @format date-time */
|
||||
To?: string;
|
||||
/** @format uuid */
|
||||
UserId?: string;
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
@ -175,6 +179,8 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
|
||||
/** @format uuid */
|
||||
Id?: string;
|
||||
Name?: string;
|
||||
/** @format uuid */
|
||||
UserId?: string;
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
@ -247,6 +253,8 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
|
||||
/** @format uuid */
|
||||
Id?: string;
|
||||
Name?: string;
|
||||
/** @format uuid */
|
||||
UserId?: string;
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
@ -269,6 +277,8 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
|
||||
/** @format uuid */
|
||||
Id?: string;
|
||||
Name?: string;
|
||||
/** @format uuid */
|
||||
UserId?: string;
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
@ -402,7 +412,7 @@ export class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType
|
||||
* @request GET:/api/User
|
||||
* @response `200` `UserViewModel` Success
|
||||
*/
|
||||
userList = (
|
||||
userGet = (
|
||||
query?: {
|
||||
/** @format uuid */
|
||||
Id?: string;
|
||||
|
@ -16,6 +16,7 @@ export interface ChangeRecordDto {
|
||||
userId?: string;
|
||||
/** @format uuid */
|
||||
spendingGroupId?: string | null;
|
||||
spendingGroupName?: string | null;
|
||||
/** @format double */
|
||||
sum?: number;
|
||||
/** @format date-time */
|
||||
@ -29,6 +30,7 @@ export interface ChangeRecordViewModel {
|
||||
sum?: number;
|
||||
/** @format date-time */
|
||||
changedAt?: string;
|
||||
spendingGroupName?: string | null;
|
||||
}
|
||||
|
||||
export interface SpendingGroupDto {
|
||||
|
30
front/src/core/services/change-record-service.ts
Normal file
30
front/src/core/services/change-record-service.ts
Normal file
@ -0,0 +1,30 @@
|
||||
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;
|
||||
}
|
||||
|
||||
async createRecord(data: ChangeRecordDto) {
|
||||
let result = await this._api.changeRecordCreate(data);
|
||||
const store = useUserStore();
|
||||
let updatedUser = await this._api.userGet({ Id: store.user.id });
|
||||
|
||||
store.updateUser(updatedUser.data);
|
||||
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;
|
||||
}
|
||||
}
|
27
front/src/core/services/group-service.ts
Normal file
27
front/src/core/services/group-service.ts
Normal file
@ -0,0 +1,27 @@
|
||||
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 createGroup(data: SpendingGroupDto) {
|
||||
let result = await this._api.spendingGroupCreate(data);
|
||||
console.log(result);
|
||||
}
|
||||
}
|
18
front/src/core/services/plans-service.ts
Normal file
18
front/src/core/services/plans-service.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Api } from "../api/Api";
|
||||
import { SpendingPlanDto, SpendingPlanViewModel } from "../api/data-contracts";
|
||||
|
||||
export class PlanService {
|
||||
async createPlan(data: SpendingPlanDto) {
|
||||
const result = await this._api.spendingPlanCreate(data);
|
||||
console.log(result);
|
||||
}
|
||||
private readonly _api: Api
|
||||
constructor(api: Api) {
|
||||
this._api = api;
|
||||
}
|
||||
|
||||
async getList(groupId: string): Promise<SpendingPlanViewModel[] | null> {
|
||||
const result = await this._api.spendingGroupDetail(groupId);
|
||||
return result.data.spendingPlans || null;
|
||||
}
|
||||
}
|
@ -5,6 +5,9 @@ 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'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
@ -13,6 +16,9 @@ app.use(createPinia())
|
||||
|
||||
// Di
|
||||
const api = new Api();
|
||||
app.provide(typeof(AuthService), new AuthService(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')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { useUserStore } from './store';
|
||||
|
||||
export default createRouter({
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{
|
||||
@ -12,11 +13,34 @@ export default createRouter({
|
||||
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;
|
Loading…
Reference in New Issue
Block a user