From f98e1fc7159ec379141ad426b626b47ab5ed9bf3 Mon Sep 17 00:00:00 2001 From: Umar Adilov <99314948+adilovcode@users.noreply.github.com> Date: Wed, 17 Dec 2025 13:41:43 +0500 Subject: [PATCH] Integrated query builder --- package.json | 1 + pnpm-lock.yaml | 127 ++++ src/app/about/page.tsx | 15 +- .../presenters/taylor-presenters.ts | 238 ++++++++ .../requests/about-us-page.request copy.ts | 13 - .../requests/certificates-page.request.ts | 5 - .../requests/charity-page.request copy.ts | 5 - .../requests/main-page.request.ts | 13 - src/app/charity/page.tsx | 12 +- src/app/clients/certificates/page.tsx | 12 +- src/app/layout.tsx | 28 +- src/app/page.tsx | 12 +- src/features/pages/api/pages.api.ts | 99 ---- src/features/pages/services/pages.service.ts | 182 ++++++ src/shared/api/taylor-query-builder.ts | 13 + src/shared/language/api/text-control.api.ts | 25 - src/shared/types/database.types.ts | 548 ++++++++++++++++++ 17 files changed, 1135 insertions(+), 213 deletions(-) create mode 100644 src/app/api-utlities/presenters/taylor-presenters.ts delete mode 100644 src/app/api-utlities/requests/about-us-page.request copy.ts delete mode 100644 src/app/api-utlities/requests/certificates-page.request.ts delete mode 100644 src/app/api-utlities/requests/charity-page.request copy.ts delete mode 100644 src/app/api-utlities/requests/main-page.request.ts delete mode 100644 src/features/pages/api/pages.api.ts create mode 100644 src/features/pages/services/pages.service.ts create mode 100644 src/shared/api/taylor-query-builder.ts delete mode 100644 src/shared/language/api/text-control.api.ts create mode 100644 src/shared/types/database.types.ts diff --git a/package.json b/package.json index 13aa12f..36187c4 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "@radix-ui/react-toast": "^1.2.11", "@radix-ui/react-tooltip": "^1.2.6", "@reduxjs/toolkit": "^2.7.0", + "@taylordb/query-builder": "^0.10.1", "aos": "^2.3.4", "axios": "^1.9.0", "class-variance-authority": "^0.7.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb1dd52..e6613b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: '@reduxjs/toolkit': specifier: ^2.7.0 version: 2.7.0(react-redux@9.2.0(@types/react@19.1.2)(react@19.1.0)(redux@5.0.1))(react@19.1.0) + '@taylordb/query-builder': + specifier: ^0.10.1 + version: 0.10.1 aos: specifier: ^2.3.4 version: 2.3.4 @@ -1089,6 +1092,9 @@ packages: '@rushstack/eslint-patch@1.11.0': resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==} + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} @@ -1189,6 +1195,12 @@ packages: '@tailwindcss/postcss@4.1.4': resolution: {integrity: sha512-bjV6sqycCEa+AQSt2Kr7wpGF1bOZJ5wsqnLEkqSbM/JEHxx/yhMH8wHmdkPyApF9xhHeMSwnnkDUUMMM/hYnXw==} + '@taylordb/query-builder@0.10.1': + resolution: {integrity: sha512-WUyohbO8R+xFC+t+zfTP5VSLakGlBu2gsdWbFWwru4KqLNFW/NCsIvSDhzNObVbtbSgvkf+oVC0li+/z9uZYig==} + + '@taylordb/shared@0.4.4': + resolution: {integrity: sha512-Xykr4I26JapNLePkapBGjz15t9Ep1iLs30VbfCcc2z30x8Qy/2tm+sSa5LcacCP2EaxNVDp2UuYKhZ7kOWBLBQ==} + '@trivago/prettier-plugin-sort-imports@5.2.2': resolution: {integrity: sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==} engines: {node: '>18.12'} @@ -1589,6 +1601,15 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -1653,6 +1674,13 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + engine.io-client@6.6.3: + resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==} + + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + enhanced-resolve@5.18.1: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} @@ -1836,6 +1864,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1847,6 +1878,9 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-json-patch@3.1.1: + resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -2685,6 +2719,14 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + socket.io-client@4.8.1: + resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==} + engines: {node: '>=10.0.0'} + + socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + sonner@2.0.3: resolution: {integrity: sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==} peerDependencies: @@ -2896,6 +2938,22 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xmlhttprequest-ssl@2.1.2: + resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} + engines: {node: '>=0.4.0'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2903,6 +2961,9 @@ packages: zod@3.24.3: resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} + zod@4.2.1: + resolution: {integrity: sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==} + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -3744,6 +3805,8 @@ snapshots: '@rushstack/eslint-patch@1.11.0': {} + '@socket.io/component-emitter@3.1.2': {} + '@standard-schema/spec@1.0.0': {} '@standard-schema/utils@0.3.0': {} @@ -3820,6 +3883,24 @@ snapshots: postcss: 8.5.3 tailwindcss: 4.1.4 + '@taylordb/query-builder@0.10.1': + dependencies: + '@taylordb/shared': 0.4.4 + eventemitter3: 5.0.1 + fast-json-patch: 3.1.1 + json-to-graphql-query: 2.3.0 + lodash: 4.17.21 + socket.io-client: 4.8.1 + zod: 4.2.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@taylordb/shared@0.4.4': + dependencies: + lodash: 4.17.21 + '@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.5.3)': dependencies: '@babel/generator': 7.27.0 @@ -4246,6 +4327,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.3.7: + dependencies: + ms: 2.1.3 + debug@4.4.0: dependencies: ms: 2.1.3 @@ -4302,6 +4387,20 @@ snapshots: emoji-regex@9.2.2: {} + engine.io-client@6.6.3: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + engine.io-parser: 5.2.3 + ws: 8.17.1 + xmlhttprequest-ssl: 2.1.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + engine.io-parser@5.2.3: {} + enhanced-resolve@5.18.1: dependencies: graceful-fs: 4.2.11 @@ -4641,6 +4740,8 @@ snapshots: esutils@2.0.3: {} + eventemitter3@5.0.1: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -4659,6 +4760,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-json-patch@3.1.1: {} + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -5455,6 +5558,24 @@ snapshots: is-arrayish: 0.3.2 optional: true + socket.io-client@4.8.1: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + engine.io-client: 6.6.3 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + sonner@2.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 @@ -5720,6 +5841,12 @@ snapshots: word-wrap@1.2.5: {} + ws@8.17.1: {} + + xmlhttprequest-ssl@2.1.2: {} + yocto-queue@0.1.0: {} zod@3.24.3: {} + + zod@4.2.1: {} diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 608ce4d..d1bc22d 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -1,20 +1,15 @@ import AboutPage from '@/pages-templates/about'; -import { mainPageApi } from '@/features/pages/api/pages.api'; - -import { makeStore } from '@/shared/store'; +import { fetchAboutUsPageContent } from '@/features/pages/services/pages.service'; export const metadata = { - title: "О нас", - description: "Узнайте больше о нашей компании, истории и ценностях. Качественное топливо и отличный сервис.", + title: 'О нас', + description: + 'Узнайте больше о нашей компании, истории и ценностях. Качественное топливо и отличный сервис.', }; export default async function About() { - const store = makeStore(); - - const { data } = await store.dispatch( - mainPageApi.endpoints.fetchAboutUsPageContent.initiate(), - ); + const data = await fetchAboutUsPageContent(); if (!data) return null; diff --git a/src/app/api-utlities/presenters/taylor-presenters.ts b/src/app/api-utlities/presenters/taylor-presenters.ts new file mode 100644 index 0000000..54926fc --- /dev/null +++ b/src/app/api-utlities/presenters/taylor-presenters.ts @@ -0,0 +1,238 @@ +import { isEmpty } from 'lodash'; + +import { + AttachmentColumnValue, + TableRaws, +} from '@/shared/types/database.types'; + +// Helper to get image URL from Attachment array +export const getAttachmentUrl = ( + attachments: AttachmentColumnValue[] | undefined | null, +): string | null => { + if (isEmpty(attachments) || !attachments?.[0]) return null; + const attachment = attachments[0]; + return `${process.env.TAYLOR_MEDIA_URL}/${attachment.url}`; +}; + +// Helper to get link select name (link to selectTable returns object with name) +export const getLinkSelectName = ( + link: { name: string } | undefined | null, +): string | null => { + return link?.name || null; +}; + +// Helper to get multiple link select names (for arrays of links) +export const getLinkSelectNames = ( + links: Array<{ name: string }> | undefined | null, +): string[] => { + if (!links || isEmpty(links)) return []; + return links.map((link) => link.name); +}; + +// Presenters for TaylorDB query builder results (direct array format, no wrapper) + +export const presentPartnersFromTaylor = ( + partners: TableRaws<'partnyory'>[], +): Array<{ id: number; name: string; poster: string | null }> => { + return partners.map((partner, index) => ({ + id: index + 1, + name: partner.nazvanie || '', + poster: getAttachmentUrl(partner.izobrozhenie), + })); +}; + +export const presentJobsFromTaylor = ( + jobs: TableRaws<'vakansii'>[], +): Array<{ + id: number; + name: string; + tags: string[]; + location: string | null; + type: string | null; +}> => { + return jobs.map((job, index) => ({ + id: index + 1, + name: job.zagolovok || '', + // tegi is a LinkColumnType, so it returns objects when loaded with .with() + tags: Array.isArray(job.tegi) + ? (job.tegi as Array<{ name: string }>).map((tag) => tag.name) + : [], + // tip and lokaciya are SingleSelectColumnType, which return arrays of strings + location: job.lokaciya?.[0] || null, + type: job.tip?.[0] || null, + })); +}; + +export const presentDiscountsFromTaylor = ( + discounts: TableRaws<'akcii'>[], +): Array<{ + id: number; + name: string; + description: string; + expiresAt: string; + image: string | null; +}> => { + return discounts.map((discount, index) => ({ + id: index + 1, + name: discount.zagolovok || '', + description: discount.opisanie || '', + expiresAt: discount.do || '', + image: getAttachmentUrl(discount.foto), + })); +}; + +export const presentStationsFromTaylor = ( + stations: TableRaws<'azs'>[], +): Array<{ + id: number; + name: string; + description: string; + address: string; + workingHours: string | null; + latitude: number; + longitude: number; + carWash: boolean; + ai92: boolean; + ai95: boolean; + dt: boolean; + z100: boolean; + propan: boolean; + electricCharge: boolean; + miniMarket: boolean; + toilet: boolean; + region: string | null; + image: string | null; +}> => { + return stations.map((station, index) => ({ + id: index + 1, + name: station.imya || '', + description: station.opisanie || '', + address: station.adress || '', + // chasyRaboty and region are SingleSelectColumnType, which return arrays of strings + workingHours: station.chasyRaboty?.[0] || null, + // Parse string coordinates to numbers + latitude: parseFloat(station.lat || '0') || 0, + longitude: parseFloat(station.long || '0') || 0, + carWash: station.avtomojka || false, + ai92: station.ai92 || false, + ai95: station.ai95 || false, + dt: station.dt || false, + z100: station.z100 || false, + propan: station.propan || false, + electricCharge: station.zaryadnayaStanciya || false, + miniMarket: station.miniMarket || false, + toilet: station.tualet || false, + region: station.region?.[0] || null, + image: getAttachmentUrl(station.foto), + })); +}; + +export const presentTeamMembersFromTaylor = ( + members: TableRaws<'komanda'>[], +): Array<{ + name: string; + photo: string | null; + profession: string; +}> => { + return members.map((member) => ({ + name: member.polnoeImya || '', + photo: getAttachmentUrl(member.foto), + profession: member.zvanie || '', + })); +}; + +export const presentHistoryItemsFromTaylor = ( + historyItems: TableRaws<'istoriyaKompanii'>[], +): Array<{ + name: string; + year: string; + description: string; +}> => { + return historyItems.map((item) => ({ + name: item.zagolovok || '', + year: String(item.god || ''), + description: item.opisanie || '', + })); +}; + +export const presentReviewsFromTaylor = ( + reviews: TableRaws<'otzyvy'>[], +): Array<{ + id: number; + fullname: string; + review: string; + rating: number; +}> => { + return reviews.map((review) => ({ + id: review.id || 0, + fullname: review.polnoeImya || '', + review: review.otzyv || '', + rating: review.rejting || 0, + })); +}; + +export const presentCharitiesFromTaylor = ( + charities: TableRaws<'blagotvoritelnyjFond'>[], +): Array<{ + id: number; + name: string; + description: string; + date: string; + location: string; + image: string | null; +}> => { + return charities.map((charity, index) => ({ + id: index + 1, + name: charity.zagolovok || '', + description: charity.opisanie || '', + date: charity.data || '', + location: charity.lokaciya || '', + image: getAttachmentUrl(charity.foto), + })); +}; + +export const presentCertificatesFromTaylor = ( + certificates: TableRaws<'sertifikaty'>[], +): Array<{ + id: number; + name: string; + description: string; + issuedAt: string; + validUntil: string; + image: string | null; +}> => { + return certificates.map((certificate, index) => ({ + id: index + 1, + name: certificate.nazvanie || '', + description: certificate.opisanie || '', + issuedAt: certificate.dataVydachi || '', + validUntil: certificate.dejstvitelenDo || '', + image: getAttachmentUrl(certificate.foto), + })); +}; + +export const presentTextsFromTaylor = ( + texts: TableRaws<'tekstovyjKontentSajta'>[], +): Array<{ + key: string; + value: string | null; +}> => { + return texts.map((item) => ({ + key: item.klyuchNeIzmenyat || '', + value: item.znachenie || null, + })); +}; + +export const presentMediaFromTaylor = ( + media: TableRaws<'mediaKontentSajta'>[], +): Array<{ + key: string; + name: string; + photo: string | null; +}> => { + return media.map((record) => ({ + key: record.klyuchNeIzmenyat || '', + name: record.mestopolozheniya || '', + photo: getAttachmentUrl(record.foto), + })); +}; diff --git a/src/app/api-utlities/requests/about-us-page.request copy.ts b/src/app/api-utlities/requests/about-us-page.request copy.ts deleted file mode 100644 index ee16f00..0000000 --- a/src/app/api-utlities/requests/about-us-page.request copy.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { - historyRequest, - reviewsRequest, - stationsWithImageRequest, - teamRequest, -} from './common'; - -export const aboutUsPageRequest = { - ...teamRequest, - ...historyRequest, - ...stationsWithImageRequest, - ...reviewsRequest, -}; diff --git a/src/app/api-utlities/requests/certificates-page.request.ts b/src/app/api-utlities/requests/certificates-page.request.ts deleted file mode 100644 index f69d4e3..0000000 --- a/src/app/api-utlities/requests/certificates-page.request.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { certificatesRequest } from './common'; - -export const certificatesPageRequest = { - ...certificatesRequest, -}; diff --git a/src/app/api-utlities/requests/charity-page.request copy.ts b/src/app/api-utlities/requests/charity-page.request copy.ts deleted file mode 100644 index 9e005de..0000000 --- a/src/app/api-utlities/requests/charity-page.request copy.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { charityRequest } from './common'; - -export const charityPageRequest = { - ...charityRequest, -}; diff --git a/src/app/api-utlities/requests/main-page.request.ts b/src/app/api-utlities/requests/main-page.request.ts deleted file mode 100644 index aa7770b..0000000 --- a/src/app/api-utlities/requests/main-page.request.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { - discountsRequest, - jobsRequest, - partnersRequest, - stationsRequest, -} from './common'; - -export const mainPageRequest = { - ...partnersRequest, - ...jobsRequest, - ...discountsRequest, - ...stationsRequest, -}; diff --git a/src/app/charity/page.tsx b/src/app/charity/page.tsx index 9559d4c..947b972 100644 --- a/src/app/charity/page.tsx +++ b/src/app/charity/page.tsx @@ -1,8 +1,6 @@ import { CharityPage } from '@/pages-templates/charity'; -import { mainPageApi } from '@/features/pages/api/pages.api'; - -import { makeStore } from '@/shared/store'; +import { fetchCharityPageContent } from '@/features/pages/services/pages.service'; export const metadata = { title: 'Благотворительность', @@ -11,13 +9,9 @@ export const metadata = { }; export default async function Charity() { - const store = makeStore(); + const data = await fetchCharityPageContent(); - const { data, isLoading, error } = await store.dispatch( - mainPageApi.endpoints.fetchCharityPageContent.initiate(), - ); - - if (isLoading || !data) return null; + if (!data) return null; return ; } diff --git a/src/app/clients/certificates/page.tsx b/src/app/clients/certificates/page.tsx index bb64e97..f1f6cb8 100644 --- a/src/app/clients/certificates/page.tsx +++ b/src/app/clients/certificates/page.tsx @@ -1,8 +1,6 @@ import { CertificatesPage } from '@/pages-templates/clients/certificates'; -import { mainPageApi } from '@/features/pages/api/pages.api'; - -import { makeStore } from '@/shared/store'; +import { fetchCertificatesPageContent } from '@/features/pages/services/pages.service'; export const metadata = { title: 'Сертификаты', @@ -11,13 +9,9 @@ export const metadata = { }; export default async function Certificates() { - const store = makeStore(); + const data = await fetchCertificatesPageContent(); - const { data, isLoading, error } = await store.dispatch( - mainPageApi.endpoints.fetchCertificatesPageContent.initiate(), - ); - - if (isLoading || !data) return null; + if (!data) return null; return ; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7134cb9..56f43d7 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,10 +1,12 @@ import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; -import { textControlApi } from '@/shared/language/api/text-control.api'; -import { mediaControlApi } from '@/shared/media/api/media-control.api'; +import { + fetchMediaContent, + fetchTextContent, +} from '@/features/pages/services/pages.service'; + import { Providers } from '@/shared/providers/providers'; -import { makeStore } from '@/shared/store'; import { MediaItem } from '@/shared/types/media.type'; import { TextItem } from '@/shared/types/text.types'; @@ -29,17 +31,11 @@ export default async function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { - const store = makeStore(); - - // Запрос текстов - const textResponse = await store.dispatch( - textControlApi.endpoints.fetchText.initiate(), - ); - - // Запрос медиа - const mediaResponse = await store.dispatch( - mediaControlApi.endpoints.fetchMedia.initiate(), - ); + // Fetch texts and media using TaylorDB query builder + const [textItems, mediaItems] = await Promise.all([ + fetchTextContent(), + fetchMediaContent(), + ]); return (
{children} diff --git a/src/app/page.tsx b/src/app/page.tsx index a340dee..617e021 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,4 @@ -import { mainPageApi } from '@/features/pages/api/pages.api'; - -import { makeStore } from '@/shared/store'; +import { fetchMainPageContent } from '@/features/pages/services/pages.service'; import { AboutSection } from '@/widgets/about-section'; import { CharitySection } from '@/widgets/charity-section'; @@ -13,13 +11,9 @@ import { StatsSection } from '@/widgets/stats-section'; import { VacanciesSection } from '@/widgets/vacancies-section'; export default async function Home() { - const store = makeStore(); + const data = await fetchMainPageContent(); - const { data, isLoading, error } = await store.dispatch( - mainPageApi.endpoints.fetchMainPageContent.initiate(), - ); - - if (isLoading || !data) return null; + if (!data) return null; return (
diff --git a/src/features/pages/api/pages.api.ts b/src/features/pages/api/pages.api.ts deleted file mode 100644 index bc68c99..0000000 --- a/src/features/pages/api/pages.api.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { jsonToGraphQLQuery } from 'json-to-graphql-query'; - -import { - AboutUsPageData, - CertificatesPageData, - CharityPageData, - MainPageData, -} from '@/app/api-utlities/@types/pages'; -import { - presentCertificates, - presentCharities, - presentDiscounts, - presentHistoryItems, - presentJobs, - presentPartners, - presentReviews, - presentStations, - presentTeamMembers, -} from '@/app/api-utlities/presenters'; -import { aboutUsPageRequest } from '@/app/api-utlities/requests/about-us-page.request copy'; -import { certificatesPageRequest } from '@/app/api-utlities/requests/certificates-page.request'; -import { charityPageRequest } from '@/app/api-utlities/requests/charity-page.request copy'; -import { mainPageRequest } from '@/app/api-utlities/requests/main-page.request'; - -import { taylorAPI } from '@/shared/api/taylor-api'; - -export const mainPageApi = taylorAPI.injectEndpoints({ - endpoints: (builder) => ({ - fetchMainPageContent: builder.query({ - query: () => ({ - url: '', - method: 'POST', - body: { - query: jsonToGraphQLQuery({ query: mainPageRequest }), - }, - }), - - transformResponse: (response: any) => { - return { - partners: presentPartners(response.data.partnyory), - jobs: presentJobs(response.data.vakansii), - discounts: presentDiscounts(response.data.akcii), - stations: presentStations(response.data.azs), - }; - }, - }), - - fetchAboutUsPageContent: builder.query({ - query: () => ({ - url: '', - method: 'POST', - body: { - query: jsonToGraphQLQuery({ query: aboutUsPageRequest }), - }, - }), - - transformResponse: (response: any) => { - return { - team: presentTeamMembers(response.data.komanda), - history: presentHistoryItems(response.data.istoriyaKompanii), - stations: presentStations(response.data.azs), - reviews: presentReviews(response.data.otzyvy), - }; - }, - }), - - fetchCharityPageContent: builder.query({ - query: () => ({ - url: '', - method: 'POST', - body: { - query: jsonToGraphQLQuery({ query: charityPageRequest }), - }, - }), - - transformResponse: (response: any) => { - return { - charities: presentCharities(response.data.blagotvoritelnyjFond), - }; - }, - }), - - fetchCertificatesPageContent: builder.query({ - query: () => ({ - url: '', - method: 'POST', - body: { - query: jsonToGraphQLQuery({ query: certificatesPageRequest }), - }, - }), - - transformResponse: (response: any) => { - return { - certificates: presentCertificates(response.data.sertifikaty), - }; - }, - }), - }), -}); diff --git a/src/features/pages/services/pages.service.ts b/src/features/pages/services/pages.service.ts new file mode 100644 index 0000000..240d87c --- /dev/null +++ b/src/features/pages/services/pages.service.ts @@ -0,0 +1,182 @@ +import { + AboutUsPageData, + CertificatesPageData, + CharityPageData, + MainPageData, +} from '@/app/api-utlities/@types/pages'; +import { + presentCertificatesFromTaylor, + presentCharitiesFromTaylor, + presentDiscountsFromTaylor, + presentHistoryItemsFromTaylor, + presentJobsFromTaylor, + presentMediaFromTaylor, + presentPartnersFromTaylor, + presentReviewsFromTaylor, + presentStationsFromTaylor, + presentTeamMembersFromTaylor, + presentTextsFromTaylor, +} from '@/app/api-utlities/presenters/taylor-presenters'; + +import { taylorQueryBuilder } from '@/shared/api/taylor-query-builder'; + +/** + * Fetches main page content using TaylorDB query builder + * Replaces the RTK Query GraphQL approach with type-safe query builder + */ +export async function fetchMainPageContent(): Promise { + // Use batch queries to fetch all data in a single request + const [partnersData, jobsData, discountsData, stationsData] = + await taylorQueryBuilder + .batch([ + // Fetch partners + taylorQueryBuilder + .selectFrom('partnyory') + .selectAll() + .with({ + izobrozhenie: (qb) => qb.selectAll(), + }), + + // Fetch jobs + taylorQueryBuilder + .selectFrom('vakansii') + .selectAll() + .with({ + tegi: (qb) => qb.select(['name']), + }), + + // Fetch discounts + taylorQueryBuilder + .selectFrom('akcii') + .selectAll() + .with({ + foto: (qb) => qb.selectAll(), + }), + + // Fetch stations + taylorQueryBuilder + .selectFrom('azs') + .selectAll() + .with({ + foto: (qb) => qb.selectAll(), + }), + ]) + .execute(); + + // Transform the data using TaylorDB-specific presenters + // The query builder returns arrays directly + return { + partners: presentPartnersFromTaylor(partnersData), + jobs: presentJobsFromTaylor(jobsData), + discounts: presentDiscountsFromTaylor(discountsData), + stations: presentStationsFromTaylor(stationsData), + }; +} + +/** + * Fetches about us page content using TaylorDB query builder + */ +export async function fetchAboutUsPageContent(): Promise { + // Use batch queries to fetch all data in a single request + const [teamData, historyData, stationsData, reviewsData] = + await taylorQueryBuilder + .batch([ + // Fetch team members + taylorQueryBuilder + .selectFrom('komanda') + .selectAll() + .with({ + foto: (qb) => qb.selectAll(), + }), + + // Fetch history items + taylorQueryBuilder.selectFrom('istoriyaKompanii').selectAll(), + + // Fetch stations + taylorQueryBuilder + .selectFrom('azs') + .selectAll() + .with({ + foto: (qb) => qb.selectAll(), + }), + + // Fetch reviews (filtered by published status) + taylorQueryBuilder + .selectFrom('otzyvy') + .selectAll() + .where('status', '=', 'Опубликовано'), + ]) + .execute(); + + return { + team: presentTeamMembersFromTaylor(teamData), + history: presentHistoryItemsFromTaylor(historyData), + stations: presentStationsFromTaylor(stationsData), + reviews: presentReviewsFromTaylor(reviewsData), + }; +} + +/** + * Fetches charity page content using TaylorDB query builder + */ +export async function fetchCharityPageContent(): Promise { + const charitiesData = await taylorQueryBuilder + .selectFrom('blagotvoritelnyjFond') + .selectAll() + .with({ + foto: (qb) => qb.selectAll(), + }) + .execute(); + + return { + charities: presentCharitiesFromTaylor(charitiesData), + }; +} + +/** + * Fetches certificates page content using TaylorDB query builder + */ +export async function fetchCertificatesPageContent(): Promise { + const certificatesData = await taylorQueryBuilder + .selectFrom('sertifikaty') + .selectAll() + .with({ + foto: (qb) => qb.selectAll(), + }) + .execute(); + + return { + certificates: presentCertificatesFromTaylor(certificatesData), + }; +} + +/** + * Fetches text content using TaylorDB query builder + */ +export async function fetchTextContent(): Promise< + Array<{ key: string; value: string | null }> +> { + const textsData = await taylorQueryBuilder + .selectFrom('tekstovyjKontentSajta') + .selectAll() + .execute(); + + return presentTextsFromTaylor(textsData); +} + +/** + * Fetches media content using TaylorDB query builder + */ +export async function fetchMediaContent(): Promise< + Array<{ key: string; name: string; photo: string | null }> +> { + const mediaData = await taylorQueryBuilder + .selectFrom('mediaKontentSajta') + .selectAll() + .with({ + foto: (qb) => qb.selectAll(), + }) + .execute(); + + return presentMediaFromTaylor(mediaData); +} diff --git a/src/shared/api/taylor-query-builder.ts b/src/shared/api/taylor-query-builder.ts new file mode 100644 index 0000000..8731f02 --- /dev/null +++ b/src/shared/api/taylor-query-builder.ts @@ -0,0 +1,13 @@ +import { createQueryBuilder } from '@taylordb/query-builder'; + +import { TaylorDatabase } from '../types/database.types'; + +// Initialize TaylorDB query builder instance +// Note: If you have generated types from taylor.types.ts, you can import them here +// import { TaylorDatabase } from '@/path/to/taylor.types'; + +export const taylorQueryBuilder = createQueryBuilder({ + baseId: process.env.TAYLOR_BASE_ID || '', + baseUrl: process.env.TAYLOR_API_ENDPOINT || '', + apiKey: process.env.TAYLOR_API_TOKEN || '', +}); diff --git a/src/shared/language/api/text-control.api.ts b/src/shared/language/api/text-control.api.ts deleted file mode 100644 index 495780d..0000000 --- a/src/shared/language/api/text-control.api.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { jsonToGraphQLQuery } from 'json-to-graphql-query'; - -import { presentTexts } from '@/app/api-utlities/presenters'; -import { textsRequest } from '@/app/api-utlities/requests/common'; - -import { taylorAPI } from '@/shared/api/taylor-api'; -import { TextItem } from '@/shared/types/text.types'; - -export const textControlApi = taylorAPI.injectEndpoints({ - endpoints: (builder) => ({ - fetchText: builder.query({ - query: () => ({ - url: '', - method: 'POST', - body: { - query: jsonToGraphQLQuery({ query: textsRequest }), - }, - }), - - transformResponse: (response: any) => { - return presentTexts(response.data.tekstovyjKontentSajta); - }, - }), - }), -}); diff --git a/src/shared/types/database.types.ts b/src/shared/types/database.types.ts new file mode 100644 index 0000000..0d0fc13 --- /dev/null +++ b/src/shared/types/database.types.ts @@ -0,0 +1,548 @@ +/** + * Copyright (c) 2025 TaylorDB + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +interface FileInformation { + fieldname: string; + originalname: string; + encoding: string; + mimetype: string; + destination: string; + filename: string; + path: string; + size: number; + format: string; + width: number; + height: number; +} + +interface UploadResponse { + collectionName: string; + fileInformation: FileInformation; + metadata: { + thumbnails: any[]; + clips: any[]; + }; + baseId: string; + storageAdaptor: string; + _id: string; + __v: number; +} + +export interface AttachmentColumnValue { + url: string; + fileType: string; + size: number; +} + +export class Attachment { + public readonly collectionName: string; + public readonly fileInformation: FileInformation; + public readonly metadata: { thumbnails: any[]; clips: any[] }; + public readonly baseId: string; + public readonly storageAdaptor: string; + public readonly _id: string; + + constructor(data: UploadResponse) { + this.collectionName = data.collectionName; + this.fileInformation = data.fileInformation; + this.metadata = data.metadata; + this.baseId = data.baseId; + this.storageAdaptor = data.storageAdaptor; + this._id = data._id; + } + + toColumnValue(): AttachmentColumnValue { + return { + url: this.fileInformation.path, + fileType: this.fileInformation.mimetype, + size: this.fileInformation.size, + }; + } +} + +type IsWithinOperatorValue = + | 'pastWeek' + | 'pastMonth' + | 'pastYear' + | 'nextWeek' + | 'nextMonth' + | 'nextYear' + | 'daysFromNow' + | 'daysAgo' + | 'currentWeek' + | 'currentMonth' + | 'currentYear'; + +type DefaultDateFilterValue = + | ( + | 'today' + | 'tomorrow' + | 'yesterday' + | 'oneWeekAgo' + | 'oneWeekFromNow' + | 'oneMonthAgo' + | 'oneMonthFromNow' + ) + | ['exactDay' | 'exactTimestamp', string] + | ['daysAgo' | 'daysFromNow', number]; + +type DateFilters = { + '=': DefaultDateFilterValue; + '!=': DefaultDateFilterValue; + '<': DefaultDateFilterValue; + '>': DefaultDateFilterValue; + '<=': DefaultDateFilterValue; + '>=': DefaultDateFilterValue; + isWithIn: + | IsWithinOperatorValue + | { value: 'daysAgo' | 'daysFromNow'; date: number }; + isEmpty: boolean; + isNotEmpty: boolean; +}; + +type DateAggregations = { + empty: number; + filled: number; + unique: number; + percentEmpty: number; + percentFilled: number; + percentUnique: number; + min: number | null; + max: number | null; + daysRange: number | null; + monthRange: number | null; +}; + +type TextFilters = { + '=': string; + '!=': string; + caseEqual: string; + hasAnyOf: string[]; + contains: string; + startsWith: string; + endsWith: string; + doesNotContain: string; + isEmpty: never; + isNotEmpty: never; +}; + +type LinkFilters = { + hasAnyOf: number[]; + hasAllOf: number[]; + isExactly: number[]; + '=': number; + hasNoneOf: number[]; + contains: string; + doesNotContain: string; + isEmpty: never; + isNotEmpty: never; +}; + +type SelectFilters = { + hasAnyOf: O[number][]; + hasAllOf: O[number][]; + isExactly: O[number][]; + '=': O[number]; + hasNoneOf: O[number][]; + contains: string; + doesNotContain: string; + isEmpty: never; + isNotEmpty: never; +}; + +type LinkAggregations = { + empty: number; + filled: number; + percentEmpty: number; + percentFilled: number; +}; + +type NumberFilters = { + '=': number; + '!=': number; + '>': number; + '>=': number; + '<': number; + '<=': number; + hasAnyOf: number[]; + hasNoneOf: number[]; + isEmpty: never; + isNotEmpty: never; +}; + +type NumberAggregations = { + sum: number; + average: number; + median: number; + min: number | null; + max: number | null; + range: number; + standardDeviation: number; + histogram: Record; + empty: number; + filled: number; + unique: number; + percentEmpty: number; + percentFilled: number; + percentUnique: number; +}; + +type CheckboxFilters = { + '=': number; +}; + +/** + * + * Column types + * + */ +export type ColumnType< + S, + U, + I, + R extends boolean, + F extends { [key: string]: any } = object, + A extends { [key: string]: any } = object, +> = { + raw: S; + insert: I; + update: U; + filters: F; + aggregations: A; + isRequired: R; +}; + +export type DateColumnType = ColumnType< + string, + string, + string, + R, + DateFilters, + DateAggregations +>; + +export type TextColumnType = ColumnType< + string, + string, + string, + R, + TextFilters +>; + +export type ALinkColumnType< + T extends string, + S, + U, + I, + R extends boolean, + F extends { [key: string]: any } = LinkFilters, + A extends LinkAggregations = LinkAggregations, +> = ColumnType & { + linkedTo: T; +}; + +export type LinkColumnType< + T extends string, + R extends boolean, +> = ALinkColumnType< + T, + object, + number | number[] | { newIds: number[]; deletedIds: number[] }, + number | number[], + R +>; + +export type AttachmentColumnType = ALinkColumnType< + 'attachmentTable', + AttachmentColumnValue[], + Attachment[] | { newIds: number[]; deletedIds: number[] } | number[], + Attachment[] | number[], + R +>; + +export type NumberColumnType = ColumnType< + number, + number, + number, + R, + NumberFilters, + NumberAggregations +>; + +export type CheckboxColumnType = ColumnType< + boolean, + boolean, + boolean, + R, + CheckboxFilters +>; + +export type AutoGeneratedNumberColumnType = ColumnType< + number, + never, + never, + false, + NumberFilters, + NumberAggregations +>; + +export type AutoGeneratedDateColumnType = ColumnType< + string, + never, + never, + false, + DateFilters, + DateAggregations +>; + +export type SingleSelectColumnType< + O extends readonly string[], + R extends boolean, +> = ALinkColumnType< + 'selectTable', + O[number], + O[number] | O[number][], + O[number] | O[number][], + R, + SelectFilters +>; + +export type TableRaws = { + [K in keyof TaylorDatabase[T]]: TaylorDatabase[T][K] extends ColumnType< + infer S, + any, + any, + infer R, + any, + any + > + ? R extends true + ? S + : S | undefined + : never; +}; + +export type TableInserts = { + [K in keyof TaylorDatabase[T]]: TaylorDatabase[T][K] extends ColumnType< + any, + infer I, + any, + infer R, + any, + any + > + ? R extends true + ? I + : I | undefined + : never; +}; + +export type TableUpdates = { + [K in keyof TaylorDatabase[T]]: TaylorDatabase[T][K] extends ColumnType< + any, + any, + infer U, + any, + any, + any + > + ? U + : never; +}; + +export type SelectTable = { + id: AutoGeneratedNumberColumnType; + name: TextColumnType; + color: TextColumnType; +}; + +export type AttachmentTable = { + id: AutoGeneratedNumberColumnType; + name: TextColumnType; + metadata: TextColumnType; + size: NumberColumnType; + fileType: TextColumnType; + url: TextColumnType; +}; + +export type CollaboratorsTable = { + id: AutoGeneratedNumberColumnType; + name: TextColumnType; + emailAddress: TextColumnType; + avatar: TextColumnType; +}; + +export type TaylorDatabase = { + /** + * + * + * Internal tables, these tables can not be queried directly. + * + */ + selectTable: SelectTable; + attachmentTable: AttachmentTable; + collaboratorsTable: CollaboratorsTable; + vakansii: VakansiiTable; + partnyory: PartnyoryTable; + azs: AzsTable; + akcii: AkciiTable; + istoriyaKompanii: IstoriyaKompaniiTable; + komanda: KomandaTable; + otzyvy: OtzyvyTable; + tekstovyjKontentSajta: TekstovyjKontentSajtaTable; + sertifikaty: SertifikatyTable; + mediaKontentSajta: MediaKontentSajtaTable; + blagotvoritelnyjFond: BlagotvoritelnyjFondTable; +}; + +export const VakansiiTipOptions = ['Офис', 'Заправки'] as const; +export const VakansiiLokaciyaOptions = ['Душанбе'] as const; + +type VakansiiTable = { + id: NumberColumnType; + createdAt: AutoGeneratedDateColumnType; + updatedAt: AutoGeneratedDateColumnType; + zagolovok: TextColumnType; + tip: SingleSelectColumnType; + lokaciya: SingleSelectColumnType; + tegi: LinkColumnType<'selectTable', false>; +}; +type PartnyoryTable = { + id: NumberColumnType; + createdAt: AutoGeneratedDateColumnType; + updatedAt: AutoGeneratedDateColumnType; + nazvanie: TextColumnType; + izobrozhenie: AttachmentColumnType; +}; + +export const AzsChasyRabotyOptions = ['Круглосуточно'] as const; +export const AzsRegionOptions = [ + 'Душанбе', + 'Бохтар', + 'Худжанд', + 'Регар', + 'Вахдат', + 'А.Джоми', + 'Обикиик', + 'Кулоб', + 'Дахана', + 'Ёвон', + 'Панч', + 'Исфара', + 'Мастчох', + 'Хисор', +] as const; + +type AzsTable = { + id: NumberColumnType; + createdAt: AutoGeneratedDateColumnType; + updatedAt: AutoGeneratedDateColumnType; + imya: TextColumnType; + adress: TextColumnType; + opisanie: TextColumnType; + chasyRaboty: SingleSelectColumnType; + lat: TextColumnType; + long: TextColumnType; + avtomojka: CheckboxColumnType; + dt: CheckboxColumnType; + ai92: CheckboxColumnType; + ai95: CheckboxColumnType; + z100: CheckboxColumnType; + propan: CheckboxColumnType; + zaryadnayaStanciya: CheckboxColumnType; + miniMarket: CheckboxColumnType; + tualet: CheckboxColumnType; + region: SingleSelectColumnType; + foto: AttachmentColumnType; +}; +type AkciiTable = { + id: NumberColumnType; + createdAt: AutoGeneratedDateColumnType; + updatedAt: AutoGeneratedDateColumnType; + zagolovok: TextColumnType; + opisanie: TextColumnType; + do: DateColumnType; + foto: AttachmentColumnType; +}; +type IstoriyaKompaniiTable = { + id: NumberColumnType; + createdAt: AutoGeneratedDateColumnType; + updatedAt: AutoGeneratedDateColumnType; + zagolovok: TextColumnType; + god: NumberColumnType; + opisanie: TextColumnType; +}; +type KomandaTable = { + id: NumberColumnType; + createdAt: AutoGeneratedDateColumnType; + updatedAt: AutoGeneratedDateColumnType; + polnoeImya: TextColumnType; + foto: AttachmentColumnType; + zvanie: TextColumnType; +}; + +export const OtzyvyStatusOptions = ['Опубликовано'] as const; + +type OtzyvyTable = { + id: NumberColumnType; + createdAt: AutoGeneratedDateColumnType; + updatedAt: AutoGeneratedDateColumnType; + polnoeImya: TextColumnType; + otzyv: TextColumnType; + rejting: NumberColumnType; + status: SingleSelectColumnType; +}; +type TekstovyjKontentSajtaTable = { + id: NumberColumnType; + createdAt: AutoGeneratedDateColumnType; + updatedAt: AutoGeneratedDateColumnType; + klyuchNeIzmenyat: TextColumnType; + znachenie: TextColumnType; + opisanie: LinkColumnType<'selectTable', false>; +}; +type SertifikatyTable = { + id: NumberColumnType; + createdAt: AutoGeneratedDateColumnType; + updatedAt: AutoGeneratedDateColumnType; + nazvanie: TextColumnType; + opisanie: TextColumnType; + dataVydachi: DateColumnType; + dejstvitelenDo: DateColumnType; + foto: AttachmentColumnType; +}; + +export const MediaKontentSajtaStranicaOptions = [ + 'Главная', + 'О нас', + 'Благотворительность', + 'Общая', + 'Клиенты', + 'Программа лояльности', +] as const; + +type MediaKontentSajtaTable = { + id: NumberColumnType; + createdAt: AutoGeneratedDateColumnType; + updatedAt: AutoGeneratedDateColumnType; + mestopolozheniya: TextColumnType; + klyuchNeIzmenyat: TextColumnType; + foto: AttachmentColumnType; + stranica: SingleSelectColumnType< + typeof MediaKontentSajtaStranicaOptions, + false + >; +}; +type BlagotvoritelnyjFondTable = { + id: NumberColumnType; + createdAt: AutoGeneratedDateColumnType; + updatedAt: AutoGeneratedDateColumnType; + zagolovok: TextColumnType; + opisanie: TextColumnType; + data: DateColumnType; + lokaciya: TextColumnType; + foto: AttachmentColumnType; +};