Build Nuxt 4 manager cabinet workflows
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Nuxt dev/build outputs
|
||||||
|
.output
|
||||||
|
.data
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Node dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.fleet
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
12
.storybook/main.ts
Normal file
12
.storybook/main.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { StorybookConfig } from '@storybook/vue3-vite';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ['../app/**/*.stories.@(ts|tsx)'],
|
||||||
|
addons: ['@storybook/addon-essentials'],
|
||||||
|
framework: {
|
||||||
|
name: '@storybook/vue3-vite',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
15
.storybook/preview.ts
Normal file
15
.storybook/preview.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { Preview } from '@storybook/vue3-vite';
|
||||||
|
import '../app/assets/css/main.css';
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
parameters: {
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
75
README.md
75
README.md
@@ -1,2 +1,75 @@
|
|||||||
# manager-frontend
|
# Nuxt Minimal Starter
|
||||||
|
|
||||||
|
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Make sure to install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
Start the development server on `http://localhost:3000`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn dev
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Build the application for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Locally preview production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm preview
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn preview
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||||
|
|||||||
8
app/app.vue
Normal file
8
app/app.vue
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<AppHeader />
|
||||||
|
<main class="container mx-auto p-4 md:p-6">
|
||||||
|
<NuxtPage />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
12
app/assets/css/main.css
Normal file
12
app/assets/css/main.css
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--brand-primary: #0f766e;
|
||||||
|
--brand-secondary: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply bg-base-200 text-base-content;
|
||||||
|
}
|
||||||
19
app/components/orders/OrderStatusBadge.stories.ts
Normal file
19
app/components/orders/OrderStatusBadge.stories.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||||
|
import OrderStatusBadge from './OrderStatusBadge.vue';
|
||||||
|
|
||||||
|
const meta: Meta<typeof OrderStatusBadge> = {
|
||||||
|
title: 'Orders/OrderStatusBadge',
|
||||||
|
component: OrderStatusBadge,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof OrderStatusBadge>;
|
||||||
|
|
||||||
|
export const InProgress: Story = {
|
||||||
|
args: { status: 'IN_PROGRESS' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Completed: Story = {
|
||||||
|
args: { status: 'COMPLETED' },
|
||||||
|
};
|
||||||
16
app/components/orders/OrderStatusBadge.vue
Normal file
16
app/components/orders/OrderStatusBadge.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
status: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const className = computed(() => {
|
||||||
|
if (props.status === 'COMPLETED') return 'badge badge-success';
|
||||||
|
if (props.status === 'CLIENT_REJECTED' || props.status === 'MANAGER_REJECTED') return 'badge badge-error';
|
||||||
|
if (props.status === 'MANAGER_BLOCKED') return 'badge badge-warning';
|
||||||
|
return 'badge badge-info';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span :class="className">{{ status }}</span>
|
||||||
|
</template>
|
||||||
13
app/components/ui/AppHeader.stories.ts
Normal file
13
app/components/ui/AppHeader.stories.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
||||||
|
import AppHeader from './AppHeader.vue';
|
||||||
|
|
||||||
|
const meta: Meta<typeof AppHeader> = {
|
||||||
|
title: 'UI/AppHeader',
|
||||||
|
component: AppHeader,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof AppHeader>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
16
app/components/ui/AppHeader.vue
Normal file
16
app/components/ui/AppHeader.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<header class="navbar bg-base-100 border-b border-base-300 sticky top-0 z-10">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<NuxtLink to="/" class="btn btn-ghost text-xl">Fregat Manager</NuxtLink>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-center hidden md:flex">
|
||||||
|
<ul class="menu menu-horizontal px-1">
|
||||||
|
<li><NuxtLink to="/requests">Заявки</NuxtLink></li>
|
||||||
|
<li><NuxtLink to="/orders">Заказы</NuxtLink></li>
|
||||||
|
<li><NuxtLink to="/invitations">Инвайты</NuxtLink></li>
|
||||||
|
<li><NuxtLink to="/referrals">Рефералка</NuxtLink></li>
|
||||||
|
<li><NuxtLink to="/withdrawals">Выводы</NuxtLink></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
1119
app/composables/graphql/generated.ts
Normal file
1119
app/composables/graphql/generated.ts
Normal file
File diff suppressed because it is too large
Load Diff
5
app/composables/useGqlClient.ts
Normal file
5
app/composables/useGqlClient.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import type { ApolloClient, NormalizedCacheObject } from '@apollo/client/core';
|
||||||
|
|
||||||
|
export function useGqlClient(): ApolloClient<NormalizedCacheObject> {
|
||||||
|
return useNuxtApp().$apollo as ApolloClient<NormalizedCacheObject>;
|
||||||
|
}
|
||||||
73
app/pages/cart.vue
Normal file
73
app/pages/cart.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useMutation } from '@vue/apollo-composable';
|
||||||
|
import { SubmitCalculationOrderDocument } from '~/composables/graphql/generated';
|
||||||
|
|
||||||
|
const productName = ref('');
|
||||||
|
const quantity = ref(1);
|
||||||
|
const width = ref(100);
|
||||||
|
const thickness = ref(50);
|
||||||
|
const color = ref('прозрачный');
|
||||||
|
|
||||||
|
const { mutate, loading, onDone, onError } = useMutation(SubmitCalculationOrderDocument);
|
||||||
|
const success = ref('');
|
||||||
|
const errorMessage = ref('');
|
||||||
|
|
||||||
|
onDone((result) => {
|
||||||
|
success.value = `Заявка ${result.data?.submitCalculationOrder.code} отправлена`;
|
||||||
|
errorMessage.value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
onError((error) => {
|
||||||
|
errorMessage.value = error.message;
|
||||||
|
success.value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
mutate({
|
||||||
|
input: {
|
||||||
|
productName: productName.value,
|
||||||
|
quantity: Number(quantity.value),
|
||||||
|
parameters: {
|
||||||
|
width: Number(width.value),
|
||||||
|
thickness: Number(thickness.value),
|
||||||
|
color: color.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="space-y-4 max-w-2xl">
|
||||||
|
<h1 class="text-2xl font-bold">Корзина / заявка на расчет</h1>
|
||||||
|
<div class="card bg-base-100 border border-base-300">
|
||||||
|
<div class="card-body space-y-3">
|
||||||
|
<label class="form-control">
|
||||||
|
<span class="label-text">Название позиции</span>
|
||||||
|
<input v-model="productName" type="text" class="input input-bordered" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control">
|
||||||
|
<span class="label-text">Количество</span>
|
||||||
|
<input v-model="quantity" type="number" min="1" class="input input-bordered" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control">
|
||||||
|
<span class="label-text">Ширина</span>
|
||||||
|
<input v-model="width" type="number" min="1" class="input input-bordered" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control">
|
||||||
|
<span class="label-text">Толщина</span>
|
||||||
|
<input v-model="thickness" type="number" min="1" class="input input-bordered" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control">
|
||||||
|
<span class="label-text">Цвет</span>
|
||||||
|
<input v-model="color" type="text" class="input input-bordered" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button class="btn btn-primary" :disabled="loading" @click="submit">Отправить менеджеру</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="success" class="alert alert-success">{{ success }}</div>
|
||||||
|
<div v-if="errorMessage" class="alert alert-error">{{ errorMessage }}</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
13
app/pages/index.vue
Normal file
13
app/pages/index.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<section class="space-y-6">
|
||||||
|
<h1 class="text-3xl font-bold">Кабинет менеджера</h1>
|
||||||
|
<p class="text-base-content/80">Рабочие разделы: заявки, согласование заказов, инвайты, рефералка и выводы.</p>
|
||||||
|
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-5">
|
||||||
|
<NuxtLink to="/requests" class="card bg-base-100 border border-base-300 shadow-sm"><div class="card-body"><h2 class="card-title">Заявки</h2></div></NuxtLink>
|
||||||
|
<NuxtLink to="/orders" class="card bg-base-100 border border-base-300 shadow-sm"><div class="card-body"><h2 class="card-title">Заказы</h2></div></NuxtLink>
|
||||||
|
<NuxtLink to="/invitations" class="card bg-base-100 border border-base-300 shadow-sm"><div class="card-body"><h2 class="card-title">Инвайты</h2></div></NuxtLink>
|
||||||
|
<NuxtLink to="/referrals" class="card bg-base-100 border border-base-300 shadow-sm"><div class="card-body"><h2 class="card-title">Рефералка</h2></div></NuxtLink>
|
||||||
|
<NuxtLink to="/withdrawals" class="card bg-base-100 border border-base-300 shadow-sm"><div class="card-body"><h2 class="card-title">Выводы</h2></div></NuxtLink>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
29
app/pages/invitations.vue
Normal file
29
app/pages/invitations.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useMutation } from '@vue/apollo-composable';
|
||||||
|
import { CreateInvitationDocument } from '~/composables/graphql/generated';
|
||||||
|
|
||||||
|
const email = ref('');
|
||||||
|
const companyName = ref('');
|
||||||
|
const token = ref('');
|
||||||
|
|
||||||
|
const createInvitation = useMutation(CreateInvitationDocument);
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
const result = await createInvitation.mutate({ input: { email: email.value, companyName: companyName.value, expiresInDays: 7 } });
|
||||||
|
token.value = result?.data?.createInvitation.token ?? '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="space-y-4 max-w-2xl">
|
||||||
|
<h1 class="text-2xl font-bold">Инвайты клиентам</h1>
|
||||||
|
<div class="card bg-base-100 border border-base-300">
|
||||||
|
<div class="card-body space-y-3">
|
||||||
|
<input v-model="email" type="email" class="input input-bordered" placeholder="Email клиента" />
|
||||||
|
<input v-model="companyName" type="text" class="input input-bordered" placeholder="Компания" />
|
||||||
|
<button class="btn btn-primary" @click="submit">Создать инвайт</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="token" class="alert alert-success">Токен: {{ token }}</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
66
app/pages/orders.vue
Normal file
66
app/pages/orders.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useMutation, useQuery } from '@vue/apollo-composable';
|
||||||
|
import OrderStatusBadge from '~/components/orders/OrderStatusBadge.vue';
|
||||||
|
import {
|
||||||
|
BlockOrderDocument,
|
||||||
|
ManagerFinalizeOrderDocument,
|
||||||
|
ManagerOrdersDocument,
|
||||||
|
ManagerSetOrderOfferDocument,
|
||||||
|
} from '~/composables/graphql/generated';
|
||||||
|
|
||||||
|
const { result, refetch } = useQuery(ManagerOrdersDocument, { status: null });
|
||||||
|
|
||||||
|
const setOffer = useMutation(ManagerSetOrderOfferDocument);
|
||||||
|
const finalize = useMutation(ManagerFinalizeOrderDocument);
|
||||||
|
const block = useMutation(BlockOrderDocument);
|
||||||
|
|
||||||
|
async function publishOffer(orderId: string) {
|
||||||
|
await setOffer.mutate({
|
||||||
|
input: {
|
||||||
|
orderId,
|
||||||
|
deliveryTerms: 'Доставка 3-5 дней',
|
||||||
|
deliveryFee: 1000,
|
||||||
|
totalPrice: 12500,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await refetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function approve(orderId: string) {
|
||||||
|
await finalize.mutate({ orderId, decision: 'APPROVE' });
|
||||||
|
await refetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reject(orderId: string) {
|
||||||
|
await finalize.mutate({ orderId, decision: 'REJECT' });
|
||||||
|
await refetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function blockOrder(orderId: string) {
|
||||||
|
await block.mutate({ input: { orderId, reason: 'Нужно уточнение параметров' } });
|
||||||
|
await refetch();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="space-y-4">
|
||||||
|
<h1 class="text-2xl font-bold">Заявки и согласования заказов</h1>
|
||||||
|
<article v-for="order in result?.managerOrders ?? []" :key="order.id" class="card bg-base-100 border border-base-300">
|
||||||
|
<div class="card-body space-y-3">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h2 class="card-title">{{ order.code }}</h2>
|
||||||
|
<OrderStatusBadge :status="order.status" />
|
||||||
|
</div>
|
||||||
|
<ul class="text-sm">
|
||||||
|
<li v-for="item in order.items" :key="item.id">{{ item.productName }} × {{ item.quantity }}</li>
|
||||||
|
</ul>
|
||||||
|
<div class="flex gap-2 flex-wrap">
|
||||||
|
<button class="btn btn-primary" @click="publishOffer(order.id)">Публиковать оффер</button>
|
||||||
|
<button class="btn btn-success" @click="approve(order.id)">Approve</button>
|
||||||
|
<button class="btn btn-error" @click="reject(order.id)">Reject</button>
|
||||||
|
<button class="btn btn-warning" @click="blockOrder(order.id)">Блокировать</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
26
app/pages/products.vue
Normal file
26
app/pages/products.vue
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useQuery } from '@vue/apollo-composable';
|
||||||
|
import { ClientProductsDocument } from '~/composables/graphql/generated';
|
||||||
|
|
||||||
|
const { result, loading, error } = useQuery(ClientProductsDocument);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="space-y-4">
|
||||||
|
<h1 class="text-2xl font-bold">Витрина товаров</h1>
|
||||||
|
<div v-if="loading" class="alert">Загрузка...</div>
|
||||||
|
<div v-else-if="error" class="alert alert-error">{{ error.message }}</div>
|
||||||
|
<div v-else class="grid gap-4 lg:grid-cols-2">
|
||||||
|
<article v-for="product in result?.clientProducts ?? []" :key="product.id" class="card bg-base-100 border border-base-300">
|
||||||
|
<div class="card-body gap-2">
|
||||||
|
<h2 class="card-title">{{ product.name }}</h2>
|
||||||
|
<p class="text-sm opacity-80">{{ product.description }}</p>
|
||||||
|
<p class="text-xs">SKU: {{ product.sku }}</p>
|
||||||
|
<ul class="text-sm space-y-1">
|
||||||
|
<li v-for="stock in product.availableInWarehouses" :key="stock.warehouse.id">{{ stock.warehouse.name }}: {{ stock.availableQty }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
71
app/pages/profile.vue
Normal file
71
app/pages/profile.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useMutation } from '@vue/apollo-composable';
|
||||||
|
import { ConnectMessengerDocument, RegisterSelfDocument } from '~/composables/graphql/generated';
|
||||||
|
|
||||||
|
const companyName = ref('');
|
||||||
|
const inn = ref('');
|
||||||
|
const contactName = ref('');
|
||||||
|
const email = ref('');
|
||||||
|
const channelId = ref('');
|
||||||
|
const channelType = ref<'TELEGRAM' | 'MAX'>('TELEGRAM');
|
||||||
|
|
||||||
|
const registerMutation = useMutation(RegisterSelfDocument);
|
||||||
|
const messengerMutation = useMutation(ConnectMessengerDocument);
|
||||||
|
|
||||||
|
const message = ref('');
|
||||||
|
|
||||||
|
function register() {
|
||||||
|
registerMutation.mutate({
|
||||||
|
input: {
|
||||||
|
companyName: companyName.value,
|
||||||
|
inn: inn.value || null,
|
||||||
|
contactName: contactName.value,
|
||||||
|
email: email.value,
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
message.value = 'Заявка на регистрацию отправлена менеджеру';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectMessenger() {
|
||||||
|
messengerMutation.mutate({
|
||||||
|
input: {
|
||||||
|
type: channelType.value,
|
||||||
|
channelId: channelId.value,
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
message.value = 'Канал уведомлений подключен';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="space-y-6 max-w-2xl">
|
||||||
|
<h1 class="text-2xl font-bold">Профиль и каналы уведомлений</h1>
|
||||||
|
|
||||||
|
<div class="card bg-base-100 border border-base-300">
|
||||||
|
<div class="card-body space-y-3">
|
||||||
|
<h2 class="card-title">Самостоятельная регистрация</h2>
|
||||||
|
<input v-model="companyName" type="text" placeholder="Компания" class="input input-bordered" />
|
||||||
|
<input v-model="inn" type="text" placeholder="ИНН" class="input input-bordered" />
|
||||||
|
<input v-model="contactName" type="text" placeholder="Контактное лицо" class="input input-bordered" />
|
||||||
|
<input v-model="email" type="email" placeholder="Email" class="input input-bordered" />
|
||||||
|
<button class="btn btn-primary" @click="register">Отправить заявку</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-base-100 border border-base-300">
|
||||||
|
<div class="card-body space-y-3">
|
||||||
|
<h2 class="card-title">Подключение мессенджера</h2>
|
||||||
|
<select v-model="channelType" class="select select-bordered">
|
||||||
|
<option value="TELEGRAM">Telegram</option>
|
||||||
|
<option value="MAX">Max</option>
|
||||||
|
</select>
|
||||||
|
<input v-model="channelId" type="text" placeholder="ID канала" class="input input-bordered" />
|
||||||
|
<button class="btn btn-secondary" @click="connectMessenger">Подключить канал</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="message" class="alert alert-success">{{ message }}</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
50
app/pages/referrals.vue
Normal file
50
app/pages/referrals.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useMutation } from '@vue/apollo-composable';
|
||||||
|
import { AddBonusTransactionDocument, CreateReferralDocument } from '~/composables/graphql/generated';
|
||||||
|
|
||||||
|
const refereeUserId = ref('');
|
||||||
|
const referralCreated = ref('');
|
||||||
|
const bonusUserId = ref('');
|
||||||
|
const bonusAmount = ref(100);
|
||||||
|
const bonusReason = ref('Реферальный бонус');
|
||||||
|
const bonusResult = ref('');
|
||||||
|
|
||||||
|
const createReferral = useMutation(CreateReferralDocument);
|
||||||
|
const addBonus = useMutation(AddBonusTransactionDocument);
|
||||||
|
|
||||||
|
async function submitReferral() {
|
||||||
|
const result = await createReferral.mutate({ input: { refereeUserId: refereeUserId.value } });
|
||||||
|
referralCreated.value = result?.data?.createReferral.id ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitBonus() {
|
||||||
|
const result = await addBonus.mutate({ input: { userId: bonusUserId.value, amount: Number(bonusAmount.value), reason: bonusReason.value } });
|
||||||
|
bonusResult.value = result?.data?.addBonusTransaction.id ?? '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="space-y-6 max-w-2xl">
|
||||||
|
<h1 class="text-2xl font-bold">Реферальная программа</h1>
|
||||||
|
|
||||||
|
<div class="card bg-base-100 border border-base-300">
|
||||||
|
<div class="card-body space-y-3">
|
||||||
|
<h2 class="card-title">Создать реферальную связь</h2>
|
||||||
|
<input v-model="refereeUserId" class="input input-bordered" placeholder="ID приглашенного пользователя" />
|
||||||
|
<button class="btn btn-primary" @click="submitReferral">Создать</button>
|
||||||
|
<p v-if="referralCreated" class="text-sm">Создано: {{ referralCreated }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-base-100 border border-base-300">
|
||||||
|
<div class="card-body space-y-3">
|
||||||
|
<h2 class="card-title">Начислить бонус</h2>
|
||||||
|
<input v-model="bonusUserId" class="input input-bordered" placeholder="ID пользователя" />
|
||||||
|
<input v-model="bonusAmount" type="number" class="input input-bordered" placeholder="Сумма" />
|
||||||
|
<input v-model="bonusReason" class="input input-bordered" placeholder="Причина" />
|
||||||
|
<button class="btn btn-secondary" @click="submitBonus">Начислить</button>
|
||||||
|
<p v-if="bonusResult" class="text-sm">Транзакция: {{ bonusResult }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
33
app/pages/requests.vue
Normal file
33
app/pages/requests.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useMutation, useQuery } from '@vue/apollo-composable';
|
||||||
|
import { RegistrationRequestsDocument, ReviewRegistrationRequestDocument } from '~/composables/graphql/generated';
|
||||||
|
|
||||||
|
const { result, refetch } = useQuery(RegistrationRequestsDocument, { status: 'PENDING' });
|
||||||
|
const review = useMutation(ReviewRegistrationRequestDocument);
|
||||||
|
|
||||||
|
async function approve(requestId: string) {
|
||||||
|
await review.mutate({ input: { requestId, decision: 'APPROVE' } });
|
||||||
|
await refetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reject(requestId: string) {
|
||||||
|
await review.mutate({ input: { requestId, decision: 'REJECT', rejectionReason: 'Не хватает данных' } });
|
||||||
|
await refetch();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="space-y-4">
|
||||||
|
<h1 class="text-2xl font-bold">Заявки на регистрацию</h1>
|
||||||
|
<article v-for="request in result?.registrationRequests ?? []" :key="request.id" class="card bg-base-100 border border-base-300">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">{{ request.companyName }}</h2>
|
||||||
|
<p>{{ request.contactName }} • {{ request.email }}</p>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button class="btn btn-success" @click="approve(request.id)">Approve</button>
|
||||||
|
<button class="btn btn-error" @click="reject(request.id)">Reject</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
34
app/pages/withdrawals.vue
Normal file
34
app/pages/withdrawals.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useMutation } from '@vue/apollo-composable';
|
||||||
|
import { ReviewRewardWithdrawalDocument } from '~/composables/graphql/generated';
|
||||||
|
|
||||||
|
const withdrawalId = ref('');
|
||||||
|
const decision = ref<'APPROVE' | 'REJECT'>('APPROVE');
|
||||||
|
const reviewComment = ref('');
|
||||||
|
const resultStatus = ref('');
|
||||||
|
|
||||||
|
const review = useMutation(ReviewRewardWithdrawalDocument);
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
const result = await review.mutate({ input: { withdrawalId: withdrawalId.value, decision: decision.value, reviewComment: reviewComment.value } });
|
||||||
|
resultStatus.value = result?.data?.reviewRewardWithdrawal.status ?? '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="space-y-4 max-w-2xl">
|
||||||
|
<h1 class="text-2xl font-bold">Заявки на вывод</h1>
|
||||||
|
<div class="card bg-base-100 border border-base-300">
|
||||||
|
<div class="card-body space-y-3">
|
||||||
|
<input v-model="withdrawalId" class="input input-bordered" placeholder="ID заявки" />
|
||||||
|
<select v-model="decision" class="select select-bordered">
|
||||||
|
<option value="APPROVE">Approve</option>
|
||||||
|
<option value="REJECT">Reject</option>
|
||||||
|
</select>
|
||||||
|
<input v-model="reviewComment" class="input input-bordered" placeholder="Комментарий" />
|
||||||
|
<button class="btn btn-primary" @click="submit">Подтвердить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="resultStatus" class="alert alert-success">Статус: {{ resultStatus }}</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
23
app/plugins/apollo.ts
Normal file
23
app/plugins/apollo.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client/core';
|
||||||
|
import { provideApolloClient } from '@vue/apollo-composable';
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(() => {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
|
||||||
|
const client = new ApolloClient({
|
||||||
|
link: new HttpLink({
|
||||||
|
uri: config.public.graphqlEndpoint,
|
||||||
|
fetch,
|
||||||
|
}),
|
||||||
|
cache: new InMemoryCache(),
|
||||||
|
connectToDevTools: import.meta.dev,
|
||||||
|
});
|
||||||
|
|
||||||
|
provideApolloClient(client);
|
||||||
|
|
||||||
|
return {
|
||||||
|
provide: {
|
||||||
|
apollo: client,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
18
app/plugins/sentry.client.ts
Normal file
18
app/plugins/sentry.client.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import * as Sentry from '@sentry/vue';
|
||||||
|
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const dsn = config.public.sentryDsn;
|
||||||
|
|
||||||
|
if (!dsn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sentry.init({
|
||||||
|
app: nuxtApp.vueApp,
|
||||||
|
dsn,
|
||||||
|
environment: config.public.sentryEnvironment,
|
||||||
|
release: config.public.sentryRelease,
|
||||||
|
tracesSampleRate: 0.1,
|
||||||
|
});
|
||||||
|
});
|
||||||
18
codegen.ts
Normal file
18
codegen.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import type { CodegenConfig } from '@graphql-codegen/cli';
|
||||||
|
|
||||||
|
const config: CodegenConfig = {
|
||||||
|
schema: './graphql/schema.graphql',
|
||||||
|
documents: ['./graphql/operations/**/*.graphql'],
|
||||||
|
generates: {
|
||||||
|
'./app/composables/graphql/generated.ts': {
|
||||||
|
plugins: ['typescript', 'typescript-operations', 'typescript-vue-apollo'],
|
||||||
|
config: {
|
||||||
|
withCompositionFunctions: true,
|
||||||
|
vueApolloComposableImportFrom: '@vue/apollo-composable',
|
||||||
|
useTypeImports: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
6
eslint.config.mjs
Normal file
6
eslint.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// @ts-check
|
||||||
|
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||||
|
|
||||||
|
export default withNuxt(
|
||||||
|
// Your custom configs here
|
||||||
|
)
|
||||||
10
graphql/operations/auth/register-self.graphql
Normal file
10
graphql/operations/auth/register-self.graphql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
mutation RegisterSelf($input: RegisterSelfInput!) {
|
||||||
|
registerSelf(input: $input) {
|
||||||
|
id
|
||||||
|
companyName
|
||||||
|
contactName
|
||||||
|
email
|
||||||
|
status
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
17
graphql/operations/catalog/client-products.graphql
Normal file
17
graphql/operations/catalog/client-products.graphql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
query ClientProducts {
|
||||||
|
clientProducts {
|
||||||
|
id
|
||||||
|
sku
|
||||||
|
name
|
||||||
|
description
|
||||||
|
isCustomizable
|
||||||
|
availableInWarehouses {
|
||||||
|
availableQty
|
||||||
|
warehouse {
|
||||||
|
id
|
||||||
|
code
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
graphql/operations/manager/add-bonus-transaction.graphql
Normal file
9
graphql/operations/manager/add-bonus-transaction.graphql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mutation AddBonusTransaction($input: AddBonusTransactionInput!) {
|
||||||
|
addBonusTransaction(input: $input) {
|
||||||
|
id
|
||||||
|
userId
|
||||||
|
amount
|
||||||
|
reason
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
7
graphql/operations/manager/block-order.graphql
Normal file
7
graphql/operations/manager/block-order.graphql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mutation BlockOrder($input: BlockOrderInput!) {
|
||||||
|
blockOrder(input: $input) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
blockReason
|
||||||
|
}
|
||||||
|
}
|
||||||
9
graphql/operations/manager/create-invitation.graphql
Normal file
9
graphql/operations/manager/create-invitation.graphql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mutation CreateInvitation($input: CreateInvitationInput!) {
|
||||||
|
createInvitation(input: $input) {
|
||||||
|
id
|
||||||
|
token
|
||||||
|
email
|
||||||
|
companyName
|
||||||
|
expiresAt
|
||||||
|
}
|
||||||
|
}
|
||||||
8
graphql/operations/manager/create-referral.graphql
Normal file
8
graphql/operations/manager/create-referral.graphql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
mutation CreateReferral($input: CreateReferralInput!) {
|
||||||
|
createReferral(input: $input) {
|
||||||
|
id
|
||||||
|
referrerId
|
||||||
|
refereeId
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
mutation ManagerFinalizeOrder($orderId: ID!, $decision: Decision!) {
|
||||||
|
managerFinalizeOrder(orderId: $orderId, decision: $decision) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
managerApproved
|
||||||
|
}
|
||||||
|
}
|
||||||
17
graphql/operations/manager/manager-orders.graphql
Normal file
17
graphql/operations/manager/manager-orders.graphql
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
query ManagerOrders($status: OrderStatus) {
|
||||||
|
managerOrders(status: $status) {
|
||||||
|
id
|
||||||
|
code
|
||||||
|
status
|
||||||
|
kind
|
||||||
|
customerId
|
||||||
|
deliveryTerms
|
||||||
|
totalPrice
|
||||||
|
createdAt
|
||||||
|
items {
|
||||||
|
id
|
||||||
|
productName
|
||||||
|
quantity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
graphql/operations/manager/registration-requests.graphql
Normal file
10
graphql/operations/manager/registration-requests.graphql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
query RegistrationRequests($status: RegistrationStatus) {
|
||||||
|
registrationRequests(status: $status) {
|
||||||
|
id
|
||||||
|
companyName
|
||||||
|
contactName
|
||||||
|
email
|
||||||
|
status
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
mutation ReviewRegistrationRequest($input: ReviewRegistrationRequestInput!) {
|
||||||
|
reviewRegistrationRequest(input: $input) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
rejectionReason
|
||||||
|
reviewedById
|
||||||
|
}
|
||||||
|
}
|
||||||
8
graphql/operations/manager/review-withdrawal.graphql
Normal file
8
graphql/operations/manager/review-withdrawal.graphql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
mutation ReviewRewardWithdrawal($input: ReviewRewardWithdrawalInput!) {
|
||||||
|
reviewRewardWithdrawal(input: $input) {
|
||||||
|
id
|
||||||
|
status
|
||||||
|
reviewComment
|
||||||
|
reviewedById
|
||||||
|
}
|
||||||
|
}
|
||||||
9
graphql/operations/manager/set-order-offer.graphql
Normal file
9
graphql/operations/manager/set-order-offer.graphql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mutation ManagerSetOrderOffer($input: SetOrderOfferInput!) {
|
||||||
|
managerSetOrderOffer(input: $input) {
|
||||||
|
id
|
||||||
|
code
|
||||||
|
status
|
||||||
|
deliveryTerms
|
||||||
|
totalPrice
|
||||||
|
}
|
||||||
|
}
|
||||||
14
graphql/operations/orders/my-current-orders.graphql
Normal file
14
graphql/operations/orders/my-current-orders.graphql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
query MyCurrentOrders {
|
||||||
|
myCurrentOrders {
|
||||||
|
id
|
||||||
|
code
|
||||||
|
kind
|
||||||
|
status
|
||||||
|
createdAt
|
||||||
|
items {
|
||||||
|
id
|
||||||
|
productName
|
||||||
|
quantity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
graphql/operations/orders/my-orders.graphql
Normal file
16
graphql/operations/orders/my-orders.graphql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
query MyOrders {
|
||||||
|
myOrders {
|
||||||
|
id
|
||||||
|
code
|
||||||
|
kind
|
||||||
|
status
|
||||||
|
totalPrice
|
||||||
|
deliveryTerms
|
||||||
|
createdAt
|
||||||
|
items {
|
||||||
|
id
|
||||||
|
productName
|
||||||
|
quantity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
mutation SubmitCalculationOrder($input: SubmitCalculationOrderInput!) {
|
||||||
|
submitCalculationOrder(input: $input) {
|
||||||
|
id
|
||||||
|
code
|
||||||
|
status
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
8
graphql/operations/orders/submit-ready-order.graphql
Normal file
8
graphql/operations/orders/submit-ready-order.graphql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
mutation SubmitReadyOrder($input: SubmitReadyOrderInput!) {
|
||||||
|
submitReadyOrder(input: $input) {
|
||||||
|
id
|
||||||
|
code
|
||||||
|
status
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
8
graphql/operations/profile/connect-messenger.graphql
Normal file
8
graphql/operations/profile/connect-messenger.graphql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
mutation ConnectMessenger($input: ConnectMessengerInput!) {
|
||||||
|
connectMessenger(input: $input) {
|
||||||
|
id
|
||||||
|
type
|
||||||
|
channelId
|
||||||
|
isActive
|
||||||
|
}
|
||||||
|
}
|
||||||
294
graphql/schema.graphql
Normal file
294
graphql/schema.graphql
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
scalar DateTime
|
||||||
|
scalar JSON
|
||||||
|
|
||||||
|
enum UserRole {
|
||||||
|
CLIENT
|
||||||
|
MANAGER
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MessengerType {
|
||||||
|
TELEGRAM
|
||||||
|
MAX
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RegistrationStatus {
|
||||||
|
PENDING
|
||||||
|
APPROVED
|
||||||
|
REJECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderKind {
|
||||||
|
READY
|
||||||
|
CALCULATION
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderStatus {
|
||||||
|
NEW
|
||||||
|
MANAGER_PROCESSING
|
||||||
|
WAITING_DOUBLE_CONFIRM
|
||||||
|
CLIENT_REJECTED
|
||||||
|
MANAGER_REJECTED
|
||||||
|
MANAGER_BLOCKED
|
||||||
|
CONFIRMED
|
||||||
|
IN_PROGRESS
|
||||||
|
COMPLETED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WithdrawalStatus {
|
||||||
|
PENDING
|
||||||
|
APPROVED
|
||||||
|
REJECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Decision {
|
||||||
|
APPROVE
|
||||||
|
REJECT
|
||||||
|
}
|
||||||
|
|
||||||
|
type Company {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
inn: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type User {
|
||||||
|
id: ID!
|
||||||
|
email: String!
|
||||||
|
fullName: String!
|
||||||
|
role: UserRole!
|
||||||
|
company: Company
|
||||||
|
}
|
||||||
|
|
||||||
|
type Invitation {
|
||||||
|
id: ID!
|
||||||
|
token: String!
|
||||||
|
email: String!
|
||||||
|
companyName: String!
|
||||||
|
managerId: ID!
|
||||||
|
acceptedById: ID
|
||||||
|
expiresAt: DateTime!
|
||||||
|
acceptedAt: DateTime
|
||||||
|
createdAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegistrationRequest {
|
||||||
|
id: ID!
|
||||||
|
companyName: String!
|
||||||
|
inn: String
|
||||||
|
contactName: String!
|
||||||
|
email: String!
|
||||||
|
status: RegistrationStatus!
|
||||||
|
rejectionReason: String
|
||||||
|
reviewedById: ID
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessengerConnection {
|
||||||
|
id: ID!
|
||||||
|
userId: ID!
|
||||||
|
type: MessengerType!
|
||||||
|
channelId: String!
|
||||||
|
isActive: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Warehouse {
|
||||||
|
id: ID!
|
||||||
|
code: String!
|
||||||
|
name: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductWarehouseBalance {
|
||||||
|
warehouse: Warehouse!
|
||||||
|
availableQty: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Product {
|
||||||
|
id: ID!
|
||||||
|
sku: String!
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
isCustomizable: Boolean!
|
||||||
|
isActive: Boolean!
|
||||||
|
availableInWarehouses: [ProductWarehouseBalance!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderItem {
|
||||||
|
id: ID!
|
||||||
|
productId: ID
|
||||||
|
productName: String!
|
||||||
|
quantity: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderStatusEvent {
|
||||||
|
id: ID!
|
||||||
|
status: OrderStatus!
|
||||||
|
actorUserId: ID!
|
||||||
|
note: String
|
||||||
|
createdAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Order {
|
||||||
|
id: ID!
|
||||||
|
code: String!
|
||||||
|
kind: OrderKind!
|
||||||
|
status: OrderStatus!
|
||||||
|
customerId: ID!
|
||||||
|
managerId: ID
|
||||||
|
clientApproved: Boolean
|
||||||
|
managerApproved: Boolean
|
||||||
|
blockReason: String
|
||||||
|
deliveryTerms: String
|
||||||
|
deliveryFee: Float
|
||||||
|
totalPrice: Float
|
||||||
|
calculationPayload: JSON
|
||||||
|
items: [OrderItem!]!
|
||||||
|
history: [OrderStatusEvent!]!
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReferralLink {
|
||||||
|
id: ID!
|
||||||
|
referrerId: ID!
|
||||||
|
refereeId: ID!
|
||||||
|
createdAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type BonusTransaction {
|
||||||
|
id: ID!
|
||||||
|
userId: ID!
|
||||||
|
amount: Float!
|
||||||
|
reason: String!
|
||||||
|
orderId: ID
|
||||||
|
createdAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type RewardWithdrawalRequest {
|
||||||
|
id: ID!
|
||||||
|
requesterId: ID!
|
||||||
|
amount: Float!
|
||||||
|
status: WithdrawalStatus!
|
||||||
|
reviewedById: ID
|
||||||
|
reviewComment: String
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReferralStats {
|
||||||
|
referrerId: ID!
|
||||||
|
availableBalance: Float!
|
||||||
|
referralsCount: Int!
|
||||||
|
transactions: [BonusTransaction!]!
|
||||||
|
pendingWithdrawals: [RewardWithdrawalRequest!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
healthcheck: String!
|
||||||
|
me: User
|
||||||
|
clientProducts: [Product!]!
|
||||||
|
myOrders: [Order!]!
|
||||||
|
myCurrentOrders: [Order!]!
|
||||||
|
managerOrders(status: OrderStatus): [Order!]!
|
||||||
|
registrationRequests(status: RegistrationStatus): [RegistrationRequest!]!
|
||||||
|
referralStats: ReferralStats!
|
||||||
|
}
|
||||||
|
|
||||||
|
input RegisterSelfInput {
|
||||||
|
companyName: String!
|
||||||
|
inn: String
|
||||||
|
contactName: String!
|
||||||
|
email: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input ReviewRegistrationRequestInput {
|
||||||
|
requestId: ID!
|
||||||
|
decision: Decision!
|
||||||
|
rejectionReason: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateInvitationInput {
|
||||||
|
email: String!
|
||||||
|
companyName: String!
|
||||||
|
expiresInDays: Int = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
input AcceptInvitationInput {
|
||||||
|
token: String!
|
||||||
|
fullName: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input ConnectMessengerInput {
|
||||||
|
type: MessengerType!
|
||||||
|
channelId: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input ReadyOrderItemInput {
|
||||||
|
productId: ID!
|
||||||
|
quantity: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
input SubmitReadyOrderInput {
|
||||||
|
items: [ReadyOrderItemInput!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
input SubmitCalculationOrderInput {
|
||||||
|
productName: String!
|
||||||
|
quantity: Float!
|
||||||
|
parameters: JSON!
|
||||||
|
}
|
||||||
|
|
||||||
|
input SetOrderOfferInput {
|
||||||
|
orderId: ID!
|
||||||
|
deliveryTerms: String!
|
||||||
|
deliveryFee: Float!
|
||||||
|
totalPrice: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
input BlockOrderInput {
|
||||||
|
orderId: ID!
|
||||||
|
reason: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateReferralInput {
|
||||||
|
refereeUserId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
input AddBonusTransactionInput {
|
||||||
|
userId: ID!
|
||||||
|
amount: Float!
|
||||||
|
reason: String!
|
||||||
|
orderId: ID
|
||||||
|
}
|
||||||
|
|
||||||
|
input RequestRewardWithdrawalInput {
|
||||||
|
amount: Float!
|
||||||
|
}
|
||||||
|
|
||||||
|
input ReviewRewardWithdrawalInput {
|
||||||
|
withdrawalId: ID!
|
||||||
|
decision: Decision!
|
||||||
|
reviewComment: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
registerSelf(input: RegisterSelfInput!): RegistrationRequest!
|
||||||
|
reviewRegistrationRequest(input: ReviewRegistrationRequestInput!): RegistrationRequest!
|
||||||
|
createInvitation(input: CreateInvitationInput!): Invitation!
|
||||||
|
acceptInvitation(input: AcceptInvitationInput!): User!
|
||||||
|
connectMessenger(input: ConnectMessengerInput!): MessengerConnection!
|
||||||
|
|
||||||
|
submitReadyOrder(input: SubmitReadyOrderInput!): Order!
|
||||||
|
submitCalculationOrder(input: SubmitCalculationOrderInput!): Order!
|
||||||
|
managerSetOrderOffer(input: SetOrderOfferInput!): Order!
|
||||||
|
clientReviewOrder(orderId: ID!, decision: Decision!): Order!
|
||||||
|
managerFinalizeOrder(orderId: ID!, decision: Decision!): Order!
|
||||||
|
blockOrder(input: BlockOrderInput!): Order!
|
||||||
|
startOrderWork(orderId: ID!): Order!
|
||||||
|
completeOrder(orderId: ID!): Order!
|
||||||
|
|
||||||
|
createReferral(input: CreateReferralInput!): ReferralLink!
|
||||||
|
addBonusTransaction(input: AddBonusTransactionInput!): BonusTransaction!
|
||||||
|
requestRewardWithdrawal(input: RequestRewardWithdrawalInput!): RewardWithdrawalRequest!
|
||||||
|
reviewRewardWithdrawal(input: ReviewRewardWithdrawalInput!): RewardWithdrawalRequest!
|
||||||
|
}
|
||||||
21
nuxt.config.ts
Normal file
21
nuxt.config.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
compatibilityDate: '2025-07-15',
|
||||||
|
devtools: { enabled: true },
|
||||||
|
modules: ['@nuxt/eslint', '@nuxtjs/tailwindcss'],
|
||||||
|
css: ['~/assets/css/main.css'],
|
||||||
|
runtimeConfig: {
|
||||||
|
public: {
|
||||||
|
graphqlEndpoint: process.env.NUXT_PUBLIC_GRAPHQL_ENDPOINT ?? 'http://localhost:4000/graphql',
|
||||||
|
sentryDsn: process.env.NUXT_PUBLIC_SENTRY_DSN ?? '',
|
||||||
|
sentryEnvironment: process.env.NUXT_PUBLIC_SENTRY_ENVIRONMENT ?? 'development',
|
||||||
|
sentryRelease: process.env.NUXT_PUBLIC_SENTRY_RELEASE ?? 'dev',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
app: {
|
||||||
|
head: {
|
||||||
|
title: 'Fregat Manager Cabinet',
|
||||||
|
meta: [{ name: 'viewport', content: 'width=device-width, initial-scale=1' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
38
package.json
Normal file
38
package.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "manager-frontend",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare",
|
||||||
|
"codegen": "graphql-codegen --config codegen.ts",
|
||||||
|
"storybook": "storybook dev -p 6006",
|
||||||
|
"build-storybook": "storybook build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@apollo/client": "^3.14.1",
|
||||||
|
"@nuxt/eslint": "1.15.2",
|
||||||
|
"@nuxtjs/tailwindcss": "6.14.0",
|
||||||
|
"@sentry/vue": "^10.46.0",
|
||||||
|
"@vue/apollo-composable": "^4.2.2",
|
||||||
|
"daisyui": "^5.5.19",
|
||||||
|
"graphql": "^16.13.2",
|
||||||
|
"nuxt": "^4.4.2",
|
||||||
|
"vue": "^3.5.30",
|
||||||
|
"vue-router": "^5.0.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@graphql-codegen/cli": "^6.2.1",
|
||||||
|
"@graphql-codegen/typed-document-node": "^6.1.7",
|
||||||
|
"@graphql-codegen/typescript": "^5.0.9",
|
||||||
|
"@graphql-codegen/typescript-operations": "^5.0.9",
|
||||||
|
"@graphql-codegen/typescript-vue-apollo": "^5.0.0",
|
||||||
|
"@storybook/addon-essentials": "8.6.14",
|
||||||
|
"@storybook/vue3-vite": "^8.6.14",
|
||||||
|
"storybook": "^8.6.14",
|
||||||
|
"typescript": "5.9.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
12726
pnpm-lock.yaml
generated
Normal file
12726
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
2
public/robots.txt
Normal file
2
public/robots.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
User-Agent: *
|
||||||
|
Disallow:
|
||||||
17
tailwind.config.ts
Normal file
17
tailwind.config.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { Config } from 'tailwindcss';
|
||||||
|
import daisyui from 'daisyui';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
'./app/**/*.{vue,ts,js}',
|
||||||
|
'./components/**/*.{vue,ts,js}',
|
||||||
|
'./pages/**/*.vue',
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [daisyui],
|
||||||
|
daisyui: {
|
||||||
|
themes: ['light', 'corporate'],
|
||||||
|
},
|
||||||
|
} satisfies Config;
|
||||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./.nuxt/tsconfig.app.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./.nuxt/tsconfig.server.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./.nuxt/tsconfig.shared.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./.nuxt/tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user