mirror of
https://github.com/spbleadersofdigtal/frontend.git
synced 2024-11-27 12:33:43 +03:00
добавил презентацию
This commit is contained in:
parent
72505624d0
commit
903461baa7
|
@ -14,6 +14,7 @@
|
|||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"axios": "^1.4.0",
|
||||
"chart.js": "^4.4.0",
|
||||
"clsx": "^2.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"focus-visible": "^5.2.0",
|
||||
|
|
49
src/api/deck/getDeck.ts
Normal file
49
src/api/deck/getDeck.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import {Question} from './types';
|
||||
import {axios} from '../../lib/axios';
|
||||
import {
|
||||
DECK_API_URL,
|
||||
QUESTION_API_URL,
|
||||
QUESTION_PARAM_DECK_ID,
|
||||
QUESTION_PARAM_QUESTION_ID,
|
||||
} from './urlKeys';
|
||||
import {ExtractFnReturnType, QueryConfig} from '../../lib/react-query';
|
||||
import {useQuery} from '@tanstack/react-query';
|
||||
import {QUERY_KEY_DECKS, QUERY_KEY_QUESTION} from './queryKeys';
|
||||
|
||||
export type GetDeckResponse = {
|
||||
deck: {
|
||||
name: string;
|
||||
description: string;
|
||||
},
|
||||
slides: {
|
||||
slide: number;
|
||||
data: {
|
||||
slug: string;
|
||||
answer: any;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
|
||||
export const getDeck = ({ deckId }: { deckId: number }): Promise<GetDeckResponse> => {
|
||||
return axios.get(
|
||||
DECK_API_URL
|
||||
.replace(`:${QUESTION_PARAM_DECK_ID}`, String(deckId))
|
||||
);
|
||||
};
|
||||
|
||||
type QueryFnType = typeof getDeck;
|
||||
|
||||
type UseDeckOptions = {
|
||||
deckId: number;
|
||||
config?: QueryConfig<QueryFnType>;
|
||||
};
|
||||
|
||||
export const useDeck = ({ deckId, config }: UseDeckOptions) => {
|
||||
return useQuery<ExtractFnReturnType<QueryFnType>>({
|
||||
...config,
|
||||
queryKey: [QUERY_KEY_DECKS, deckId],
|
||||
queryFn: async () => {
|
||||
return await getDeck({ deckId });
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,9 +1,11 @@
|
|||
export const DECKS_API_URL = '/decks/';
|
||||
|
||||
|
||||
export const FIRST_QUESTION_PARAM = 'deckId';
|
||||
export const FIRST_QUESTION_API_URL = `/decks/question/:${FIRST_QUESTION_PARAM}`;
|
||||
|
||||
export const QUESTION_PARAM_DECK_ID = 'deckId';
|
||||
export const QUESTION_PARAM_QUESTION_ID = 'questionId';
|
||||
export const QUESTION_API_URL = `/decks/question/:${FIRST_QUESTION_PARAM}/:${QUESTION_PARAM_QUESTION_ID}`;
|
||||
|
||||
export const DECK_PARAM = 'deckId';
|
||||
export const DECK_API_URL = `/decks/question/:${FIRST_QUESTION_PARAM}/presentation`;
|
|
@ -1,6 +1,5 @@
|
|||
$font-family-raleway: 'Raleway';
|
||||
$font-family-ibm-plex-mono: 'IBM Plex Mono';
|
||||
$font-family-lora: 'Lora', serif;
|
||||
|
||||
$font-family-default: system-ui, /* macOS 10.11-10.12 */ -apple-system, /* Windows 6+ */ 'Segoe UI',
|
||||
/* Android 4+ */ 'Roboto', /* Ubuntu 10.10+ */ 'Ubuntu', /* Gnome 3+ */ 'Cantarell', /* KDE Plasma 5+ */ 'Noto Sans',
|
||||
|
@ -152,10 +151,3 @@ $line-height-67: 67px;
|
|||
font-weight: $font-weight-400;
|
||||
line-height: $line-height-16;
|
||||
}
|
||||
|
||||
@mixin text-presentation {
|
||||
font-family: $font-family-lora;
|
||||
font-size: $font-size-16;
|
||||
font-weight: $font-weight-400;
|
||||
line-height: $line-height-20;
|
||||
}
|
||||
|
|
|
@ -46,11 +46,3 @@ $fonts-path: '../../assets/fonts' !default;
|
|||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: $font-family-lora;
|
||||
src: url('#{$fonts-path}/Lora/Lora-Regular.ttf') format('truetype');
|
||||
font-weight: $font-weight-400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
3
src/assets/icons/loader.svg
Normal file
3
src/assets/icons/loader.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 3C11.4474 3 10.9994 3.448 10.9994 4.00065C10.9994 4.55329 11.4474 5.00129 12 5.00129C15.8653 5.00129 18.9987 8.13472 18.9987 12C18.9987 15.8653 15.8653 18.9987 12 18.9987C8.13472 18.9987 5.00129 15.8653 5.00129 12C5.00129 11.4474 4.55329 10.9994 4.00065 10.9994C3.448 10.9994 3 11.4474 3 12C3 16.9706 7.02944 21 12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 556 B |
|
@ -54,7 +54,7 @@
|
|||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
background-color: white;
|
||||
background-color: $color-on-surface-light-100;
|
||||
z-index: 1;
|
||||
padding: 2px 4px;
|
||||
width: 16px;
|
||||
|
|
25
src/components/Loader/Loader.module.scss
Normal file
25
src/components/Loader/Loader.module.scss
Normal file
|
@ -0,0 +1,25 @@
|
|||
@import 'src/app/styles/vars';
|
||||
|
||||
.Loader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.LoaderIcon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: $color-on-surface-dark-100;
|
||||
animation: spinner 0.7s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spinner {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
35
src/components/Loader/Loader.tsx
Normal file
35
src/components/Loader/Loader.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { memo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import s from './Loader.module.scss';
|
||||
import { ReactComponent as LoaderIcon } from '../../assets/icons/loader.svg';
|
||||
|
||||
// export enum LoaderSize {
|
||||
// /**
|
||||
// * Размер лоадера 20х20
|
||||
// */
|
||||
// small = 'small',
|
||||
// /**
|
||||
// * Размер лоадера 30х30
|
||||
// */
|
||||
// medium = 'medium',
|
||||
// /**
|
||||
// * Размер лоадера 40х40
|
||||
// */
|
||||
// large = 'large'
|
||||
// }
|
||||
|
||||
export interface LoaderProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Loader = memo((props: LoaderProps) => {
|
||||
const { className } = props;
|
||||
|
||||
return (
|
||||
<div className={clsx(s.Loader, className)}>
|
||||
<LoaderIcon className={clsx(s.LoaderIcon)} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Loader.displayName = 'Loader';
|
1
src/components/Loader/index.ts
Normal file
1
src/components/Loader/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './Loader';
|
|
@ -1,3 +1,3 @@
|
|||
// export const API_URL = 'http://192.168.83.181:8000/api';
|
||||
export const API_URL = 'http://192.168.83.181:8000/api';
|
||||
// export const API_URL = 'http://192.168.22.4:8000/api';
|
||||
export const API_URL = 'https://ed68-77-234-219-9.ngrok-free.app/api';
|
||||
// export const API_URL = 'https://ed68-77-234-219-9.ngrok-free.app/api';
|
|
@ -1,38 +1,28 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
// import {App} from './app';
|
||||
import {App} from './app';
|
||||
import {AppProvider} from './app';
|
||||
import {PDFViewer, pdf} from '@react-pdf/renderer';
|
||||
import {Document} from './pages/deck/document/Document';
|
||||
|
||||
// import 'focus-visible';
|
||||
//
|
||||
// import 'sanitize.css';
|
||||
// import 'sanitize.css/forms.css';
|
||||
// import 'sanitize.css/typography.css';
|
||||
// import './app/styles/index.scss';
|
||||
import 'focus-visible';
|
||||
|
||||
import 'sanitize.css';
|
||||
import 'sanitize.css/forms.css';
|
||||
import 'sanitize.css/typography.css';
|
||||
import './app/styles/index.scss';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
// root.render(
|
||||
// // <React.StrictMode>
|
||||
// <AppProvider>
|
||||
// <App />
|
||||
// </AppProvider>
|
||||
// // </React.StrictMode>
|
||||
// );
|
||||
root.render(
|
||||
// <React.StrictMode>
|
||||
<AppProvider>
|
||||
<App />
|
||||
</AppProvider>
|
||||
// </React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
// reportWebVitals();
|
||||
|
||||
const App = () => (
|
||||
<PDFViewer>
|
||||
<Document />
|
||||
</PDFViewer>
|
||||
);
|
||||
|
||||
root.render(<App />);
|
||||
reportWebVitals();
|
||||
|
|
|
@ -16,6 +16,8 @@ import {usePrevious} from '../../hooks/usePrevious';
|
|||
import {generateAnswerFromData} from './utils/generateAnswerFromData';
|
||||
import {generateTextFromAnswer} from './utils/generateTextFromAnswer';
|
||||
import {Button, ButtonVariant} from '../../components/Button';
|
||||
import {useNavigate} from 'react-router-dom';
|
||||
import {DECK_PAGE_PARAM, DECK_PAGE_ROUTE} from '../../app/routes';
|
||||
|
||||
export interface ChatPageProps {
|
||||
/**
|
||||
|
@ -26,8 +28,8 @@ export interface ChatPageProps {
|
|||
|
||||
const QUESTION_POLLING_MS = 1000;
|
||||
|
||||
const DEFAULT_DECK_ID = 65;
|
||||
const DEFAULT_QUESTION_ID = 32;
|
||||
const DEFAULT_DECK_ID = 0;
|
||||
const DEFAULT_QUESTION_ID = 0;
|
||||
|
||||
export const ChatPage: ReactFCC<ChatPageProps> = (props) => {
|
||||
const {className} = props;
|
||||
|
@ -115,6 +117,7 @@ export const ChatPage: ReactFCC<ChatPageProps> = (props) => {
|
|||
|
||||
|
||||
const { mutateAsync: createAnswer } = useCreateAnswer();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onSubmit: SubmitHandler<any> = useCallback(async (data) => {
|
||||
if (!question || !data.value) {
|
||||
|
@ -136,16 +139,24 @@ export const ChatPage: ReactFCC<ChatPageProps> = (props) => {
|
|||
text: generateTextFromAnswer(question.type, answer)
|
||||
});
|
||||
|
||||
if (question.next_id) {
|
||||
setQuestionId(question!.next_id);
|
||||
} else {
|
||||
navigate(DECK_PAGE_ROUTE.replace(`:${DECK_PAGE_PARAM}`, String(deckId)))
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [createAnswer, deckId, pushHistory, question, questionId]);
|
||||
|
||||
// Пропуск вопроса
|
||||
const onSkip = useCallback(() => {
|
||||
if (question && !question.params?.required) {
|
||||
setQuestionId(question.next_id);
|
||||
if (question.next_id) {
|
||||
setQuestionId(question!.next_id);
|
||||
} else {
|
||||
navigate(DECK_PAGE_ROUTE.replace(`:${DECK_PAGE_PARAM}`, String(deckId)))
|
||||
}
|
||||
}, [question]);
|
||||
}
|
||||
}, [deckId, navigate, question]);
|
||||
|
||||
// ---------- Скролл чата ----------
|
||||
// todo при печатании текста тоже двигать скролл
|
||||
|
|
|
@ -36,7 +36,6 @@ export const ChatFormMultipleDateDescription: ReactFCC<ChatFormMultipleDateDescr
|
|||
for (const date in hint.value) {
|
||||
newValue[format(new Date(date),'yyyy-MM-dd')] = hint.value[date]
|
||||
}
|
||||
console.log(hint.value);
|
||||
setValue({ ...hint.value })
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -49,7 +49,6 @@ export const ChatFormMultipleLinkDescription: ReactFCC<ChatFormMultipleLinkDescr
|
|||
const newValue = { ...value };
|
||||
const text = newValue[link];
|
||||
delete newValue[link];
|
||||
console.log()
|
||||
onChange({ ...newValue, [e.target.value]: text })
|
||||
}}
|
||||
/>
|
||||
|
|
14
src/pages/deck/DeckPage.module.scss
Normal file
14
src/pages/deck/DeckPage.module.scss
Normal file
|
@ -0,0 +1,14 @@
|
|||
@import 'src/app/styles/vars';
|
||||
|
||||
.DeckPage {
|
||||
position: relative;
|
||||
height: 100svh;
|
||||
}
|
||||
|
||||
.DeckPage__loader {
|
||||
width: 50px !important;
|
||||
height: 50px !important;
|
||||
position: absolute;
|
||||
top: calc(50% - 25px);
|
||||
left: calc(50% - 25px);
|
||||
}
|
|
@ -2,15 +2,89 @@ import {ReactFCC} from '../../utils/ReactFCC';
|
|||
import {PDFViewer} from '@react-pdf/renderer';
|
||||
import {useUrlParam} from '../../hooks/useUrlParam';
|
||||
import {DECK_PAGE_PARAM} from '../../app/routes';
|
||||
import {Document} from './document/Document';
|
||||
import {MyDocument} from './document/Document';
|
||||
import {useDeck} from '../../api/deck/getDeck';
|
||||
import {useEffect, useRef, useState} from 'react';
|
||||
import {generateMarketChart} from './document/media/generateMarketChart';
|
||||
import {Loader} from '../../components/Loader';
|
||||
import s from './DeckPage.module.scss';
|
||||
import {generateFinancialChart} from './document/media/generateFinancialChart';
|
||||
import {generateGrowChart} from './document/media/generateGrowChart';
|
||||
|
||||
export const DeckPage: ReactFCC = () => {
|
||||
const deckId = useUrlParam(DECK_PAGE_PARAM, {parser: parseInt});
|
||||
console.log(deckId);
|
||||
|
||||
const { data } = useDeck({
|
||||
deckId: deckId ?? 0,
|
||||
config: {
|
||||
enabled: !!deckId
|
||||
}
|
||||
});
|
||||
|
||||
const [marketChart, setMarketChart] = useState('');
|
||||
const [financialChart, setFinancialChart] = useState('');
|
||||
const [growChart, setGrowChart] = useState('');
|
||||
|
||||
const [rendered, setRendered] = useState(false);
|
||||
|
||||
const initRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || initRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
initRef.current = true;
|
||||
|
||||
const market_values = data.slides[4].data.find((i) => i.slug === 'market_values')?.answer as any;
|
||||
const users_metrics = data.slides[7].data.find((i) => i.slug === 'users_metrics')?.answer as any;
|
||||
|
||||
const company_value = data.slides[10].data.find((i) => i.slug === 'company_value')?.answer.sum as number;
|
||||
const future_value = data.slides[10].data.find((i) => i.slug === 'future_value')?.answer.sum as number;
|
||||
const time_to_spend = data.slides[10].data.find((i) => i.slug === 'time_to_spend')?.answer as any;
|
||||
|
||||
generateMarketChart(
|
||||
'chart1',
|
||||
[market_values.tam, market_values.sam, market_values.som],
|
||||
(data) => setMarketChart(data)
|
||||
);
|
||||
|
||||
generateFinancialChart(
|
||||
'chart2',
|
||||
[users_metrics.cac, users_metrics.ltv],
|
||||
(data) => setFinancialChart(data)
|
||||
);
|
||||
|
||||
generateGrowChart(
|
||||
'chart3',
|
||||
[company_value, future_value, time_to_spend],
|
||||
(data) => setGrowChart(data)
|
||||
);
|
||||
}, [data]);
|
||||
|
||||
|
||||
return (
|
||||
<PDFViewer>
|
||||
<Document />
|
||||
<div className={s.DeckPage}>
|
||||
{!rendered && (
|
||||
<Loader className={s.DeckPage__loader} />
|
||||
)}
|
||||
{data && marketChart && financialChart && growChart ? (
|
||||
<PDFViewer style={{ width: '100vw', height: '100vh' }}>
|
||||
<MyDocument
|
||||
onRender={() => setRendered(true)}
|
||||
data={data}
|
||||
marketChart={marketChart}
|
||||
financialChart={financialChart}
|
||||
growChart={growChart}
|
||||
/>
|
||||
</PDFViewer>
|
||||
) : (
|
||||
<div style={{ visibility: 'hidden' }}>
|
||||
<canvas id={'chart1'} />
|
||||
<canvas id={'chart2'} />
|
||||
<canvas id={'chart3'} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,33 +1,54 @@
|
|||
import {ReactFCC} from '../../../utils/ReactFCC';
|
||||
import {Page, View, Text, StyleSheet} from '@react-pdf/renderer';
|
||||
import {Document, Font} from '@react-pdf/renderer';
|
||||
import {Slide1} from './slides/Slide1';
|
||||
import {GetDeckResponse} from '../../../api/deck/getDeck';
|
||||
import {Slide2} from './slides/Slide2';
|
||||
import {Slide3} from './slides/Slide3';
|
||||
import {Slide4} from './slides/Slide4';
|
||||
import {Slide5} from './slides/Slide5';
|
||||
import {Slide6} from './slides/Slide6';
|
||||
import {Slide8} from './slides/Slide8';
|
||||
import {Slide7} from './slides/Slide7';
|
||||
import Chart from 'chart.js/auto';
|
||||
import {Slide9} from './slides/Slide9';
|
||||
import {Slide10} from './slides/Slide10';
|
||||
import {Slide11} from './slides/Slide11';
|
||||
import {Slide12} from './slides/Slide12';
|
||||
import {Slide13} from './slides/Slide13';
|
||||
|
||||
export interface Props {
|
||||
Chart.defaults.font.size = 20;
|
||||
|
||||
export interface MyDocumentProps {
|
||||
data: GetDeckResponse;
|
||||
onRender: () => void;
|
||||
marketChart: string;
|
||||
financialChart: string;
|
||||
growChart: string;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#E4E4E4'
|
||||
},
|
||||
section: {
|
||||
margin: 10,
|
||||
padding: 10,
|
||||
flexGrow: 1
|
||||
}
|
||||
Font.register({
|
||||
family: "Roboto",
|
||||
src: "https://cdnjs.cloudflare.com/ajax/libs/ink/3.1.10/fonts/Roboto/roboto-medium-webfont.ttf"
|
||||
});
|
||||
|
||||
export const Document: ReactFCC<Props> = (props) => {
|
||||
export const MyDocument: ReactFCC<MyDocumentProps> = (props) => {
|
||||
const { data, marketChart, financialChart, growChart, onRender } = props;
|
||||
|
||||
return (
|
||||
<Document>
|
||||
<Page>
|
||||
<View>
|
||||
<Text>Section #1</Text>
|
||||
</View>
|
||||
<View>
|
||||
<Text>Section #2</Text>
|
||||
</View>
|
||||
</Page>
|
||||
<Document onRender={onRender}>
|
||||
<Slide1 title={data.deck.name} description={data.deck.description} data={data.slides[0].data} />
|
||||
<Slide2 data={data.slides[1].data} />
|
||||
<Slide3 data={data.slides[2].data} />
|
||||
<Slide4 data={data.slides[3].data} />
|
||||
<Slide5 data={data.slides[4].data} marketChart={marketChart} />
|
||||
<Slide6 data={data.slides[5].data} />
|
||||
<Slide7 data={data.slides[6].data} />
|
||||
<Slide8 data={data.slides[7].data} financialChart={financialChart} />
|
||||
<Slide9 data={data.slides[8].data} />
|
||||
<Slide10 data={data.slides[9].data} />
|
||||
<Slide11 data={data.slides[10].data} growChart={growChart} />
|
||||
<Slide12 data={data.slides[11].data} />
|
||||
<Slide13 data={data.slides[12].data} />
|
||||
</Document>
|
||||
);
|
||||
};
|
||||
|
|
28
src/pages/deck/document/media/generateFinancialChart.ts
Normal file
28
src/pages/deck/document/media/generateFinancialChart.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import Chart from 'chart.js/auto';
|
||||
|
||||
export const generateFinancialChart = (rootId: string, [cac, ltv]: [number, number], callback: (data: string) => void) => {
|
||||
const chart = new Chart((document.getElementById(rootId) as HTMLCanvasElement).getContext('2d') as CanvasRenderingContext2D, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['LTV', 'CAC'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Сравнение LTV (Lifetime Value) и CAC (Customer Acquisition Cost)',
|
||||
data: [ltv, cac],
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.3)',
|
||||
borderColor: 'rgba(255,99,132,1)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
aspectRatio: 2,
|
||||
animation: {
|
||||
onComplete: function () {
|
||||
callback(chart.toBase64Image());
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
32
src/pages/deck/document/media/generateGrowChart.ts
Normal file
32
src/pages/deck/document/media/generateGrowChart.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import Chart from 'chart.js/auto';
|
||||
import format from 'date-fns/format';
|
||||
import addYears from 'date-fns/addYears';
|
||||
|
||||
export const generateGrowChart = (rootId: string, [company_value, future_value, date]: [number, number, Date], callback: (data: string) => void) => {
|
||||
const chart = new Chart((document.getElementById(rootId) as HTMLCanvasElement).getContext('2d') as CanvasRenderingContext2D, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [
|
||||
format(new Date(), 'dd.MM.yyyy'),
|
||||
format(date ? new Date(date) : addYears(new Date(), 1), 'dd.MM.yyyy')],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Рост после привлечения инвестиций',
|
||||
data: [company_value, future_value],
|
||||
fill: false,
|
||||
borderColor: 'rgba(255, 99, 132, 0.5)',
|
||||
tension: 0.1
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
// indexAxis: 'y',
|
||||
aspectRatio: 2,
|
||||
animation: {
|
||||
onComplete: function () {
|
||||
callback(chart.toBase64Image());
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
28
src/pages/deck/document/media/generateMarketChart.ts
Normal file
28
src/pages/deck/document/media/generateMarketChart.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import Chart from 'chart.js/auto';
|
||||
|
||||
export const generateMarketChart = (rootId: string, [tam, sam, som]: [number, number, number], callback: (data: string) => void) => {
|
||||
const chart = new Chart((document.getElementById(rootId) as HTMLCanvasElement).getContext('2d') as CanvasRenderingContext2D, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['SAM', 'SOM'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Сравнение SAM и SOM',
|
||||
data: [sam, som],
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.3)',
|
||||
borderColor: 'rgba(255,99,132,1)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
aspectRatio: 2,
|
||||
animation: {
|
||||
onComplete: function () {
|
||||
callback(chart.toBase64Image());
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
2
src/pages/deck/document/shared.ts
Normal file
2
src/pages/deck/document/shared.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export const bgColor = '#EFEEE7';
|
||||
export const primaryColor = '#2B2C30';
|
81
src/pages/deck/document/slides/Slide1.tsx
Normal file
81
src/pages/deck/document/slides/Slide1.tsx
Normal file
|
@ -0,0 +1,81 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import format from 'date-fns/format';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
|
||||
export interface Slide1Props {
|
||||
title: string;
|
||||
description: string;
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
padding: '56px',
|
||||
fontFamily: 'Roboto'
|
||||
},
|
||||
mainBlock: {
|
||||
flex: '1',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'stretch'
|
||||
},
|
||||
title: {
|
||||
fontSize: 96,
|
||||
width: '100%',
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '0 0'
|
||||
},
|
||||
description: {
|
||||
marginTop: '16px',
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
},
|
||||
secondaryBlock: {
|
||||
//
|
||||
},
|
||||
info: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
rowGap: 4
|
||||
},
|
||||
infoText: {
|
||||
fontFamily: 'Helvetica',
|
||||
fontSize: '14px',
|
||||
}
|
||||
});
|
||||
|
||||
export const Slide1: ReactFCC<Slide1Props> = (props) => {
|
||||
const { title, description, data } = props;
|
||||
|
||||
const name = data.find((i) => i.slug === 'names')?.answer as string;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View style={styles.mainBlock}>
|
||||
<Text style={styles.title}>{name}</Text>
|
||||
<View style={styles.divider} />
|
||||
<Text style={styles.description}>{description}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.secondaryBlock}>
|
||||
<View style={styles.info}>
|
||||
<Text style={styles.infoText}>Henrietta Mitchell, Founder & CEO</Text>
|
||||
<Text style={styles.infoText}>{format(new Date(), 'dd MMM, yyyy')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
69
src/pages/deck/document/slides/Slide10.tsx
Normal file
69
src/pages/deck/document/slides/Slide10.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
import {currencyFormatter, formatDate} from '../../../../utils/fomat';
|
||||
|
||||
export interface Slide10Props {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
text: {
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
marginBottom: 24
|
||||
},
|
||||
subtitle: {
|
||||
width: '100%',
|
||||
fontSize: '24px',
|
||||
},
|
||||
});
|
||||
|
||||
export const Slide10: ReactFCC<Slide10Props> = (props) => {
|
||||
const { data } = props;
|
||||
|
||||
const how_much_investments = data.find((i) => i.slug === 'how_much_investments')?.answer.sum as any;
|
||||
const time_to_spend = data.find((i) => i.slug === 'time_to_spend')?.answer as any;
|
||||
const investments_sold = data.find((i) => i.slug === 'investments_sold')?.answer as any;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Инвестиционный раунд</Text>
|
||||
<View style={styles.divider} />
|
||||
|
||||
<Text style={styles.subtitle}>Сколько инвестиций необходимо</Text>
|
||||
<Text style={styles.text}>{currencyFormatter.format(how_much_investments)}</Text>
|
||||
|
||||
<Text style={styles.subtitle}>Цели инвестиций</Text>
|
||||
<Text style={styles.text}>{investments_sold}</Text>
|
||||
|
||||
<Text style={styles.subtitle}>Срок освоения инвестиций</Text>
|
||||
<Text style={styles.text}>{formatDate(time_to_spend)}</Text>
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
63
src/pages/deck/document/slides/Slide11.tsx
Normal file
63
src/pages/deck/document/slides/Slide11.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
|
||||
export interface Slide11Props {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
growChart: string;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
text: {
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
marginBottom: 24
|
||||
},
|
||||
subtitle: {
|
||||
width: '100%',
|
||||
fontSize: '24px',
|
||||
},
|
||||
image: {
|
||||
maxWidth: '600px',
|
||||
maxHeight: '300px',
|
||||
margin: '50 auto 0'
|
||||
}
|
||||
});
|
||||
|
||||
export const Slide11: ReactFCC<Slide11Props> = (props) => {
|
||||
const { data, growChart } = props;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Инвестиционный раунд</Text>
|
||||
<View style={styles.divider} />
|
||||
|
||||
<Image style={styles.image} src={growChart} />
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
91
src/pages/deck/document/slides/Slide12.tsx
Normal file
91
src/pages/deck/document/slides/Slide12.tsx
Normal file
|
@ -0,0 +1,91 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
import {formatDate} from '../../../../utils/fomat';
|
||||
import {Fragment} from 'react';
|
||||
|
||||
export interface Slide12Props {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: '18px',
|
||||
marginBottom: '4px',
|
||||
marginLeft: '-6px'
|
||||
},
|
||||
text: {
|
||||
fontSize: '14px',
|
||||
marginLeft: '-6px'
|
||||
},
|
||||
map: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginTop: 70
|
||||
},
|
||||
item: {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
borderTop: '1px solid black',
|
||||
padding: '16px 16px 0 0',
|
||||
},
|
||||
dot: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
backgroundColor: 'black',
|
||||
borderRadius: '50%',
|
||||
marginTop: '-3.5px'
|
||||
}
|
||||
});
|
||||
|
||||
export const Slide12: ReactFCC<Slide12Props> = (props) => {
|
||||
const { data } = props;
|
||||
|
||||
const aims = data.find((i) => i.slug === 'aims')?.answer as any;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Роадмап</Text>
|
||||
<View style={styles.divider} />
|
||||
|
||||
<View style={styles.map}>
|
||||
<View style={styles.dot} />
|
||||
{Object.entries(aims).map(([date, text], index) => (
|
||||
<Fragment key={index}>
|
||||
<View style={styles.item}>
|
||||
<Text style={styles.subtitle}>{formatDate(date)}</Text>
|
||||
<Text style={styles.text}>{text as string}</Text>
|
||||
</View>
|
||||
<View style={styles.dot} />
|
||||
</Fragment>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
67
src/pages/deck/document/slides/Slide13.tsx
Normal file
67
src/pages/deck/document/slides/Slide13.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
import {formatDate} from '../../../../utils/fomat';
|
||||
import {Fragment} from 'react';
|
||||
|
||||
export interface Slide13Props {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
text: {
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
marginBottom: 24
|
||||
},
|
||||
subtitle: {
|
||||
width: '100%',
|
||||
fontSize: '24px',
|
||||
marginBottom: 24
|
||||
},
|
||||
});
|
||||
|
||||
export const Slide13: ReactFCC<Slide13Props> = (props) => {
|
||||
const { data } = props;
|
||||
|
||||
const links = data.find((i) => i.slug === 'links')?.answer as any;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Контакты</Text>
|
||||
<View style={styles.divider} />
|
||||
|
||||
{Object.entries(links).map(([name, link], index) => (
|
||||
<Fragment key={index}>
|
||||
<Text style={styles.subtitle}>{`${name}: ${link}`}</Text>
|
||||
{/*<Text style={styles.text}>{text as string}</Text>*/}
|
||||
</Fragment>
|
||||
))}
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
53
src/pages/deck/document/slides/Slide2.tsx
Normal file
53
src/pages/deck/document/slides/Slide2.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
|
||||
export interface Slide2Props {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
text: {
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
},
|
||||
});
|
||||
|
||||
export const Slide2: ReactFCC<Slide2Props> = (props) => {
|
||||
const { data } = props;
|
||||
|
||||
const problem = data.find((i) => i.slug === 'problems')?.answer as string;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Проблема</Text>
|
||||
<View style={styles.divider} />
|
||||
<Text style={styles.text}>{problem}</Text>
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
53
src/pages/deck/document/slides/Slide3.tsx
Normal file
53
src/pages/deck/document/slides/Slide3.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
|
||||
export interface SlideProps {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
text: {
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
},
|
||||
});
|
||||
|
||||
export const Slide3: ReactFCC<SlideProps> = (props) => {
|
||||
const { data } = props;
|
||||
|
||||
const actuality = data.find((i) => i.slug === 'actuality')?.answer as string;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Описание и ценностное предложение стартапа</Text>
|
||||
<View style={styles.divider} />
|
||||
<Text style={styles.text}>{actuality}</Text>
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
56
src/pages/deck/document/slides/Slide4.tsx
Normal file
56
src/pages/deck/document/slides/Slide4.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
|
||||
export interface SlideProps {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
text: {
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
marginBottom: 24
|
||||
},
|
||||
});
|
||||
|
||||
export const Slide4: ReactFCC<SlideProps> = (props) => {
|
||||
const { data } = props;
|
||||
|
||||
const solve = data.find((i) => i.slug === 'solve')?.answer as string;
|
||||
const works = data.find((i) => i.slug === 'works')?.answer as string;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Решение</Text>
|
||||
<View style={styles.divider} />
|
||||
<Text style={styles.text}>{solve}</Text>
|
||||
<Text style={styles.text}>{works}</Text>
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
62
src/pages/deck/document/slides/Slide5.tsx
Normal file
62
src/pages/deck/document/slides/Slide5.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
|
||||
export interface Slide5Props {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
marketChart: string;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
text: {
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
marginBottom: 24
|
||||
},
|
||||
image: {
|
||||
maxWidth: '600px',
|
||||
maxHeight: '300px',
|
||||
margin: '0 auto'
|
||||
}
|
||||
});
|
||||
|
||||
export const Slide5: ReactFCC<Slide5Props> = (props) => {
|
||||
const { data, marketChart } = props;
|
||||
|
||||
const market_values = data.find((i) => i.slug === 'market_values')?.answer as any;
|
||||
const users = data.find((i) => i.slug === 'users')?.answer as string;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Рынок</Text>
|
||||
<View style={styles.divider} />
|
||||
<Text style={styles.text}>Целевая аудитория: {users}</Text>
|
||||
<Image style={styles.image} src={marketChart} />
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
71
src/pages/deck/document/slides/Slide6.tsx
Normal file
71
src/pages/deck/document/slides/Slide6.tsx
Normal file
|
@ -0,0 +1,71 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
|
||||
export interface Slide6Props {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
text: {
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
marginBottom: 24
|
||||
},
|
||||
subtitle: {
|
||||
width: '100%',
|
||||
fontSize: '24px',
|
||||
},
|
||||
});
|
||||
|
||||
export const Slide6: ReactFCC<Slide6Props> = (props) => {
|
||||
const { data } = props;
|
||||
|
||||
const competitors = data.find((i) => i.slug === 'competitors')?.answer as string;
|
||||
const competitors_strength = data.find((i) => i.slug === 'competitors_strength')?.answer as string;
|
||||
const competitors_low = data.find((i) => i.slug === 'competitors_low')?.answer as string;
|
||||
const advantages = data.find((i) => i.slug === 'advantages')?.answer as string;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Конкуренты</Text>
|
||||
<View style={styles.divider} />
|
||||
|
||||
<Text style={styles.text}>{competitors}</Text>
|
||||
|
||||
<Text style={styles.subtitle}>Сильные стороны</Text>
|
||||
<Text style={styles.text}>{competitors_strength}</Text>
|
||||
|
||||
<Text style={styles.subtitle}>Слабые стороны</Text>
|
||||
<Text style={styles.text}>{competitors_low}</Text>
|
||||
|
||||
<Text style={styles.subtitle}>Наши преимущества</Text>
|
||||
<Text style={styles.text}>{advantages}</Text>
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
59
src/pages/deck/document/slides/Slide7.tsx
Normal file
59
src/pages/deck/document/slides/Slide7.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
|
||||
export interface Slide7Props {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
text: {
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
marginBottom: 24
|
||||
},
|
||||
subtitle: {
|
||||
width: '100%',
|
||||
fontSize: '24px',
|
||||
},
|
||||
});
|
||||
|
||||
export const Slide7: ReactFCC<Slide7Props> = (props) => {
|
||||
const { data } = props;
|
||||
|
||||
const money = data.find((i) => i.slug === 'money')?.answer as string;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Бизнес-модель</Text>
|
||||
<View style={styles.divider} />
|
||||
|
||||
<Text style={styles.text}>{money}</Text>
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
63
src/pages/deck/document/slides/Slide8.tsx
Normal file
63
src/pages/deck/document/slides/Slide8.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
|
||||
export interface Slide8Props {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
financialChart: string;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
text: {
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
marginBottom: 24
|
||||
},
|
||||
image: {
|
||||
maxWidth: '600px',
|
||||
maxHeight: '300px',
|
||||
margin: '0 auto'
|
||||
}
|
||||
});
|
||||
|
||||
export const Slide8: ReactFCC<Slide8Props> = (props) => {
|
||||
const { data, financialChart } = props;
|
||||
|
||||
const how_much_investments = data.find((i) => i.slug === 'how_much_investments')?.answer.sum as any;
|
||||
const financial_indicators = data.find((i) => i.slug === 'financial_indicators')?.answer as any;
|
||||
const users_metrics = data.find((i) => i.slug === 'users_metrics')?.answer as any;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Финансы</Text>
|
||||
<View style={styles.divider} />
|
||||
<Text style={styles.text}>{financial_indicators}</Text>
|
||||
<Image style={styles.image} src={financialChart} />
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
55
src/pages/deck/document/slides/Slide9.tsx
Normal file
55
src/pages/deck/document/slides/Slide9.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import {ReactFCC} from '../../../../utils/ReactFCC';
|
||||
import {Image, Page, StyleSheet, Text, View} from '@react-pdf/renderer';
|
||||
import {bgColor, primaryColor} from '../shared';
|
||||
import {GetDeckResponse} from '../../../../api/deck/getDeck';
|
||||
import {ExtractArray} from '../../../../utils/types';
|
||||
|
||||
export interface Slide9Props {
|
||||
data: ExtractArray<GetDeckResponse['slides']>['data'];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
backgroundColor: bgColor,
|
||||
color: primaryColor,
|
||||
fontFamily: 'Roboto',
|
||||
padding: '48px',
|
||||
},
|
||||
title: {
|
||||
fontSize: 36,
|
||||
width: '100%',
|
||||
letterSpacing: 3,
|
||||
textTransform: 'uppercase'
|
||||
},
|
||||
divider: {
|
||||
width: '100%',
|
||||
height: 1,
|
||||
backgroundColor: primaryColor,
|
||||
margin: '8px 0 16px'
|
||||
},
|
||||
text: {
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
marginBottom: 24
|
||||
},
|
||||
});
|
||||
|
||||
export const Slide9: ReactFCC<Slide9Props> = (props) => {
|
||||
const { data } = props;
|
||||
|
||||
// const how_much_investments = data.find((i) => i.slug === 'how_much_investments')?.answer as any;
|
||||
// const users_metrics = data.find((i) => i.slug === 'users_metrics')?.answer as any;
|
||||
|
||||
return (
|
||||
<Page size="A4" orientation={'landscape'} style={styles.page}>
|
||||
<View>
|
||||
<Text style={styles.title}>Команда</Text>
|
||||
{/*<View style={styles.divider} />*/}
|
||||
{/*<Text style={styles.text}>{how_much_investments}</Text>*/}
|
||||
</View>
|
||||
</Page>
|
||||
)
|
||||
}
|
|
@ -9,6 +9,8 @@ export type IntrinsicPropsWithoutRef<E extends keyof JSX.IntrinsicElements> = Re
|
|||
JSX.IntrinsicElements[E]
|
||||
>;
|
||||
|
||||
export type ExtractArray<T> = T extends (infer U)[] ? U : T;
|
||||
|
||||
export type AnchorPropsWithoutRef = IntrinsicPropsWithoutRef<'a'>;
|
||||
export type DivPropsWithoutRef = IntrinsicPropsWithoutRef<'div'>;
|
||||
export type PPropsWithoutRef = IntrinsicPropsWithoutRef<'p'>;
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -1606,6 +1606,11 @@
|
|||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@kurkle/color@^0.3.0":
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f"
|
||||
integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==
|
||||
|
||||
"@leichtgewicht/ip-codec@^2.0.1":
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
|
||||
|
@ -3380,6 +3385,13 @@ char-regex@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e"
|
||||
integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==
|
||||
|
||||
chart.js@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.0.tgz#df843fdd9ec6bd88d7f07e2b95348d221bd2698c"
|
||||
integrity sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==
|
||||
dependencies:
|
||||
"@kurkle/color" "^0.3.0"
|
||||
|
||||
check-types@^11.1.1:
|
||||
version "11.2.2"
|
||||
resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.2.tgz#7afc0b6a860d686885062f2dba888ba5710335b4"
|
||||
|
|
Loading…
Reference in New Issue
Block a user