diff --git a/app/app.vue b/app/app.vue index e11bde4..7deddd3 100644 --- a/app/app.vue +++ b/app/app.vue @@ -86,11 +86,19 @@ const managerPageTabs = computed(() => { { key: 'messages', label: 'Сообщения', - active: true, + active: route.path === '/admin/settings/messages', to: { path: '/admin/settings/messages', }, }, + { + key: 'sync', + label: '1С', + active: route.path === '/admin/settings/sync', + to: { + path: '/admin/settings/sync', + }, + }, ]; } diff --git a/app/composables/graphql/generated.ts b/app/composables/graphql/generated.ts index 8b1bd3b..6549385 100644 --- a/app/composables/graphql/generated.ts +++ b/app/composables/graphql/generated.ts @@ -152,6 +152,28 @@ export type DeliveryAddress = { userId: Scalars['ID']['output']; }; +export type IntegrationSyncDashboard = { + __typename?: 'IntegrationSyncDashboard'; + generatedAt: Scalars['DateTime']['output']; + items: Array; + lastActivityAt?: Maybe; + totalClients: Scalars['Int']['output']; + totalOrders: Scalars['Int']['output']; + totalProducts: Scalars['Int']['output']; +}; + +export type IntegrationSyncItem = { + __typename?: 'IntegrationSyncItem'; + description: Scalars['String']['output']; + id: Scalars['ID']['output']; + lastSyncedAt?: Maybe; + note: Scalars['String']['output']; + source: Scalars['String']['output']; + status: Scalars['String']['output']; + syncedCount: Scalars['Int']['output']; + title: Scalars['String']['output']; +}; + export type Invitation = { __typename?: 'Invitation'; acceptedAt?: Maybe; @@ -557,6 +579,7 @@ export type Query = { __typename?: 'Query'; clientProducts: Array; healthcheck: Scalars['String']['output']; + integrationSyncDashboard: IntegrationSyncDashboard; managerBonusAccount: ManagerBonusAccount; managerBonusBalances: Array; managerNotificationHistory: Array; @@ -1080,6 +1103,11 @@ export type UpsertMyCounterpartyProfileMutationVariables = Exact<{ export type UpsertMyCounterpartyProfileMutation = { __typename?: 'Mutation', upsertMyCounterpartyProfile: { __typename?: 'CounterpartyProfile', id: string, companyName: string, companyFullName: string, inn: string, kpp?: string | null, ogrn?: string | null, legalAddress: string, bankName: string, bik: string, correspondentAccount: string, checkingAccount: string, signerFullName: string, signerPosition: string, signerBasis: string, isComplete: boolean, updatedAt: any } }; +export type IntegrationSyncDashboardQueryVariables = Exact<{ [key: string]: never; }>; + + +export type IntegrationSyncDashboardQuery = { __typename?: 'Query', integrationSyncDashboard: { __typename?: 'IntegrationSyncDashboard', generatedAt: any, lastActivityAt?: any | null, totalOrders: number, totalProducts: number, totalClients: number, items: Array<{ __typename?: 'IntegrationSyncItem', id: string, title: string, description: string, source: string, syncedCount: number, lastSyncedAt?: any | null, status: string, note: string }> } }; + export const ConsumeLoginTokenDocument = gql` mutation ConsumeLoginToken($token: String!) { @@ -2843,4 +2871,45 @@ export const UpsertMyCounterpartyProfileDocument = gql` export function useUpsertMyCounterpartyProfileMutation(options: VueApolloComposable.UseMutationOptions | ReactiveFunction> = {}) { return VueApolloComposable.useMutation(UpsertMyCounterpartyProfileDocument, options); } -export type UpsertMyCounterpartyProfileMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn; \ No newline at end of file +export type UpsertMyCounterpartyProfileMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn; +export const IntegrationSyncDashboardDocument = gql` + query IntegrationSyncDashboard { + integrationSyncDashboard { + generatedAt + lastActivityAt + totalOrders + totalProducts + totalClients + items { + id + title + description + source + syncedCount + lastSyncedAt + status + note + } + } +} + `; + +/** + * __useIntegrationSyncDashboardQuery__ + * + * To run a query within a Vue component, call `useIntegrationSyncDashboardQuery` and pass it any options that fit your needs. + * When your component renders, `useIntegrationSyncDashboardQuery` returns an object from Apollo Client that contains result, loading and error properties + * you can use to render your UI. + * + * @param options that will be passed into the query, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/query.html#options; + * + * @example + * const { result, loading, error } = useIntegrationSyncDashboardQuery(); + */ +export function useIntegrationSyncDashboardQuery(options: VueApolloComposable.UseQueryOptions | VueCompositionApi.Ref> | ReactiveFunction> = {}) { + return VueApolloComposable.useQuery(IntegrationSyncDashboardDocument, {}, options); +} +export function useIntegrationSyncDashboardLazyQuery(options: VueApolloComposable.UseQueryOptions | VueCompositionApi.Ref> | ReactiveFunction> = {}) { + return VueApolloComposable.useLazyQuery(IntegrationSyncDashboardDocument, {}, options); +} +export type IntegrationSyncDashboardQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn; \ No newline at end of file diff --git a/app/pages/bonus-program.vue b/app/pages/bonus-program.vue index ecb0efd..d662894 100644 --- a/app/pages/bonus-program.vue +++ b/app/pages/bonus-program.vue @@ -24,6 +24,12 @@ const availableBalance = computed(() => bonusAccount.value?.availableBalance ?? const canWithdraw = computed(() => availableBalance.value >= 100); const selectedEntry = computed(() => String(route.query.entry || '').trim()); +const rewardCards = [ + { id: 'ozon-3000', store: 'Ozon', title: 'Подарочная карта Ozon', amount: 3000 }, + { id: 'wildberries-4000', store: 'Wildberries', title: 'Подарочная карта Wildberries', amount: 4000 }, + { id: 'mvideo-5000', store: 'М.Видео', title: 'Подарочная карта М.Видео', amount: 5000 }, +]; + const entryTitle = computed(() => { if (selectedEntry.value.includes('withdrawal')) { return 'Вы открыли бонусную программу из уведомления о выводе.'; @@ -115,14 +121,11 @@ async function submitWithdrawal() {

{{ entryTitle }} - Здесь отдельно живут баланс, начисления, выводы и переходы из бонусных уведомлений. + Здесь отдельно живут история начислений, магазин вознаграждений и выводы.

- - Message board - История уведомлений @@ -293,6 +296,36 @@ async function submitWithdrawal() {
+ +
+
+
+

Магазин

+

Вознаграждения

+
+ + {{ rewardCards.length }} + +
+ +
+
+

+ {{ reward.store }} +

+

+ {{ reward.title }} +

+

+ {{ formatMoney(reward.amount) }} бонусов +

+
+
+
diff --git a/app/pages/orders/index.vue b/app/pages/orders/index.vue index 5e9befc..2cb25ba 100644 --- a/app/pages/orders/index.vue +++ b/app/pages/orders/index.vue @@ -11,6 +11,8 @@ type OrderItem = MyOrdersQuery['myOrders'][number]; const allOrders = useQuery(MyOrdersDocument); const search = ref(''); const statusFilter = ref<'ALL' | 'WAITING' | 'ACTIVE' | 'CLOSED'>('ALL'); +const dateFrom = ref(''); +const dateTo = ref(''); const ACTIVE_STATUSES = new Set(['NEW', 'MANAGER_PROCESSING', 'WAITING_DOUBLE_CONFIRM', 'CONFIRMED', 'IN_PROGRESS']); const CLOSED_STATUSES = new Set(['COMPLETED', 'CLIENT_REJECTED', 'MANAGER_REJECTED', 'MANAGER_BLOCKED']); @@ -28,6 +30,29 @@ function matchesFilter(order: OrderItem) { return CLOSED_STATUSES.has(order.status); } +function matchesDate(order: OrderItem) { + const orderTimestamp = new Date(order.createdAt).getTime(); + if (!Number.isFinite(orderTimestamp)) { + return false; + } + + if (dateFrom.value) { + const fromTimestamp = new Date(`${dateFrom.value}T00:00:00`).getTime(); + if (Number.isFinite(fromTimestamp) && orderTimestamp < fromTimestamp) { + return false; + } + } + + if (dateTo.value) { + const toTimestamp = new Date(`${dateTo.value}T23:59:59.999`).getTime(); + if (Number.isFinite(toTimestamp) && orderTimestamp > toTimestamp) { + return false; + } + } + + return true; +} + const filteredOrders = computed(() => { const orders = allOrders.result.value?.myOrders ?? []; const normalizedSearch = search.value.trim().toLowerCase(); @@ -42,7 +67,7 @@ const filteredOrders = computed(() => { .toLowerCase(); const matchSearch = !normalizedSearch || text.includes(normalizedSearch); - return matchSearch && matchesFilter(order); + return matchSearch && matchesFilter(order) && matchesDate(order); }); }); @@ -54,7 +79,7 @@ const { visibleItems: visibleOrders, } = useIncrementalList(filteredOrders, { pageSize: 24, - resetKeys: [search, statusFilter], + resetKeys: [search, statusFilter, dateFrom, dateTo], }); @@ -66,15 +91,52 @@ const { search-placeholder="Номер заказа или товар" > diff --git a/app/pages/settings-sync.vue b/app/pages/settings-sync.vue new file mode 100644 index 0000000..aea972e --- /dev/null +++ b/app/pages/settings-sync.vue @@ -0,0 +1,156 @@ + + + diff --git a/graphql/operations/settings/integration-sync-dashboard.graphql b/graphql/operations/settings/integration-sync-dashboard.graphql new file mode 100644 index 0000000..ad1d826 --- /dev/null +++ b/graphql/operations/settings/integration-sync-dashboard.graphql @@ -0,0 +1,19 @@ +query IntegrationSyncDashboard { + integrationSyncDashboard { + generatedAt + lastActivityAt + totalOrders + totalProducts + totalClients + items { + id + title + description + source + syncedCount + lastSyncedAt + status + note + } + } +} diff --git a/graphql/schema.graphql b/graphql/schema.graphql index 767f2f9..58bc239 100644 --- a/graphql/schema.graphql +++ b/graphql/schema.graphql @@ -193,6 +193,26 @@ type NotificationTemplate { channels: [NotificationTemplateChannel!]! } +type IntegrationSyncItem { + id: ID! + title: String! + description: String! + source: String! + syncedCount: Int! + lastSyncedAt: DateTime + status: String! + note: String! +} + +type IntegrationSyncDashboard { + generatedAt: DateTime! + lastActivityAt: DateTime + totalOrders: Int! + totalProducts: Int! + totalClients: Int! + items: [IntegrationSyncItem!]! +} + type Warehouse { id: ID! code: String! @@ -380,6 +400,7 @@ type Query { myMessengerConnections: [MessengerConnection!]! myNotificationHistory(channel: MessengerType!, limit: Int = 50): [NotificationHistoryItem!]! notificationTemplates: [NotificationTemplate!]! + integrationSyncDashboard: IntegrationSyncDashboard! managerNotificationHistory(userId: ID!, channel: MessengerType!, limit: Int = 50): [NotificationHistoryItem!]! clientProducts: [Product!]! order(id: ID!): Order