@coopenomics/desktop 2.2.9 → 2.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/package.json +5 -5
  3. package/src/boot/init-stores.ts +8 -2
  4. package/src/desktops/User/model/index.ts +11 -10
  5. package/src/entities/Account/api/index.ts +11 -2
  6. package/src/entities/Account/model/store.ts +15 -5
  7. package/src/entities/Account/types/index.ts +9 -1
  8. package/src/entities/Session/model/store.ts +2 -3
  9. package/src/features/Account/UpdateAccount/api/index.ts +14 -0
  10. package/src/features/Account/UpdateAccount/index.ts +1 -0
  11. package/src/features/Account/UpdateAccount/model/index.ts +16 -0
  12. package/src/features/Branch/SelectBranch/ui/SelectBranchOverlay.vue +3 -2
  13. package/src/features/User/AddUser/ui/AddUserDialog/AddUserDialog.vue +3 -0
  14. package/src/features/User/Logout/model/index.ts +3 -3
  15. package/src/pages/Cooperative/ListOfBranches/ui/ListOfBranchesPage.vue +2 -4
  16. package/src/shared/lib/composables/useEditableData.ts +1 -1
  17. package/src/shared/ui/EditableEntrepreneurCard/EditableEntrepreneurCard.vue +170 -0
  18. package/src/shared/ui/EditableEntrepreneurCard/index.ts +1 -0
  19. package/src/shared/ui/EditableIndividualCard/EditableIndividualCard.vue +203 -0
  20. package/src/shared/ui/EditableIndividualCard/index.ts +1 -0
  21. package/src/shared/ui/EditableOrganizationCard/EditableOrganizationCard.vue +215 -0
  22. package/src/shared/ui/EditableOrganizationCard/index.ts +1 -0
  23. package/src/shared/ui/UserDataForm/OrganizationDataForm/OrganizationDataForm.vue +1 -0
  24. package/src/widgets/Cooperative/Participants/ListOfParticipants/ui/ListOfParticipantsWidget.vue +154 -71
  25. package/src/widgets/IndividualCard/ui/IndividualCard.vue +23 -23
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.2.10](https://github.com/coopenomics/mono/compare/v2.2.9...v2.2.10) (2025-03-27)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * add Zeus type ([7bcb6e3](https://github.com/coopenomics/mono/commit/7bcb6e30a77b0ab89c5293188b58f08f19c8761e))
12
+
13
+
14
+
15
+
16
+
6
17
  ## [2.2.9](https://github.com/coopenomics/mono/compare/v2.2.8...v2.2.9) (2025-03-12)
7
18
 
8
19
  **Note:** Version bump only for package @coopenomics/desktop
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coopenomics/desktop",
3
- "version": "2.2.9",
3
+ "version": "2.2.10",
4
4
  "description": "A Desktop Project",
5
5
  "productName": "Desktop App",
6
6
  "author": "Alex Ant <dacom.dark.sun@gmail.com>",
@@ -20,8 +20,8 @@
20
20
  "prepublishOnly": "npm run build:lib"
21
21
  },
22
22
  "dependencies": {
23
- "@coopenomics/controller": "2.2.9",
24
- "@coopenomics/sdk": "2.2.9",
23
+ "@coopenomics/controller": "2.2.10",
24
+ "@coopenomics/sdk": "2.2.10",
25
25
  "@dicebear/collection": "^9.0.1",
26
26
  "@dicebear/core": "^9.0.1",
27
27
  "@fortawesome/fontawesome-svg-core": "^6.5.2",
@@ -45,7 +45,7 @@
45
45
  "@wharfkit/wallet-plugin-privatekey": "^1.1.0",
46
46
  "axios": "^1.2.1",
47
47
  "compression": "^1.7.4",
48
- "cooptypes": "2.2.9",
48
+ "cooptypes": "2.2.10",
49
49
  "dompurify": "^3.1.7",
50
50
  "dotenv": "^16.4.5",
51
51
  "email-regex": "^5.0.0",
@@ -94,5 +94,5 @@
94
94
  "npm": ">= 6.13.4",
95
95
  "yarn": ">= 1.21.1"
96
96
  },
97
- "gitHead": "51d736eba49ce8aaea6e58c40772254c0c194f46"
97
+ "gitHead": "e5e28ff652112fe3bacb8036a898a1344ad361fa"
98
98
  }
@@ -28,8 +28,14 @@ export default boot(async ({ router }) => {
28
28
  await cardStore.initWallet();
29
29
 
30
30
  // Загрузка аккаунта
31
- if (session.isAuth && session.username)
32
- await account.getAccount(session.username)
31
+ if (session.isAuth && session.username){
32
+ try {
33
+ await account.getAccount(session.username)
34
+ } catch(e){
35
+ session.close()
36
+ }
37
+ }
38
+
33
39
 
34
40
  // Добавление динамических маршрутов как дочерних к 'base'
35
41
  const baseRoute = router.getRoutes().find(route => route.name === 'base');
@@ -160,16 +160,6 @@ export const manifest = {
160
160
 
161
161
  },
162
162
 
163
- {
164
- path: '/:coopname/contacts',
165
- name: 'contacts',
166
- component: markRaw(ContactsPage),
167
- meta: {
168
- title: 'Контакты',
169
- icon: 'fa-solid fa-info',
170
- roles: [],
171
- },
172
- },
173
163
  // {
174
164
  // path: '/:coopname/marketplace',
175
165
  // name: 'marketplace',
@@ -251,6 +241,17 @@ export const manifest = {
251
241
  // },
252
242
  // ],
253
243
  // },
244
+ {
245
+ path: '/:coopname/contacts',
246
+ name: 'contacts',
247
+ component: markRaw(ContactsPage),
248
+ meta: {
249
+ title: 'Контакты',
250
+ icon: 'fa-solid fa-info',
251
+ roles: [],
252
+ },
253
+ },
254
+
254
255
  // {
255
256
  // meta: {
256
257
  // title: 'Поддержка',
@@ -1,6 +1,6 @@
1
1
  import { client } from 'src/shared/api/client';
2
2
  import { Queries } from '@coopenomics/sdk';
3
- import type { IAccount } from '../types';
3
+ import type { IAccount, IAccounts, IGetAccounts } from '../types';
4
4
 
5
5
  async function getAccount(username: string): Promise<IAccount | undefined> {
6
6
  const { [Queries.Accounts.GetAccount.name]: output } = await client.Query(Queries.Accounts.GetAccount.query, {
@@ -12,6 +12,15 @@ async function getAccount(username: string): Promise<IAccount | undefined> {
12
12
  return output;
13
13
  }
14
14
 
15
+ async function getAccounts(variables?: IGetAccounts): Promise<IAccounts> {
16
+ const { [Queries.Accounts.GetAccounts.name]: output } = await client.Query(Queries.Accounts.GetAccounts.query, {
17
+ variables
18
+ });
19
+
20
+ return output;
21
+ }
22
+
15
23
  export const api ={
16
- getAccount
24
+ getAccount,
25
+ getAccounts
17
26
  }
@@ -1,25 +1,35 @@
1
1
  import { defineStore } from 'pinia'
2
2
  import { ref, Ref } from 'vue'
3
3
  import { api } from '../api'
4
- import type { IAccount } from '../types';
4
+ import type { IAccount, IAccounts, IGetAccounts } from '../types';
5
5
 
6
6
  const namespace = 'accountStore';
7
7
 
8
- interface ISystemStore {
8
+ interface IAccountStore {
9
9
  account: Ref<IAccount | undefined>
10
+ accounts: Ref<IAccounts>
10
11
  getAccount: (username: string) => Promise<IAccount | undefined>;
12
+ getAccounts: (data?: IGetAccounts) => Promise<IAccounts>;
11
13
  }
12
14
 
13
- export const useAccountStore = defineStore(namespace, (): ISystemStore => {
15
+ export const useAccountStore = defineStore(namespace, (): IAccountStore => {
14
16
  const account = ref<IAccount>()
17
+ const accounts = ref<IAccounts>({items: [], totalCount: 0, totalPages: 0, currentPage: 1})
15
18
 
16
- const getAccount = async (username: string) => {
19
+ const getAccount = async (username: string): Promise<IAccount | undefined> => {
17
20
  account.value = await api.getAccount(username);
18
21
  return account.value
19
22
  };
23
+
24
+ const getAccounts = async(data?: IGetAccounts): Promise<IAccounts> => {
25
+ accounts.value = await api.getAccounts(data);
26
+ return accounts.value
27
+ }
20
28
 
21
29
  return {
22
30
  account,
23
- getAccount
31
+ accounts,
32
+ getAccount,
33
+ getAccounts
24
34
  }
25
35
  })
@@ -1,3 +1,11 @@
1
- import { Queries } from '@coopenomics/sdk';
1
+ import { Queries, Zeus } from '@coopenomics/sdk';
2
2
 
3
3
  export type IAccount = Queries.Accounts.GetAccount.IOutput[typeof Queries.Accounts.GetAccount.name]
4
+ export type IAccounts = Queries.Accounts.GetAccounts.IOutput[typeof Queries.Accounts.GetAccounts.name]
5
+ export type IGetAccounts = {data?: Queries.Accounts.GetAccounts.IInput['data'], options?: Queries.Accounts.GetAccounts.IInput['options']}
6
+
7
+ export const AccountTypes = Zeus.AccountType
8
+
9
+ export type IIndividualData = Zeus.ModelTypes['Individual']
10
+ export type IOrganizationData = Zeus.ModelTypes['Organization']
11
+ export type IEntrepreneurData = Zeus.ModelTypes['Entrepreneur']
@@ -57,8 +57,6 @@ export const useSessionStore = defineStore('session', (): ISessionStore => {
57
57
  await globalStore.init();
58
58
  isAuth.value = globalStore.hasCreditials;
59
59
 
60
- //TODO добавить более детальную проверку авторизации
61
-
62
60
  getInfo();
63
61
 
64
62
  try {
@@ -80,7 +78,8 @@ export const useSessionStore = defineStore('session', (): ISessionStore => {
80
78
  } catch (e: any) {
81
79
  console.error(e);
82
80
  FailAlert(e.message);
83
- //TODO logout
81
+ close()
82
+ globalStore.logout()
84
83
  }
85
84
  }
86
85
  };
@@ -0,0 +1,14 @@
1
+ import { client } from 'src/shared/api/client'
2
+ import { Mutations } from '@coopenomics/sdk'
3
+
4
+ async function updateAccount(data: Mutations.Accounts.UpdateAccount.IInput['data']): Promise<Mutations.Accounts.UpdateAccount.IOutput[[typeof Mutations.Accounts.UpdateAccount.name][number]]>{
5
+ const {[Mutations.Accounts.UpdateAccount.name]: result} = await client.Mutation(Mutations.Accounts.UpdateAccount.mutation, {variables: {
6
+ data
7
+ }})
8
+
9
+ return result
10
+ }
11
+
12
+ export const api = {
13
+ updateAccount
14
+ }
@@ -0,0 +1 @@
1
+ export * from './model'
@@ -0,0 +1,16 @@
1
+ import type { Mutations } from '@coopenomics/sdk';
2
+ import { api } from '../api'
3
+ import { IAccount } from 'src/entities/Account/types';
4
+
5
+ export type IUpdateAccountInput = Mutations.Accounts.UpdateAccount.IInput['data']
6
+
7
+ export function useUpdateAccount() {
8
+
9
+ async function updateAccount(data: IUpdateAccountInput): Promise<IAccount> {
10
+ const account = await api.updateAccount(data);
11
+
12
+ return account;
13
+ }
14
+
15
+ return { updateAccount };
16
+ }
@@ -60,8 +60,9 @@ div
60
60
  }
61
61
 
62
62
  const back = () => step.value--
63
-
64
- branchStore.loadPublicBranches({ coopname: system.info.coopname })
63
+
64
+ if (session.isAuth)
65
+ branchStore.loadPublicBranches({ coopname: system.info.coopname })
65
66
 
66
67
  const generate = async () => {
67
68
  isLoading.value = true
@@ -99,6 +99,7 @@ import { useSystemStore } from 'src/entities/System/model';
99
99
  const { info } = useSystemStore()
100
100
 
101
101
  import { notEmpty } from 'src/shared/lib/utils';
102
+ import { useAccountStore } from 'src/entities/Account/model';
102
103
 
103
104
  const { state, addUserState, clearUserData } = useRegistratorStore()
104
105
  const spread_minimum = ref(true) //TODO REPLACE IT!
@@ -204,6 +205,7 @@ const addUserNow = (userDataForm: any) => {
204
205
  userDataForm.validate().then(async (success: boolean) => {
205
206
  if (success) {
206
207
  try {
208
+ const accountStore = useAccountStore()
207
209
  loading.value = true
208
210
  await addUser()
209
211
  SuccessAlert('Пайщик добавлен в реестр, а приглашение отправлено на его email');
@@ -211,6 +213,7 @@ const addUserNow = (userDataForm: any) => {
211
213
  addUserState.created_at = ''
212
214
  clearUserData()
213
215
  loading.value = false
216
+ accountStore.getAccounts()
214
217
  } catch (e: any) {
215
218
  loading.value = false
216
219
  FailAlert(`Возникла ошибка: ${e.message}`)
@@ -1,13 +1,13 @@
1
1
  import { useSessionStore } from 'src/entities/Session'
2
2
  import { useGlobalStore } from 'src/shared/store'
3
- import { api } from '../api'
3
+ // import { api } from '../api'
4
4
  import { useCurrentUserStore } from 'src/entities/User'
5
5
 
6
6
  export function useLogoutUser() {
7
7
  async function logout(): Promise<void> {
8
8
  const global = useGlobalStore()
9
-
10
- if (global.tokens?.refresh.token) await api.logoutUser(global.tokens?.refresh.token)
9
+ //TODO: "начать с начала" при регистрации бажит на это - да и в целом пора бы маршруты срезать эти
10
+ // if (global.tokens?.refresh.token) await api.logoutUser(global.tokens?.refresh.token)
11
11
 
12
12
  await global.logout()
13
13
 
@@ -53,11 +53,9 @@ div
53
53
  BankDetailsCard(:bankDetails="props.row.bank_account")
54
54
  div.col-md-4.col-xs-12.q-pa-sm
55
55
  p.text-center.text-overline карточка председателя
56
- IndividualCard(:individual="props.row.trustee" :readonly="true").q-mt-sm
56
+ EditableIndividualCard(:participantData="props.row.trustee" :readonly="true").q-mt-sm
57
57
  div.text-wrap
58
58
  p.text-grey для замены председателя участка - измените его имя аккаунта в карточке участка на аккаунт одного из пайщиков.
59
-
60
-
61
59
  </template>
62
60
 
63
61
  <script lang="ts" setup>
@@ -67,7 +65,7 @@ import { useEditableTableRows } from 'src/shared/lib/composables/useEditableTabl
67
65
  import { CreateBranchButton } from 'src/features/Branch/CreateBranch';
68
66
  import { getNameFromUserData } from 'src/shared/lib/utils/getNameFromUserData';
69
67
  import { BranchCard } from 'src/widgets/BranchCard';
70
- import { IndividualCard } from 'src/widgets/IndividualCard';
68
+ import { EditableIndividualCard } from 'src/shared/ui/EditableIndividualCard';
71
69
  import { BankDetailsCard } from 'src/widgets/BankDetailsCard';
72
70
  import { useSystemStore } from 'src/entities/System/model';
73
71
  const { info } = useSystemStore()
@@ -21,7 +21,7 @@ export function useEditableData<T extends Record<string, any>>(
21
21
 
22
22
  // Автоматически отслеживаем изменения в editableData
23
23
  watch(
24
- () => editableData,
24
+ editableData,
25
25
  (newData) => {
26
26
  isEditing.value = !isEqual(newData.value, originalData.value); // Глубокое сравнение объектов
27
27
  validateForm(); // Проверка валидности формы при изменении данных
@@ -0,0 +1,170 @@
1
+ <template lang="pug">
2
+ q-form(ref="form")
3
+ q-input(
4
+ dense
5
+ v-model="data.last_name"
6
+ standout="bg-teal text-white"
7
+ label="Фамилия"
8
+ :readonly="readonly"
9
+ :rules="[val => notEmpty(val), val => validatePersonalName(val)]"
10
+ autocomplete="off"
11
+ )
12
+ q-input(
13
+ dense
14
+ v-model="data.first_name"
15
+ standout="bg-teal text-white"
16
+ label="Имя"
17
+ :readonly="readonly"
18
+ :rules="[val => notEmpty(val), val => validatePersonalName(val)]"
19
+ autocomplete="off"
20
+ )
21
+ q-input(
22
+ dense
23
+ v-model="data.middle_name"
24
+ standout="bg-teal text-white"
25
+ label="Отчество"
26
+ :readonly="readonly"
27
+ :rules="[val => validatePersonalName(val)]"
28
+ autocomplete="off"
29
+ )
30
+
31
+ q-input(
32
+ dense
33
+ v-model="data.birthdate"
34
+ standout="bg-teal text-white"
35
+ mask="date"
36
+ label="Дата рождения"
37
+ placeholder="Формат: год/месяц/день"
38
+ :readonly="readonly"
39
+ :rules="['date', val => notEmpty(val)]"
40
+ autocomplete="off"
41
+ )
42
+ template(v-slot:append)
43
+ q-icon(name="event" class="cursor-pointer" v-if="!readonly")
44
+ q-popup-proxy(cover transition-show="scale" transition-hide="scale")
45
+ q-date(v-model="data.birthdate")
46
+ .row.items-center.justify-end
47
+ q-btn(v-close-popup label="Закрыть" color="primary" flat)
48
+
49
+ q-input(
50
+ dense
51
+ v-model="data.phone"
52
+ standout="bg-teal text-white"
53
+ label="Номер телефона"
54
+ mask="+7 (###) ###-##-##"
55
+ fill-mask
56
+ :readonly="readonly"
57
+ :rules="[val => notEmpty(val), val => notEmptyPhone(val)]"
58
+ autocomplete="off"
59
+ )
60
+
61
+ q-select(
62
+ dense
63
+ v-model="data.country"
64
+ standout="bg-teal text-white"
65
+ label="Страна"
66
+ :options="[{ label: 'Российская Федерация', value: 'Российская Федерация' }]"
67
+ map-options
68
+ emit-value
69
+ :readonly="readonly"
70
+ :rules="[val => notEmpty(val)]"
71
+ )
72
+
73
+ q-input(
74
+ dense
75
+ v-model="data.city"
76
+ standout="bg-teal text-white"
77
+ label="Город"
78
+ :readonly="readonly"
79
+ :rules="[val => notEmpty(val)]"
80
+ autocomplete="off"
81
+ )
82
+
83
+ q-input(
84
+ dense
85
+ v-model="data.full_address"
86
+ standout="bg-teal text-white"
87
+ label="Адрес регистрации"
88
+ :readonly="readonly"
89
+ :rules="[val => notEmpty(val)]"
90
+ autocomplete="off"
91
+ )
92
+
93
+ q-input(
94
+ dense
95
+ v-model="data.details.inn"
96
+ standout="bg-teal text-white"
97
+ mask="############"
98
+ label="ИНН предпринимателя"
99
+ :readonly="readonly"
100
+ :rules="[val => notEmpty(val), val => (val.length === 10 || val.length === 12) || 'ИНН должен содержать 10 или 12 цифр']"
101
+ autocomplete="off"
102
+ )
103
+
104
+ q-input(
105
+ dense
106
+ v-model="data.details.ogrn"
107
+ standout="bg-teal text-white"
108
+ mask="###############"
109
+ label="ОГРНИП"
110
+ :readonly="readonly"
111
+ :rules="[val => notEmpty(val), val => (val.length === 13 || val.length === 15) || 'ОГРНИП должен содержать 13 или 15 цифр']"
112
+ autocomplete="off"
113
+ )
114
+
115
+ EditableActions(
116
+ v-if="!readonly"
117
+ :isEditing="isEditing"
118
+ :isDisabled="isDisabled"
119
+ @save="saveChanges"
120
+ @cancel="cancelChanges"
121
+ )
122
+ </template>
123
+
124
+ <script setup lang="ts">
125
+ import { ref } from 'vue';
126
+ import { useEditableData } from 'src/shared/lib/composables/useEditableData';
127
+ import { notEmpty, notEmptyPhone, validatePersonalName } from 'src/shared/lib/utils';
128
+ import { failAlert, SuccessAlert } from 'src/shared/api';
129
+ import { EditableActions } from 'src/shared/ui/EditableActions';
130
+ import { type IUpdateAccountInput, useUpdateAccount } from 'src/features/Account/UpdateAccount/model';
131
+ import { type IEntrepreneurData } from 'src/entities/Account/types';
132
+
133
+ const emit = defineEmits(['update']);
134
+ const { updateAccount } = useUpdateAccount();
135
+
136
+ const props = defineProps({
137
+ participantData: {
138
+ type: Object as () => IEntrepreneurData,
139
+ required: true
140
+ },
141
+ readonly: {
142
+ type: Boolean,
143
+ default: false
144
+ }
145
+ });
146
+
147
+ const localEntrepreneurData = ref(props.participantData);
148
+ const form = ref();
149
+
150
+ const handleSave = async () => {
151
+ try {
152
+ const account_data: IUpdateAccountInput = {
153
+ username: props.participantData.username,
154
+ entrepreneur_data: data.value,
155
+ };
156
+ await updateAccount(account_data);
157
+ emit('update', JSON.parse(JSON.stringify(data.value)));
158
+ SuccessAlert('Данные аккаунта обновлены');
159
+ } catch (e) {
160
+ console.log(e);
161
+ failAlert(e);
162
+ }
163
+ };
164
+
165
+ const { editableData: data, isEditing, isDisabled, saveChanges, cancelChanges } = useEditableData(
166
+ localEntrepreneurData.value,
167
+ handleSave,
168
+ form
169
+ );
170
+ </script>
@@ -0,0 +1 @@
1
+ export {default as EditableEntrepreneurCard} from './EditableEntrepreneurCard.vue'
@@ -0,0 +1,203 @@
1
+ <template lang="pug">
2
+ q-form(ref="form" v-if="data")
3
+ q-input(
4
+ dense
5
+ v-model="data.email"
6
+ standout="bg-teal text-white"
7
+ label="Email"
8
+ :readonly="readonly"
9
+ :rules="[val => validEmail(val)]"
10
+ autocomplete="off"
11
+ )
12
+ q-input(
13
+ dense
14
+ v-model="data.first_name"
15
+ standout="bg-teal text-white"
16
+ label="Имя"
17
+ :readonly="readonly"
18
+ :rules="[val => notEmpty(val)]"
19
+ autocomplete="off"
20
+ )
21
+
22
+ q-input(
23
+ dense
24
+ v-model="data.middle_name"
25
+ standout="bg-teal text-white"
26
+ label="Отчество"
27
+ :readonly="readonly"
28
+ :rules="[val => validatePersonalName(val)]"
29
+ autocomplete="off"
30
+ )
31
+
32
+ q-input(
33
+ dense
34
+ v-model="data.last_name"
35
+ standout="bg-teal text-white"
36
+ label="Фамилия"
37
+ :readonly="readonly"
38
+ :rules="[val => notEmpty(val), val => validatePersonalName(val)]"
39
+ autocomplete="off"
40
+ )
41
+
42
+ q-input(
43
+ dense
44
+ v-model="data.birthdate"
45
+ standout="bg-teal text-white"
46
+ mask="date"
47
+ label="Дата рождения"
48
+ placeholder="Формат: год/месяц/день"
49
+ :readonly="readonly"
50
+ :rules="['date', val => notEmpty(val)]"
51
+ autocomplete="off"
52
+ )
53
+ template(v-slot:append)
54
+ q-icon(name="event" class="cursor-pointer" v-if="!readonly")
55
+ q-popup-proxy(cover transition-show="scale" transition-hide="scale")
56
+ q-date(v-model="data.birthdate")
57
+ .row.items-center.justify-end
58
+ q-btn(v-close-popup label="Закрыть" color="primary" flat)
59
+
60
+ q-input(
61
+ dense
62
+ v-model="data.phone"
63
+ standout="bg-teal text-white"
64
+ label="Телефон"
65
+ :readonly="readonly"
66
+ :rules="[val => notEmpty(val)]"
67
+ autocomplete="off"
68
+ )
69
+
70
+ div(v-if="data.passport")
71
+ q-input(
72
+ dense
73
+ v-model="data.passport.code"
74
+ standout="bg-teal text-white"
75
+ label="Код подразделения"
76
+ mask="###-###"
77
+ :readonly="readonly"
78
+ :rules="[val => notEmpty(val), val => val.length === 7 || 'Код подразделения состоит из 6 цифр и тире (xxx-xxx)']"
79
+ autocomplete="off"
80
+ )
81
+
82
+ q-input(
83
+ dense
84
+ :model-value="data.passport.series"
85
+ @update:model-value="val => data.passport.series = Number(val)"
86
+ standout="bg-teal text-white"
87
+ label="Серия паспорта"
88
+ mask="####"
89
+ type="text"
90
+ :readonly="readonly"
91
+ :rules="[val => notEmpty(val), val => String(val).length === 4 || 'Серия должна состоять из 4 цифр']"
92
+ autocomplete="off"
93
+ )
94
+
95
+ q-input(
96
+ dense
97
+ :model-value="data.passport.number"
98
+ @update:model-value="val => data.passport.number = Number(val)"
99
+ standout="bg-teal text-white"
100
+ label="Номер паспорта"
101
+ mask="######"
102
+ type="text"
103
+ :readonly="readonly"
104
+ :rules="[val => notEmpty(val), val => String(val).length === 6 || 'Номер паспорта должен состоять из 6 цифр']"
105
+ autocomplete="off"
106
+ )
107
+
108
+
109
+
110
+ q-input(
111
+ dense
112
+ v-model="data.passport.issued_at"
113
+ standout="bg-teal text-white"
114
+ mask="date"
115
+ label="Дата выдачи"
116
+ :readonly="readonly"
117
+ :rules="['date', val => notEmpty(val)]"
118
+ autocomplete="off"
119
+ )
120
+ template(v-slot:append)
121
+ q-icon(name="event" class="cursor-pointer" v-if="!readonly")
122
+ q-popup-proxy(cover transition-show="scale" transition-hide="scale")
123
+ q-date(v-model="data.passport.issued_at")
124
+ .row.items-center.justify-end
125
+ q-btn(v-close-popup label="Закрыть" color="primary" flat)
126
+
127
+ q-input(
128
+ dense
129
+ v-model="data.passport.issued_by"
130
+ standout="bg-teal text-white"
131
+ label="Кем выдан"
132
+ :readonly="readonly"
133
+ :rules="[val => notEmpty(val)]"
134
+ autocomplete="off"
135
+ )
136
+
137
+ q-input(
138
+ dense
139
+ v-model="data.full_address"
140
+ standout="bg-teal text-white"
141
+ label="Адрес регистрации"
142
+ :readonly="readonly"
143
+ :rules="[val => notEmpty(val)]"
144
+ autocomplete="off"
145
+ )
146
+
147
+ EditableActions(
148
+ v-if="!readonly"
149
+ :isEditing="isEditing"
150
+ :isDisabled="isDisabled"
151
+ @save="saveChanges"
152
+ @cancel="cancelChanges"
153
+ )
154
+ </template>
155
+
156
+ <script lang="ts" setup>
157
+ import { ref } from 'vue';
158
+ import { useEditableData } from 'src/shared/lib/composables/useEditableData';
159
+ import { validEmail } from 'src/shared/lib/utils/validEmailRule';
160
+ import { validatePersonalName, notEmpty } from 'src/shared/lib/utils';
161
+ import { failAlert, SuccessAlert } from 'src/shared/api';
162
+ import { type IUpdateAccountInput, useUpdateAccount } from 'src/features/Account/UpdateAccount/model';
163
+ import { EditableActions } from 'src/shared/ui/EditableActions';
164
+ import { type IIndividualData } from 'src/entities/Account/types';
165
+
166
+ const { updateAccount } = useUpdateAccount()
167
+
168
+ const props = defineProps({
169
+ participantData: {
170
+ type: Object as () => IIndividualData,
171
+ required: true
172
+ },
173
+ readonly: {
174
+ type: Boolean,
175
+ default: false
176
+ }
177
+ });
178
+
179
+ const localParticipantData = ref(props.participantData);
180
+ const form = ref();
181
+ const emit = defineEmits(['update']);
182
+
183
+ const handleSave = async () => {
184
+ try {
185
+ const account_data: IUpdateAccountInput = {
186
+ username: props.participantData.username,
187
+ individual_data: data.value,
188
+ }
189
+ await updateAccount(account_data);
190
+ emit('update', JSON.parse(JSON.stringify(data.value)))
191
+ SuccessAlert('Данные аккаунта обновлены')
192
+ } catch (e: any) {
193
+ failAlert(e);
194
+ }
195
+ };
196
+
197
+ const { editableData: data, isEditing, isDisabled, saveChanges, cancelChanges } = useEditableData(
198
+ localParticipantData.value,
199
+ handleSave,
200
+ form
201
+ );
202
+ </script>
203
+
@@ -0,0 +1 @@
1
+ export {default as EditableIndividualCard} from './EditableIndividualCard.vue'
@@ -0,0 +1,215 @@
1
+ <template lang="pug">
2
+ q-form(ref="form" v-if="data")
3
+ q-select(
4
+ dense
5
+ v-model="data.type"
6
+ standout="bg-teal text-white"
7
+ label="Тип организации"
8
+ :options="[{ label: 'Потребительский Кооператив', value: 'coop' }, { label: 'Производственный Кооператив', value: 'prodcoop' }, { label: 'ООО', value: 'ooo' }]"
9
+ emit-value
10
+ map-options
11
+ :rules="[val => notEmpty(val)]"
12
+ :readonly="readonly"
13
+ )
14
+
15
+ q-input(
16
+ dense
17
+ v-model="data.short_name"
18
+ standout="bg-teal text-white"
19
+ label="Краткое наименование"
20
+ :rules="[val => notEmpty(val)]"
21
+ :readonly="readonly"
22
+ autocomplete="off"
23
+ )
24
+ q-input(
25
+ dense
26
+ v-model="data.full_name"
27
+ standout="bg-teal text-white"
28
+ label="Полное наименование"
29
+ :rules="[val => notEmpty(val)]"
30
+ :readonly="readonly"
31
+ autocomplete="off"
32
+ )
33
+
34
+ q-input(
35
+ dense
36
+ v-model="data.represented_by.last_name"
37
+ standout="bg-teal text-white"
38
+ label="Фамилия представителя"
39
+ :rules="[val => notEmpty(val), val => validatePersonalName(val)]"
40
+ :readonly="readonly"
41
+ autocomplete="off"
42
+ )
43
+ q-input(
44
+ dense
45
+ v-model="data.represented_by.first_name"
46
+ standout="bg-teal text-white"
47
+ label="Имя представителя"
48
+ :rules="[val => notEmpty(val), val => validatePersonalName(val)]"
49
+ :readonly="readonly"
50
+ autocomplete="off"
51
+ )
52
+ q-input(
53
+ dense
54
+ v-model="data.represented_by.middle_name"
55
+ standout="bg-teal text-white"
56
+ label="Отчество представителя"
57
+ :rules="[val => validatePersonalName(val)]"
58
+ :readonly="readonly"
59
+ autocomplete="off"
60
+ )
61
+ q-input(
62
+ dense
63
+ v-model="data.represented_by.based_on"
64
+ standout="bg-teal text-white"
65
+ label="На основании"
66
+ :rules="[val => notEmpty(val)]"
67
+ :readonly="readonly"
68
+ autocomplete="off"
69
+ )
70
+ q-input(
71
+ dense
72
+ v-model="data.represented_by.position"
73
+ standout="bg-teal text-white"
74
+ label="Должность представителя"
75
+ :rules="[val => notEmpty(val)]"
76
+ :readonly="readonly"
77
+ autocomplete="off"
78
+ )
79
+
80
+ q-input(
81
+ dense
82
+ v-model="data.phone"
83
+ standout="bg-teal text-white"
84
+ label="Телефон"
85
+ mask="+7 (###) ###-##-##"
86
+ fill-mask
87
+ :rules="[val => notEmpty(val), val => notEmptyPhone(val)]"
88
+ :readonly="readonly"
89
+ autocomplete="off"
90
+ )
91
+
92
+ q-input(
93
+ dense
94
+ v-model="data.country"
95
+ standout="bg-teal text-white"
96
+ label="Страна"
97
+ :rules="[val => notEmpty(val)]"
98
+ :readonly="readonly"
99
+ autocomplete="off"
100
+ )
101
+ q-input(
102
+ dense
103
+ v-model="data.city"
104
+ standout="bg-teal text-white"
105
+ label="Город"
106
+ :rules="[val => notEmpty(val)]"
107
+ :readonly="readonly"
108
+ autocomplete="off"
109
+ )
110
+ q-input(
111
+ dense
112
+ v-model="data.full_address"
113
+ standout="bg-teal text-white"
114
+ label="Юридический адрес"
115
+ :rules="[val => notEmpty(val)]"
116
+ :readonly="readonly"
117
+ autocomplete="off"
118
+ )
119
+ q-input(
120
+ dense
121
+ v-model="data.fact_address"
122
+ standout="bg-teal text-white"
123
+ label="Фактический адрес"
124
+ :rules="[val => notEmpty(val)]"
125
+ :readonly="readonly"
126
+ autocomplete="off"
127
+ )
128
+
129
+ q-input(
130
+ dense
131
+ v-model="data.details.inn"
132
+ standout="bg-teal text-white"
133
+ mask="############"
134
+ label="ИНН"
135
+ :rules="[val => notEmpty(val), val => (val.length === 10 || val.length === 12) || 'ИНН должен содержать 10 или 12 цифр']"
136
+ :readonly="readonly"
137
+ autocomplete="off"
138
+ )
139
+ q-input(
140
+ dense
141
+ v-model="data.details.ogrn"
142
+ standout="bg-teal text-white"
143
+ mask="###############"
144
+ label="ОГРН"
145
+ :rules="[val => notEmpty(val), val => (val.length === 13 || val.length === 15) || 'ОГРН должен содержать 13 или 15 цифр']"
146
+ :readonly="readonly"
147
+ autocomplete="off"
148
+ )
149
+ q-input(
150
+ dense
151
+ v-model="data.details.kpp"
152
+ standout="bg-teal text-white"
153
+ mask="#########"
154
+ label="КПП"
155
+ :rules="[val => notEmpty(val), val => val.length === 9 || 'КПП должен содержать 9 цифр']"
156
+ :readonly="readonly"
157
+ autocomplete="off"
158
+ )
159
+
160
+ EditableActions(
161
+ v-if="!readonly"
162
+ :isEditing="isEditing"
163
+ :isDisabled="isDisabled"
164
+ @save="saveChanges"
165
+ @cancel="cancelChanges"
166
+ )
167
+ </template>
168
+
169
+ <script lang="ts" setup>
170
+ import { ref } from 'vue';
171
+ import { useEditableData } from 'src/shared/lib/composables/useEditableData';
172
+ import { notEmpty, notEmptyPhone, validatePersonalName } from 'src/shared/lib/utils';
173
+ import { failAlert, SuccessAlert } from 'src/shared/api';
174
+ import { EditableActions } from 'src/shared/ui/EditableActions';
175
+ import { type IUpdateAccountInput, useUpdateAccount } from 'src/features/Account/UpdateAccount/model';
176
+ import { type IOrganizationData } from 'src/entities/Account/types';
177
+
178
+ const emit = defineEmits(['update']);
179
+ const { updateAccount } = useUpdateAccount();
180
+
181
+ const props = defineProps({
182
+ participantData: {
183
+ type: Object as () => IOrganizationData,
184
+ required: true
185
+ },
186
+ readonly: {
187
+ type: Boolean,
188
+ default: false
189
+ }
190
+ });
191
+
192
+ const localOrganizationData = ref(props.participantData);
193
+ const form = ref();
194
+
195
+ const handleSave = async () => {
196
+ try {
197
+ const account_data: IUpdateAccountInput = {
198
+ username: props.participantData.username,
199
+ organization_data: data.value,
200
+ };
201
+ await updateAccount(account_data);
202
+ emit('update', JSON.parse(JSON.stringify(data.value)));
203
+ SuccessAlert('Данные аккаунта обновлены');
204
+ } catch (e) {
205
+ console.log(e);
206
+ failAlert(e);
207
+ }
208
+ };
209
+ const { editableData: data, isEditing, isDisabled, saveChanges, cancelChanges } = useEditableData(
210
+ localOrganizationData.value,
211
+ handleSave,
212
+ form
213
+ );
214
+ </script>
215
+
@@ -0,0 +1 @@
1
+ export {default as EditableOrganizationCard} from './EditableOrganizationCard.vue'
@@ -7,6 +7,7 @@ div(v-if="userData.organization_data").q-gutter-sm.q-mt-md
7
7
  :options="[{ label: 'Потребительский Кооператив', value: 'coop' }, { label: 'Производственный Кооператив', value: 'prodcoop' }, { label: 'ООО', value: 'ooo' }]"
8
8
  emit-value
9
9
  map-options).q-mb-md
10
+
10
11
  q-input(v-model="userData.organization_data.short_name" standout="bg-teal text-white" hint="ПК Ромашка" label="Краткое наименование организации" :rules="[val => notEmpty(val)]" autocomplete="off")
11
12
  q-input(v-model="userData.organization_data.full_name" standout="bg-teal text-white" hint="Потребительский Кооператив 'Ромашка'" label="Полное наименование организации" :rules="[val => notEmpty(val)]" autocomplete="off")
12
13
  q-input(v-model="userData.organization_data.represented_by.last_name" standout="bg-teal text-white" label="Фамилия представителя" hint="" :rules="[val => notEmpty(val), val => validatePersonalName(val)]" autocomplete="off")
@@ -1,10 +1,10 @@
1
1
  <template lang="pug">
2
+
2
3
  q-table(
3
- ref="tableRef" v-model:expanded="expanded"
4
+ ref="tableRef"
4
5
  flat
5
- :rows="participants.results"
6
+ :rows="accountStore.accounts.items"
6
7
  :columns="columns"
7
- :table-colspan="9"
8
8
  row-key="username"
9
9
  :pagination="pagination"
10
10
  virtual-scroll
@@ -15,12 +15,10 @@ q-table(
15
15
  ).full-height
16
16
  template(#top)
17
17
  slot(name="top")
18
-
18
+
19
19
  template(#header="props")
20
-
21
20
  q-tr(:props="props")
22
21
  q-th(auto-width)
23
-
24
22
  q-th(
25
23
  v-for="col in props.cols"
26
24
  :key="col.name"
@@ -28,76 +26,161 @@ q-table(
28
26
  ) {{ col.label }}
29
27
 
30
28
  template(#body="props")
29
+
31
30
  q-tr(:key="`m_${props.row.username}`" :props="props")
32
31
  q-td(auto-width)
33
- // q-toggle(v-model="props.expand" checked-icon="fas fa-chevron-circle-left" unchecked-icon="fas fa-chevron-circle-right" )
34
- q-btn(size="sm" color="primary" round dense :icon="props.expand ? 'remove' : 'add'" @click="props.expand = !props.expand")
32
+ q-btn(
33
+ size="sm"
34
+ color="primary"
35
+ round
36
+ dense
37
+ :icon="expanded.get(props.row.username) ? 'remove' : 'add'"
38
+ @click="toggleExpand(props.row.username)"
39
+ )
40
+
41
+ q-td {{ getName(props.row) }}
42
+ q-td {{ props.row.private_account.type === AccountTypes.Individual ? 'физ. лицо' : (props.row.private_account.type === AccountTypes.Organization ? 'юр. лицо' : (props.row.private_account.type === AccountTypes.Entrepreneur ? 'инд. предприниматель' : 'неизвестно')) }}
35
43
  q-td {{ props.row.username }}
36
- q-td {{ props.row.private_data?.last_name }}
37
- q-td {{ props.row.private_data?.first_name }}
38
- q-td {{ props.row.private_data?.middle_name }}
39
- q-td {{ props.row.private_data?.phone }}
40
- q-td {{ props.row.private_data?.email }}
41
- q-td {{ moment(props.row.private_data?.birthdate).format('DD.MM.YY') }}
42
- q-td {{ moment(props.row.private_data?._created_at).format('DD.MM.YY HH:mm:ss') }}
43
-
44
-
45
- q-tr(v-show="props.expand" :key="`e_${props.row.username}`" :props="props" class="q-virtual-scroll--with-prev")
46
- q-td(colspan="100%")
47
- slot(:expand="props.expand" :receiver="props.row.username")
48
-
44
+ q-td {{ moment(props.row.blockchain_account?.created).format('DD.MM.YY HH:mm:ss') }}
45
+
46
+ q-tr(v-if="expanded.get(props.row.username)" :key="`e_${props.row.username}`" :props="props" class="q-virtual-scroll--with-prev")
47
+ q-td(colspan="100%" style="padding: 0 !important;")
48
+ q-tabs(
49
+ v-model="currentTab[props.row.username]"
50
+ align="justify"
51
+ stretch
52
+ dense
53
+ indicator-color="lime"
54
+ )
55
+ q-tab(name="info" label="Данные" class="bg-primary text-white")
56
+ q-tab(name="document" label="Документы" class="bg-primary text-white")
57
+
58
+ q-tab-panels(v-model="currentTab[props.row.username]" animated)
59
+ q-tab-panel(name="info")
60
+ component(:is="useComponent(props.row)" :participantData="usePrivateData(props.row)" @update="newData => update(props.row, newData)")
61
+
62
+ q-tab-panel(name="document")
63
+ slot(:expand="expanded.get(props.row.username)" :receiver="props.row.username")
64
+
49
65
  </template>
50
-
51
66
  <script setup lang="ts">
52
- import { ref } from 'vue'
53
- import { Notify } from 'quasar'
54
-
55
- import { sendGET } from 'src/shared/api';
56
- const participants = ref({ results: [] })
57
- const onLoading = ref(false)
58
-
59
- import moment from 'moment-with-locales-es6'
60
-
61
- const loadParticipants = async () => {
62
- try {
63
- onLoading.value = true
64
-
65
- participants.value = await sendGET('/v1/users', {
66
- limit: 100
67
- })
68
-
69
- onLoading.value = false
70
- } catch (e: any) {
71
- onLoading.value = false
72
- Notify.create({
73
- message: e.message,
74
- type: 'negative',
75
- })
67
+ import { ref, reactive } from 'vue'
68
+ import { Notify } from 'quasar'
69
+ import { EditableEntrepreneurCard } from 'src/shared/ui/EditableEntrepreneurCard';
70
+ import { EditableIndividualCard } from 'src/shared/ui/EditableIndividualCard';
71
+ import { EditableOrganizationCard } from 'src/shared/ui/EditableOrganizationCard';
72
+ import { useAccountStore } from 'src/entities/Account/model';
73
+ import moment from 'moment-with-locales-es6'
74
+ import { AccountTypes, type IAccount, type IIndividualData, type IOrganizationData, type IEntrepreneurData } from 'src/entities/Account/types';
75
+
76
+ const accountStore = useAccountStore()
77
+
78
+ const onLoading = ref(false)
79
+
80
+ const update = (
81
+ account: IAccount,
82
+ newData: IIndividualData | IOrganizationData | IEntrepreneurData
83
+ ) => {
84
+
85
+ if (account.private_account?.type === AccountTypes.Individual) {
86
+ const individual = newData as IIndividualData;
87
+ account.private_account.individual_data = {
88
+ ...individual,
89
+ passport: individual.passport ?? undefined, // заменяет null на undefined
90
+ };
91
+ } else if (account.private_account?.type === AccountTypes.Entrepreneur) {
92
+ account.private_account.entrepreneur_data = newData as IEntrepreneurData
93
+ } else if (account.private_account?.type === AccountTypes.Organization) {
94
+ account.private_account.organization_data = newData as IOrganizationData
95
+ }
96
+
97
+ };
98
+
99
+
100
+ // `Map` для отслеживания состояния раскрытых строк и вкладок
101
+ const expanded = reactive(new Map<string, boolean>())
102
+ const currentTab = reactive<Record<string, string>>({})
103
+
104
+ // Определяем, какой компонент использовать
105
+ const useComponent = (account: IAccount) => {
106
+ if (account.private_account)
107
+ switch (account.private_account.type) {
108
+ case AccountTypes.Individual:
109
+ return EditableIndividualCard
110
+ case AccountTypes.Entrepreneur:
111
+ return EditableEntrepreneurCard
112
+ case AccountTypes.Organization:
113
+ return EditableOrganizationCard
114
+ }
115
+ }
116
+
117
+ const usePrivateData = (account: IAccount) => {
118
+ if (account.private_account)
119
+ switch (account.private_account.type) {
120
+ case AccountTypes.Individual:
121
+ return account.private_account?.individual_data
122
+ case AccountTypes.Entrepreneur:
123
+ return account.private_account?.entrepreneur_data
124
+ case AccountTypes.Organization:
125
+ return account.private_account?.organization_data
126
+ }
127
+ }
128
+
129
+ // Загружаем данные
130
+ const loadParticipants = async () => {
131
+ try {
132
+ onLoading.value = true
133
+
134
+ await accountStore.getAccounts({options: {
135
+ page: 1,
136
+ limit: 1000,
137
+ sortOrder: 'DESC'
138
+ }})
139
+
140
+ onLoading.value = false
141
+ } catch (e: any) {
142
+ onLoading.value = false
143
+ Notify.create({
144
+ message: e.message,
145
+ type: 'negative',
146
+ })
147
+ }
76
148
  }
77
- }
78
-
79
- loadParticipants()
80
-
81
- const columns = [
82
- { name: 'username', align: 'left', label: 'Аккаунт', field: 'username', sortable: true },
83
- { name: 'last_name', align: 'left', label: 'Фамилия', field: 'last_name', sortable: true },
84
- { name: 'first_name', align: 'left', label: 'Имя', field: 'first_name', sortable: true },
85
- { name: 'middle_name', align: 'left', label: 'Отчество', field: 'middle_name', sortable: true },
86
-
87
- { name: 'phone', align: 'left', label: 'Телефон', field: 'phone', sortable: false },
88
- { name: 'email', align: 'left', label: 'Е-почта', field: 'email', sortable: false },
89
- { name: 'birthday', align: 'left', label: 'Дата рождения', field: 'birthday', sortable: true },
90
- {
91
- name: 'created_at',
92
- align: 'left',
93
- label: 'Зарегистрирован',
94
- field: 'created_at',
95
- sortable: true,
96
- },
97
- ] as any
98
149
 
99
- const expanded = ref([])
100
- const tableRef = ref(null)
101
- const pagination = ref({ rowsPerPage: 10 })
150
+ loadParticipants()
151
+
152
+ const columns = [
153
+ { name: 'name', align: 'left', label: 'ФИО / Наименование', field: 'name', sortable: true },
154
+ { name: 'type', align: 'left', label: 'Тип', field: 'type' },
155
+ { name: 'username', align: 'left', label: 'Аккаунт', field: 'username', sortable: true },
156
+ {
157
+ name: 'created_at',
158
+ align: 'left',
159
+ label: 'Зарегистрирован',
160
+ field: 'created_at',
161
+ sortable: true,
162
+ },
163
+ ] as any
164
+
165
+ const tableRef = ref(null)
166
+ const pagination = ref({ rowsPerPage: 10 })
167
+
168
+ // Функция для переключения раскрытия строки
169
+ const toggleExpand = (id: string) => {
170
+ expanded.set(id, !expanded.get(id))
171
+ if (!currentTab[id]) {
172
+ currentTab[id] = 'info' // Устанавливаем вкладку по умолчанию
173
+ }
174
+ }
175
+
176
+ const getName = (account: IAccount) => {
177
+ if (account?.private_account?.type === AccountTypes.Individual){
178
+ return `${account.private_account.individual_data?.last_name} ${account.private_account.individual_data?.first_name} ${account.private_account.individual_data?.middle_name}`
179
+ } else if (account?.private_account?.type === AccountTypes.Entrepreneur){
180
+ return `${account.private_account.entrepreneur_data?.last_name} ${account.private_account.entrepreneur_data?.first_name} ${account.private_account.entrepreneur_data?.middle_name}`
181
+ } else if (account?.private_account?.type === AccountTypes.Organization){
182
+ return `${account.private_account.organization_data?.short_name}`
183
+ }
184
+ }
102
185
 
103
- </script>
186
+ </script>
@@ -142,29 +142,29 @@ div
142
142
  )
143
143
  </template>
144
144
 
145
- <script lang="ts" setup>
146
- import { useEditableData } from 'src/shared/lib/composables/useEditableData';
147
- import { notEmpty, notEmptyPhone } from 'src/shared/lib/utils';
148
- import { validEmail } from 'src/shared/lib/utils/validEmailRule';
149
- import { validatePersonalName } from 'src/shared/lib/utils';
150
- import { EditableActions } from 'src/shared/ui/EditableActions';
151
- import { Cooperative } from 'cooptypes';
145
+ <script lang="ts" setup>
146
+ import { useEditableData } from 'src/shared/lib/composables/useEditableData';
147
+ import { notEmpty, notEmptyPhone } from 'src/shared/lib/utils';
148
+ import { validEmail } from 'src/shared/lib/utils/validEmailRule';
149
+ import { validatePersonalName } from 'src/shared/lib/utils';
150
+ import { EditableActions } from 'src/shared/ui/EditableActions';
151
+ import { Cooperative } from 'cooptypes';
152
152
 
153
- const props = defineProps({
154
- individual: {
155
- type: Object as () => Cooperative.Users.IIndividualData,
156
- required: true
157
- },
158
- readonly: {
159
- type: Boolean,
160
- required: false,
161
- default: true
162
- }
163
- });
153
+ const props = defineProps({
154
+ individual: {
155
+ type: Object as () => Cooperative.Users.IIndividualData,
156
+ required: true
157
+ },
158
+ readonly: {
159
+ type: Boolean,
160
+ required: false,
161
+ default: true
162
+ }
163
+ });
164
164
 
165
- const handleSave = (updatedIndividual: Cooperative.Users.IIndividualData) => {
166
- console.log('Сохранено:', updatedIndividual);
167
- };
165
+ const handleSave = (updatedIndividual: Cooperative.Users.IIndividualData) => {
166
+ console.log('Сохранено:', updatedIndividual);
167
+ };
168
168
 
169
- const { editableData: data, isEditing, saveChanges, cancelChanges } = useEditableData(props.individual, handleSave);
170
- </script>
169
+ const { editableData: data, isEditing, saveChanges, cancelChanges } = useEditableData(props.individual, handleSave);
170
+ </script>