Skip to content

Commit

Permalink
Merge pull request #33 from moevm/users-actions
Browse files Browse the repository at this point in the history
Users actions
  • Loading branch information
vladDotH authored Dec 9, 2023
2 parents eb7769a + cd39626 commit 0fbd7fa
Show file tree
Hide file tree
Showing 20 changed files with 639 additions and 174 deletions.
331 changes: 178 additions & 153 deletions client/package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"lint": "eslint --fix 'src/**/*.ts' 'src/**/*.vue'"
},
"dependencies": {
"@formkit/themes": "^1.0.0-beta.15",
"@formkit/vue": "^1.0.0-beta.15",
"@formkit/themes": "^1.0.0",
"@formkit/vue": "^1.0.0",
"@geoman-io/leaflet-geoman-free": "^2.15.0",
"@popperjs/core": "^2.11.6",
"@vueuse/core": "^9.13.0",
Expand All @@ -22,6 +22,7 @@
"bootstrap": "^5.2.3",
"bootstrap-icons": "^1.10.3",
"leaflet": "^1.9.3",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"npm-run-all": "^4.1.5",
"pinia": "^2.0.32",
Expand All @@ -32,6 +33,7 @@
"devDependencies": {
"@types/bootstrap": "^5.2.6",
"@types/leaflet": "^1.9.1",
"@types/lodash": "^4.14.202",
"@types/node": "^18.14.1",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
Expand Down
5 changes: 3 additions & 2 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
>
<span class="navbar-toggler-icon"></span>
</button>
<div id="navbar" class="collapse navbar-collapse fs-5">
<div id="navbar" class="collapse navbar-collapse fs-6">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li v-for="route of routes" :key="route" class="nav-item ps-4">
<router-link
Expand Down Expand Up @@ -84,14 +84,15 @@ const routes = computed(() => [
routeNames.MapsList,
routeNames.Queue,
routeNames.ObjectsList,
...(userStore.isAuthed ? [routeNames.Upload] : []),
...(userStore.isAuthed ? [routeNames.Upload, routeNames.Profile] : []),
]);
const routesTranslation = {
[routeNames.Map]: "Глобальная карта",
[routeNames.MapsList]: "Карты",
[routeNames.Queue]: "Очередь",
[routeNames.ObjectsList]: "Объекты",
[routeNames.Upload]: "Загрузить",
[routeNames.Profile]: "Профиль",
};
async function logout() {
Expand Down
44 changes: 44 additions & 0 deletions client/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import axios, { AxiosInstance } from "axios";
import { useLoadingStore } from "@/store/loading";
import { useToaster } from "@/store/toaster";
import { ToastTypes } from "@/config/toast";

const host = import.meta.env.CLIENT_SERVER_URL ?? "localhost";
const port = import.meta.env.CLIENT_SERVER_PORT ?? "5000";
Expand All @@ -16,6 +19,47 @@ const max_map_zoom = import.meta.env.MAX_ZOOM ?? 15;

export const map_zoom = [min_map_zoom, max_map_zoom];

api.interceptors.request.use(async (req) => {
const loadingStore = useLoadingStore();
loadingStore.enqueue();

return req;
});

api.interceptors.response.use(
async (res) => {
const loadingStore = useLoadingStore();
loadingStore.dequeue();
return res;
},
async (error) => {
const loadingStore = useLoadingStore(),
toaster = useToaster();

loadingStore.dequeue();

let data: any;
if (error?.response?.data?.constructor === ArrayBuffer) {
try {
data = JSON.parse(new TextDecoder().decode(error?.response?.data));
} catch {
data = {};
}
} else {
data = error?.response?.data;
}

console.error(error?.response);
toaster.addToast({
title: "Ошибка",
body: data?.message ?? data ?? "Ошибка сервера",
type: ToastTypes.danger,
});

throw error;
}
);

export const objectTypesColors: Map<string, string> = new Map();
objectTypesColors.set("Лес", "green");
objectTypesColors.set("Вырубка", "red");
Expand Down
4 changes: 4 additions & 0 deletions client/src/components/common/Modal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const props = withDefaults(
{ backdrop: true }
);
const emit = defineEmits<{ (e: "opened"): void; (e: "closed"): void }>();
const modalEl = ref<HTMLDivElement | null>(null),
modal = ref<bootstrap.Modal | null>(null);
Expand All @@ -39,10 +41,12 @@ onMounted(() => {
function open() {
modal.value?.show();
emit("opened");
}
function close() {
modal.value?.hide();
emit("closed");
}
defineExpose({ open, close, modal: readonly(modal) });
Expand Down
56 changes: 56 additions & 0 deletions client/src/components/routes/profile/EditProfileModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<template>
<Modal ref="modal" backdrop="static" @opened="onOpened">
<template #header="{ close }">
<h4 class="modal-title">Редактировать данные</h4>
<button type="button" class="btn-close" @click="close"></button>
</template>

<template v-if="user" #body>
<FormKit v-model="user" type="form" :actions="false">
<FormKit name="login" type="text" label="Логин" />
<FormKit name="name" type="text" label="Имя" />
<FormKit name="password" type="text" label="Пароль" />
</FormKit>
</template>

<template #footer="{ close }">
<button type="button" class="btn btn-secondary" @click="close">
Отмена
</button>
<button type="button" class="btn btn-danger" @click="submit">
Подтвердить
</button>
</template>
</Modal>
</template>

<script setup lang="ts">
import Modal from "@/components/common/Modal.vue";
import { ref } from "vue";
import { User } from "@/types/users";
import _ from "lodash";
const props = defineProps<{ user: User }>();
const emit = defineEmits<{ (e: "submit", u: User): void }>();
const modal = ref<InstanceType<typeof Modal> | null>(null);
const user = ref<User | null>(null);
function onOpened() {
user.value = _.cloneDeep(props.user);
}
function submit() {
if (user.value) {
emit("submit", user.value);
}
modal.value?.close();
}
defineExpose({ modal });
</script>

<style scoped lang="scss"></style>
70 changes: 70 additions & 0 deletions client/src/components/routes/profile/ProfileComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<template>
<div class="container-lg mt-3">
<div class="row justify-content-center">
<i
class="col-auto text-primary bi bi-person-circle"
style="font-size: 256px"
/>
<div class="col-auto">
<div class="fs-4 d-flex align-items-center">
<span class="fs-3">{{ userStore.user?.login }}</span>
<span class="badge bg-secondary ms-3">
#{{ userStore.user?._id.$oid }}
</span>
<i
class="bi bi-person-gear fs-1 text-primary ms-5"
role="button"
@click="edit"
/>
</div>
<div class="fs-4 mt-2">
<div>Имя: {{ userStore.user?.name }}</div>
<div class="mt-1">
Роль:
{{ UserRoleTranslations[userStore.user?.role ?? UserRole.user] }}
</div>
</div>
</div>
</div>

<EditProfileModal
v-if="userStore.user"
ref="editUserModal"
:user="userStore.user"
@submit="onEdit"
/>
</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { useUserStore } from "@/store/user";
import { useToaster } from "@/store/toaster";
import { UserRole, UserRoleTranslations } from "@/config/users";
import EditProfileModal from "@/components/routes/profile/EditProfileModal.vue";
import { User } from "@/types/users";
import _ from "lodash";
import { ProfileAPI } from "@/components/routes/profile/api";
import { ToastTypes } from "@/config/toast";

const editUserModal = ref<InstanceType<typeof EditProfileModal> | null>(null);

const userStore = useUserStore(),
toaster = useToaster();

function edit() {
editUserModal.value?.modal?.open();
}

async function onEdit(data: User) {
await ProfileAPI.updateSelf(_.pick(data, ["login", "name", "password"]));
await userStore.fetchUser();
toaster.addToast({
title: "Обновлено",
body: "Профиль успешно обновлён",
type: ToastTypes.success,
});
}
</script>

<style scoped lang="scss"></style>
8 changes: 8 additions & 0 deletions client/src/components/routes/profile/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { api } from "@/api";
import { UserSelfUpdate } from "@/components/routes/profile/types";

function updateSelf(data: UserSelfUpdate) {
return api.put("/users/user/self", { ...data });
}

export const ProfileAPI = { updateSelf };
3 changes: 3 additions & 0 deletions client/src/components/routes/profile/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { User } from "@/types/users";

export type UserSelfUpdate = Omit<User, "_id" | "role">;
64 changes: 64 additions & 0 deletions client/src/components/routes/users/UserDataModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<template>
<Modal ref="modal" backdrop="static" @opened="onOpened">
<template #header="{ close }">
<h4 class="modal-title">Редактировать данные</h4>
<button type="button" class="btn-close" @click="close"></button>
</template>

<template v-if="user" #body>
<FormKit v-model="user" type="form" :actions="false">
<FormKit name="login" type="text" label="Логин" />
<FormKit name="password" type="text" label="Пароль" />
<FormKit name="name" type="text" label="Имя" />
<FormKit
name="role"
type="select"
label="Роль"
placeholder="Выберите роль"
:options="UserRoleTranslations"
/>
</FormKit>
</template>

<template #footer="{ close }">
<button type="button" class="btn btn-secondary" @click="close">
Отмена
</button>
<button type="button" class="btn btn-danger" @click="submit">
Подтвердить
</button>
</template>
</Modal>
</template>

<script setup lang="ts">
import Modal from "@/components/common/Modal.vue";
import { ref } from "vue";
import { User } from "@/types/users";
import { UserRoleTranslations } from "@/config/users";
import _ from "lodash";
const props = defineProps<{ user: User }>();
const emit = defineEmits<{ (e: "submit", u: User): void }>();
const modal = ref<InstanceType<typeof Modal> | null>(null);
const user = ref<User | null>(null);
function onOpened() {
user.value = _.cloneDeep(props.user);
}
function submit() {
if (user.value) {
emit("submit", user.value);
}
modal.value?.close();
}
defineExpose({ modal });
</script>

<style scoped lang="scss"></style>
Loading

0 comments on commit 0fbd7fa

Please sign in to comment.