Commit cd79e373 by Иван Кубота

upd prices

parent fed7543f
module.exports = {
src: './src',
entry: {js: 'main.jsx', html: 'index.html'},
public: './public',
scss: {
shared: `@import '/main.scss';`
},
build: './build'
};
module.exports = {
src: './src',
entry: {js: 'main.jsx', html: 'index.html'},
public: './public',
scss: {
shared: `@import '/main.scss';`
},
build: './build'
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>МИР — разработка</title>
<link href="https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400&display=swap&subset=cyrillic" rel="stylesheet">
<!--<link href="https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300&display=swap" rel="stylesheet">-->
<style>
.content {
margin:0 0 0 64px;
position: relative;
}
body {
margin: 0 0 64px 0;
padding: 0;
font-family: 'Yanone Kaffeesatz', sans-serif;
font-weight: 300;
font-size: 24px;
line-height: 1.3;
}
.sub {
padding: 0 0 0 32px;
}
.description {
font-size: 20px;
}
.title {
display: inline-block;
vertical-align: top;
margin: 0.5em 0;
padding: 4px 4px 0 0;
position: relative;
}
.title-1 {
font-size: 56px;
line-height: 1;
}
.title-2 {
font-size: 48px;
}
.title-25 {
font-size: 40px;
}
.title-3 {
font-size: 32px;
}
.title-4 {
font-size: 24px;
}
.time {
display: inline;
font-size: 16px;
color: #777;
}
.cost {
display: inline;
padding: 0 16px 0 0;
}
.cost-name, .cost-amount {
padding: 0 4px 0 0;
display: inline;
white-space: nowrap;
}
.cost-amount {
padding-left: 8px;
}
.cost-after {
display: inline-block;
}
.collapsed .sub, .collapsed .description{
display: none;
}
button {
border: 0;
background: transparent;
font-size: 32px;
vertical-align: top;
padding: 0;
margin: 0;
width: 32px;
margin-left: -32px;
font-family: monospace;
}
.option {
padding: 16px 0 0 0;
font-size:32px;
}
.option input {
width: 24px;
height: 24px;
margin: 0 8px 0 -32px;
padding: 0;
}
.clickable {
cursor: pointer
}
.calculation .cost {
display: block;
}
.title.sub-options {
padding: 8px 0 8px 0;
margin-left: -32px;
}
.title-1:before, .title-2:before, .title-3:before {
content: '';
left: -135px;
width: 128px;
height: 8px;
position: absolute;
z-index: -1;
top: 35px;
}
.title-1, .title-2, .title-3 {
background-repeat: no-repeat;
background-size: 100% 56%;
background-position: 35% 55%;
}
.title-2 {
color: #104361;
/*background-image: linear-gradient(14deg, #184f6b 0%, #092839 100%);*/
/*text-shadow: 0px 1px 3px #092839;*/
}
.title-3 {
color: #870036;
/*
background-image: linear-gradient(179deg, #76002c 0%, #f5a94b 100%);
background-size: 100% 40%;
text-shadow: 0px 1px 3px #76002c;*/
}
.title-1 {
color: #334808;
/*
background-image: linear-gradient(179deg, #666f3b 0%, #182103 100%);
background-size: 100% 40%;
background-position: 35% 68%;
text-shadow: 0px 1px 3px #182103;*/
}
.options {
margin: 64px 0 0 0;
margin-left: -3px;
}
.pre-options {
margin-left: -32px;
}
.description {
white-space: pre-line;
max-width: 640px;
}
.comment {
color: #559405;
font-style: italic;
padding: 16px 0 8px 0;
}
.list {
margin: 4px 0 16px 0;
padding-inline-start: 13px;
/*list-style-image: linear-gradient(14deg, #997ab5 0%, #75848a 100%);*/
}
ul {
list-style: square; /* Remove default bullets */
}
.nobr {
white-space: nowrap;
display: inline-block;
}
</style>
</head>
<body>
<div style="height:64px; background: linear-gradient(14deg, #997ab5 0%, #75848a 100%); margin-bottom: 32px;">
<div class="title-2" style="padding: 12px 64px;color: #fff;">Калькулятор разработки проекта</div>
</div>
<div class="content">
<div class="block">
<div id="works"></div>
<script>
if(!('isArray' in Array)){
Array.isArray = function(el) {
return Object.prototype.toString.call(el).toLowerCase().indexOf('array')>-1;
};
}
if(!('keys' in Object)){
Object.keys = function(obj) {
var ks = [];
for(var k in obj)
ks.push(k);
return ks;
};
Array.prototype.map = function(fn) {
var ks = [];
for( var i = 0, _i = this.length; i < _i; i++ ){
var x = this[ i ];
ks.push(fn(x, i));
}
return ks;
};
Array.prototype.filter = function(fn) {
var ks = [];
for( var i = 0, _i = this.length; i < _i; i++ ){
var x = this[ i ];
fn(x, i) && ks.push(x);
}
return ks;
};
}
var D = function(type, cfg, children){
var el = document.createElement(type);
cfg.cls && (el.className = cfg.cls);
if(cfg.on){
for(var i in cfg.on){
(el.addEventListener||el.attachEvent).call(el, i.toLowerCase(), cfg.on[i]);
}
}
if(cfg.attr){
for(var i in cfg.attr){
if(cfg.attr[i]){
el.setAttribute( i, cfg.attr[ i ] );
}
}
}
if(cfg.prop){
for(var i in cfg.prop){
if(cfg.attr[i]){
el[ i ] = cfg.attr[ i ];
}
}
}
D.appendChild(el, children);
return el;
};
D.appendChild = function(el, children) {
!Array.isArray(children) && (children = [children]);
for( var i = 0, _i = children.length; i < _i; i++ ){
var child = children[ i ];
if(child === void 0 || child === null)
continue;
if(typeof child === 'string' || typeof child === 'number'){
el.appendChild(document.createTextNode(child+''));
}else if(Array.isArray(child)){
D.appendChild(el, child);
}else{
el.appendChild(child);
}
}
};
var Title = function(c, ch) {
if(typeof c === 'string'){
return D('div', {cls: 'title-wrap'}, D( 'div', { cls: 'title ' + c }, ch ));
}else{
c.cls +=' title'
return D('div', {cls: 'title-wrap'},D( 'div', c, ch ));
}
};
var numberFormat = function(num) {
var strNum = num+'';
var i,
out = [],
_i = strNum.length,
count = (_i%3)||3,
last = count;
out.push(strNum.substr(0, count));
count = 3;
for(i = last; i < _i; i+=3){
out.push(strNum.substr(i, count))
}
return out.join(',');
};
(function(w) {
var L = function() {
},
l = L.prototype = {
_plural: function(n) {
return (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)
},
plural: function(num) {
return arguments[l._plural(num)+1];
}
};
w.Localizer = w.L = new L;
})(window);
var div = function(cls, data) {
return D('div', {cls: cls||''}, data);
};
var comment = function(data) {
return D('div', {cls: 'comment'}, data);
};
var list = function(title, items) {
var mapped = [];
for( var i = 0, _i = items.length; i < _i; i++ ){
var item = items[ i ];
mapped.push(D('li', {cls: 'list-item'}, item));
}
return [D('div', {cls: 'list-title'},title), D('ul', {cls: 'list'}, mapped)];
};
var jobs = {
front: {name: 'Фронтэнд', price: 20},
layout: {name: 'Вёрстка', price: 15},
integration: {name: 'Интеграция', price: 15},
model: {name: 'Архитектура', price: 30},
admin: {name: 'Администрирование', price: 25},
back: {name: 'Бэкэнд', price: 25}
};
var work = [
{
title: 'Разработка ОТТ платформы МИР',
description: [
list('Для разработки OTT платформы требуется реализовать базовые части', [
'Пользователи, их регистрация, авторизация, переход в мобильное приложение',
'smartTV и веб или мобильные приложения',
'Локализация системы',
'Вёрстка дизайна',
'Проработка логики компонентов, групп компонентов и блоков',
'Воспроизведение видео',
'Редактирование интерактивного содержания'
]),
],
items: [
{
title: 'Подготовка инфраструктуры для разработки',
description: [
'Работы по настройке серверов должны быть выполнены специалистами знакомыми с практиками CI\CD (непрерывной интеграции и развёртывания).',
list('Работы включают в себя следующие обязательные шаги', [
'развёртывание инфраструктуры для непрерывной интеграции. Jenkins',
'настройка инфраструктуры для запуска автоматических интеграционных тестов',
'создание упрощённой версии инфраструктуры для работы ручных тестировщиков',
'развёртывание и настройка трекера задач. Jira или YouTrack',
'подготовка репозитория с кодом',
'установа вики для хранения всей информации о разработанных частях системы (ввод новых разработчиков и ускорение интеграции частей системы между собой)'
])
],
admin: 24
},
{
title: 'Единая точка обработки входящих запросов',
description: 'Все запросы пользователей поступают в единую точку - API для последующей обработки конкретными модулями. Реализация базового backend модуля',
admin: 8,
model: 4,
back: 8,
items: [
{
title: 'Модульная система',
description: 'Модуль должен автоматически регистрироваться в API системе и сообщать ей своё название, список реализуемых, методов и их параменты',
model:2,
back: 8,
integration: 8,
},
{
title: 'Демонстрационный модуль',
description: 'Модуль являющийся образцом для разработки других модулей. Является живой документацией. Должен реализовывать два метода доступных для вызова: ассинхронный отложенный и синхронный.',
model: 4,
back: 4
}
],
tag: 'api'
},
{
title: 'Микросервисная архитектура',
admin: 32,
back: 24,
integration: 8,
description: 'Каждая система представлена отдельным независимым сервисом использующим шину данных для общения с другими сервисами.',
items: [
{
title: 'Базовый микросервис',
description: 'Разработка демонстрационного модуля реализующего сообщения информации о себе системе через шину данных. Модуль должен реализовывать два метода: один выполняющий действие - запрос на заданный URL, второй - выполняющий запрос и возвращающий результат. Данный модуль нужен в качестве образца для написания других модулей. Любой метод можно рассматривать в качестве узла Remote Procedure Call',
back: 8,
integration: 8,
},
{
title: 'Микросервис для регистрации микросервисов',
description: 'Разработка микросервиса отвечающего за регистрацию других микросервисов и их методов в системе. Произвести реализацию на основе базового микросервиса',
back: 16,
integration: 4,
model: 8
}
],
tag: 'microservice'
},
{
title: 'Контентная часть',
description: 'Социальная сеть и хранение, отображение, изменение всех загружаемых данных',
items: [
{
tag: 'noContent||simple',
title: 'Отображение только предвариательно заведённых нами проектов и контента',
items: [
{
title: 'Модель проекта',
description: 'Проработка структуры для хранения настроек проекта',
model: 40,
back: 8
},
{
title: 'Заведение проектов и их вложенной структуры',
description: 'Ручное наполнение модели проектов структурой согласно мокапам',
integration: 40
},
{
title: 'Перекодирование видео, наполнение разделов',
description: 'Ручное перекодирование видео и его добавление в разделы проектов с форматированным описанием',
integration: 40,
admin: 16,
back: 16,
front: 26
}
]
},
{
tag: 'simple||content||moderator||social||socialGroup',
title: 'Пользователи комментируют и оценивают контент',
items: [
{
title: 'Комментарии',
description: [
list('Модуль отвечает за все операции связанные с комментариями. Абстракция комментариев необходима для',
['отображения комментариев к контенту',
'общения пользователей через внутреннюю почту (личные сообщения)',
'отображения комментариев на личной странице пользователя',
'отображения уведомлений в произвольных разделах',
'связи с техподдержкой'])
],
items: [
{
title: 'Разработка модели комментария',
description: 'Разработка и реализация модели комментариев позволяющей добавлять комментарий к любой SystemID сущности. Комментарий должен иметь возможность добавляться в виде ответа на другой комментарий и тем самым представлять собой древовидную структуру. Комментарий должен содержать в себе как минимум: текст, автора, дату, ссылку на родительский объект.',
back: 8, integration:8, front: 24, layout: 4
},
{
title: 'Количество непрочитанных комментариев',
description: 'Пользователь должен иметь возможность узнать количество новых комментариев в некоторой сущности, а при заходе в просмотр комментариев - иметь возможность визуально отличить уже просмотренные комментарии от новых',
back: 16, layout: 4, front: 8, model: 16, integration: 8
}
]
},
{
title: 'Оценки',
description: 'Пользователи могут оценивать контент по различным критериям',
items: [
{
title: 'Модель оценки',
description: 'Определить требуемую модель для хранения оценок и реализовать методы добавления и изменения оценки.',
back: 16,
model: 8,
integration: 3
},
{
title: 'Типы оценок',
description: 'Задать возможные типы оценок и реализовать соответствующие формы с пользовательским интерфейсом',
back: 4,
front: 8,
layout: 8,
integration: 3
}
]
}
]
},
{
tag: 'content||moderator||social||socialGroup',
title: 'Пользователи создают контент и доабвляют его',
description: 'Любой пользователь может загрузить в проект свой видеоролик или текстовую статью',
items: [
{
title: 'Видео',
items: [
{title: 'Загрузка видеофайла из браузера пользователя', front: 16, back: 24, model: 4, integration: 16, layout: 2},
{title: 'Перекодирование видоефайлов', description: 'После загрузки'}
]
}//TODO!!!!
]
},
{
tag: 'moderator||social||socialGroup',
title: 'Модераторы',
items: [
{title: 'Назначение пользователя модератором'},
{title: 'Интерфейс модерирования'}
]
},
{
tag: 'moderator||social||socialGroup',
title: 'Модераторы',
items: [
{title: 'Назначение пользователя модератором'},
{title: 'Интерфейс модерирования'}
]
},
{tag: 'social||socialGroup'},
{tag: 'socialGroup'}
]
},
{
title: 'Пользователи',
description: 'Пользователь иметь возможность выполнять весь спектр возможных в системе действий',
items: [
{title: 'Модель пользователя'},
{title: 'Регистрация пользователя'},
{title: 'Авторизация пользователя'},
{title: 'Сканер QR кодов'},
{title: 'Двуфакторное подтверждение действия пользователя',
items: [
{title: 'SMS', tag: 'tuFaSMS||tuFaBoth'},
{title: 'E-mail', tag: 'tuFaEmail||tuFaBoth'},
]},
{
title: 'Слияние пользовательских аккаунтов',
items: [
{title: 'diff алгоритм'}
]
},
{title: 'Профиль пользователя'},
{title: 'Права доступа', tag: 'moderator||social||socialGroup',
items: [
{title: 'Модель прав дрступа'},
{title: 'Группы прав'},
{title: 'Модуль проверки прав'},
]},
]
},
{
title: 'Кроссплатформенность',
description: 'На рынке представлен широкий ассортимент smartTV платформ. Каждый производитель считает своим долгом сделать уникальное решение.',
items: [
{tag: 'WebOS'},
{tag: 'Tizen'},
{tag: 'Chromecast'},
{tag: 'Amazon'},
{tag: 'Browser'}
]
},
{
title: 'Локализация',
},
{title: 'Типовые блоки', description: 'Стандартные блоки из которых будут строиться страницы', items: [
{title: 'Типы обложек разделов', description: 'В макетах существует несколько похожих компонентов обложки раздела, подраздела, списка видеофайлов и топа. Все эти блоки будут собраны на основе обложки раздела и будут доступны для переиспользования.'},
{title: 'Интерактивный слой', description: 'Слой доступный для наложения на поток видео', items: [
{title:'Опрос', description: `Выбор ответа из предложенных вариантов.
Действие доступно как из приложения smartTV с помощью пульта, так и с мобильного телефона.
Настраивается позиция блока, количество и текст вариантов. При включенной опции «Свой вариант» — зритель получает возможность ввести свой текст.`},
{title:'Вопрос', description: `Предлагает зрителю ответить на вопрос. Без вариантов ответов. Как и опрос, можно реализовать набор фразы из кусочков.
Настраивается максимальная и минимальная длина ответа.`},
{title:'Загадка', description: `Ввод букв в заранее спозиционированные клетки. Позволяет организовывать викторины, онлайн-кроссворды, судоку. Ввод доступен как с мобильного, там и со smartTV приложений. Настраивается стиль клеток, их количество, расположение относительно друг друга и доступные для ввода символы, цвета или даже звуки (конкурс с подбором мелодии!)`},
{title:'Переход', description: `Реализовать элемент навигации, работающий как гиперссылка. Элемент должен выполнять переход в назначенное место. В том числе, позволять перейти к другому времени в текущем видео.`},
{title:'Разветвление сюжета', description: 'Элемент навигации аналогичный переходу, но с остановкой подачи основного контента. Должен реализовывать настройку фона во время ожидания реакции пользователя.'},
{title:'Результат опроса', description: 'Не интерактивный элемент. Должен отображать данные существующего опроса.', items: [
{title: 'Столбчатый график'},
{title: 'Pie chart'}
]},
{title:'Виджет', description: 'Не интерактивный элемент позволяющий отобразить информационный компонент',
items: [
{title:'Прогноз погоды', description: 'Задаётся длительность прогноза до 14 дней. Для реализации требуется интеграция с погодным сервисом'},
{title:'Текущую погоду', description: 'Должен настраиваться город для отображения погодных данных. По-умолчанию должна быть установлена текущая гео-позиция зрителя'},
{title:'Курс валют', description: 'Задаётся пара валют. Для реализации требуется интеграция с одной из бирж'},
{title:'Изображение'},
{title:'Текстовый блок', description: 'Блок можно использовать как для встраиваемой рекламы, так и для реализации субтитров. Блок должен позволять настроить шрифт, размер и цвет текста'},
]},
]}
]},
{
title: 'UI-система',
description: 'Все страницы будут строиться из заранее подготовленных качественных компонентов выполненных в полном соответствии с макетов',
items: [
{title: 'Текстовое поле', description: 'Текстовое поле с синхронизацией и возможностью одновременного ввода текста со smartTV и мобильного устройства'},
{title: 'Slider', description: 'Компонент позволяющий выбрать число из диапазона. Ввод значения должен осуществляться как из smartTV платформы, так и с мобильного устройства'},
{title: 'Обложка раздела', description: 'Компонент показывающий список других компонентов. Позволяет задавать фон, заголовок, тизерный элемент и список дочерних компонентов'},
{title: 'Button', description: 'Кнопка с возможностью нажатия со smartTV и мобильного устройства. Должна иметь возможность принимать недоступное для нажатия состояние. Должна содержать свойства: текст, иконка, стиль'},
{title: 'Древовидная горизонтальная последовательность', description: 'Компонент отображает список других компонентов с заданными характеристиками позиций и связей. Используется для отображения череды серий'},
{title: 'Список', description: 'Список элементов с возможностью добавления и удаления элементов'},
{title: 'Видеоплеер', description: 'Адаптированный под наш дизайн видеоплеер с интегрированной возможностью отображения интерактивного слоя'},
{title: 'Страницы', description: 'Сборка страниц из компонентов',
items: [
{title: 'Главная страница', description: 'Страница со списком проектов доступных пользователю платформы'},
{title: 'Страница авторизации и регистрации', description: 'Адаптивная страница регистрации и авторизации пользователя'},
{title: 'Страница раздела', description: 'Страница внутренностей раздела подготовленная для отображения на smartTV, mobile и web browser устройствах.'},
{title: 'Редактор интерактива', description: 'Редактор видео позволяющий добавлять интерактивные слои, назначать эффект появления и исчезновения слоёв, определять время и настройки слоёв. Осуществлять предпросмотр результата наложения интерактива.'},
{title: 'Страница видеоролика', description: 'Страница отображающая видеоролик, список серий, информацию о видео-ролике'},
{title: 'Страница добавления видео', description: 'Страница содержит компонент загрузки файла и предоставляет интерфейс для задания свойств видеофайла', tag: '(micro||tiny||small||small2||normal||big)&&(content||moderator||social||socialGroup)'},
{title: 'Страница настройки интерактива видео', description: 'Страница с редактором интерактивных слоёв на загруженном видеофайле', tag: '(micro||tiny||small||small2||normal||big)&&(content||moderator||social||socialGroup)'},
{title: 'Страница отображающая последовательность челленджа'}
]},
],
tag: 'components'
},
{
title: 'Вёрстка макетов',
description: 'Все страницы будут свёрстаны в соответствии с макетами',
items: [
],
tag: 'asIs'
}
],
kk: [
list('Вёрстка', [
'Разбиение страниц на типовые блоки. В макетах много похожих блоков, но если присмотреться, то они имеют специфику и модификации. Каждое такое отличие нужно заметить, учесть и встроить в общую систему.',
'Разработка системы сеток. В макетах много страниц, но нет универсальной модульной сетки. Такой подход к компоновке интерфейса требует дополнительного времени.',
'Сборка страниц из блоков с использованием сетки'
]),
comment('После того как компоненты будут свёрстаны — приходит время добавить интерактив'),
list('Оживление интерфейса', [
'Фотогалереи с автоматическим и ручным перелистыванием',
'Слайдеры с пагинацией и без',
'Интерактивные подсказки при наведении на регионы карты',
'Формы обратной связи',
'Части админки подразумевающие взаимодействие с контентом на странице'
]),
comment('Для отображения страниц где-то должно храниться их содержимое'),
list('Разработка и проектирование серверной части', [
'Проектирование базы данных с учётом предметной области',
'Декомпозиция каждого раздела в соответствующие модели и методы API',
'Возможность добавления, удаления и редактирования каждой записи в модели данных',
'Блог. Статьи, их список, получение в отсортированном и сгруппированном виде'
]),
comment('Админка. Добавление стандартных страниц, редактирование и добавление элементов на страницах'),
list('Средства администрирования', [
'Разработка средств управления контентом',
'Разработка и наполнение структуры для хранения составных частей страницы',
'Внутристраничные инструменты редактирования',
'Авторизация и права администраторов',
'Добавление партнёров',
'Добавление страницы блога',
'Добавление страницы направления',
'Добавление страницы жанра',
'Добавление страницы гида',
'Добавление страницы кейса наука',
'Добавление страницы кейса искусство'
]),
comment('В процессе разработки всех перечисленных выше частей образуются отдельно стоящие модули, которые требуется связать друг с другом в единое целое ')
],
items2: [
{
title: 'Компоненты',
items: [
{
title: 'Основное меню',
layout: 4,
front: 2,
integration: 2,
model: 1,
admin: 2,
back: 1,
description: 'Основное меню с пунктами и подпунктами. Анимация открытия, переход между страницами и возможность добавлять типовые разделы'
},
{ title: 'Автослайдер с фотографиями большой', layout: 2, front: 4, description: 'Слайдер с фотографиями который адаптируется к размерам доступной области и сам себя прокручивает через заданный интервал' },
{
title: 'Блоки с текстом',
layout: 16,
front: 4,
integration: 2,
model: 2,
admin: 8,
description: 'В макетах представлено множество текстовых блоков в различной стилистике. Текстовые блоки являются основным строительным материалом для всего сайта'
},
{
title: 'Блоки с картинками',
layout: 4,
front: 1,
integration: 2,
model: 1,
admin: 4,
description: 'Блоки с изображениями различных размеров. Во время разработки мы учтём необходимость изменять размер и обрезать изображение и правильно сожмём картинки для быстрой загрузки сайта'
},
{
title: 'Карта с выделяемым регионом',
layout: 2,
front: 2,
description: 'Компонент отображающий одну из частей Кавказа (Северный, Южный и общий). Поддерживает выделение области',
times: 3,
admin: 3,
model: 1,
integration: 2
},
{title: 'Прилипающий компонент карты', layout: 1, description: 'Компонент который остаётся на месте во время скролла страницы. Уезжает и появляется после достижения определённой отметки',front: 2},
{title: 'Карточка тура', layout: 3, description: 'Карточка-предпросмотр информации о туре'},
{title: 'Слайдер с карточками туров', layout: 3, front: 4, description: 'Слайдер с возможностью листания как с помощью кнопок, так и с помощью мобильных тач-событий'},
{title: 'Карточка особого тура', layout: 1, description: 'Адаптивная вёрстка карточки особого тура. Позиционирование нескольких карточек такого типа'},
{title: 'Слайдер с отзывами', layout: 2, front: 2, description: 'Слайдер с отзывами скопированными из Google'},
{title: 'Слайдер с навигацией', layout: 3, front: 4, description: 'Слайдер с отображением количества слайдов и навигацией по ним. Поддерживает добавление текста и номера слайда к картинке'},
{title: 'Форма обратной связи', layout: 1, integration: 1, description: 'Форма обратной связи и её интеграция с сервером отправки почтовых сообщений'},
{title: 'Карточка из блога', layout: 2, integration: 1, model: 1, description: 'Вёрстка карточки предпросмотра статьи из блога. Взаимодействие со списком новых статей'},
{title: 'Сотрудничающие компании', layout: 2, integration: 1, admin: 2, model: 1, description: 'Отображение списка компаний. Загрузка нового изображения и описания такой компании'},
{title: 'Подвал (низ) сайта', layout: 3, admin: 1, model: 1, integration: 1, description: 'Блок присутствующий на каждой странице. Закладывается возможность добавлять ссылки на произвольные страницы' },
]
},
{
title: 'Страницы',
items: [
{title: 'Статья в блоге', layout: 8, integration: 6, admin: 6, model: 3, back: 2, description: 'Типовая страница статьи в блоге. Добавление статьи в список статей блога и в качестве отдельной страницы'},
{title: 'Страница гида', layout: 4, integration: 2, admin: 3, model: 2, description: 'Страница гида. Отображение таких страниц в списках гидов. Переход на страницу гида по прямому адресу'},
{title: 'О кавказе', layout: 4, description: 'Создание и наполнение страницы «О кавказе». Добавление страницы в навигацию'},
{title: 'Сотрудничество', layout: 4, integration: 1, description: 'Создание и наполнение страницы «Сотрудничество». Отображение обновляемого списка компаний вступивших в сотрудничество'},
{title: 'Индивидуальные туры', layout: 2, integration: 2, description: 'Отображение ссылок на страницы блога с тэгом #Портфолио'},
{title: 'Корпоративные туры', layout: 2, integration: 1, description: 'Статья и список готовых туров которыми можно вдохновиться'},
{title: 'Туры 2020-2021', layout: 4, integration: 2, description: 'Список туров запланированных на 2020-2021 годы'},
{title: 'Страница тура', layout: 4, integration: 2, description: 'Типовая страница тура'},
{title: 'Медиа и искусство', layout: 4,
integration: 2, description: 'Страница с общей информацией и списком проектов Медиа и искусство',
items: [
{title: 'Типовая страница кейса медиа и искусство', layout: 4, admin: 2, model: 2, back: 1}
]},
{title: 'Научные исследования', layout: 4,
integration: 2, description: 'Страница с общей информацией и списком исследования',
items: [
{title: 'Типовая страница кейса научные исследования', layout: 4, admin: 2, model: 2, back: 1}
]},
{title: 'Развитие территорий', layout: 4, description: 'Страница со списком проектов связанных с развитием территорий'},
{title: 'Направления', layout: 4, integration: 2, admin: 4, model: 2, back: 2,
description: 'Типовая страница и наполнение 16 направлений на основе типовой',
items: [
{title: 'Типовая страница направления', description: 'Страница направления с возможностью добавлять предусмотренные блоки', model: 2, admin: 2, back: 3},
{title: 'Направление на основе типовой страницы', times: 16, description: 'Наполнение страниц направлений. Работа рутинная, но проделать её надо 16 раз', layout: 1},
]},
{title: 'Жанры', layout: 4, integration: 2, admin: 4, model: 2, back: 2,
items: [
{title: 'Типовая страница жанра', description: 'Страница жанра с возможностью добавлять предусмотренные блоки', model: 2, admin: 2, back: 3},
{title: 'Жанр на основе типовой страницы', times: 6, description: 'Наполнение страниц жанров', layout: 2},
]},
{title: 'О нас', layout: 4, integration: 2, admin: 4, model: 2, back: 2, description: 'Вёрстка страницы и её администрирование'},
{title: 'Блог', layout: 4, integration: 2, admin: 4, model: 2, back: 2, description: 'Главная страница блога с отображением списка постом и навигацией по ним'},
{
title: 'Страница поиска',
description: 'Страница с отображением ссылок на все разделы сайта. Для функционирования требует наполнения базы текстов по которым производится поиск. В качестве поискового движка будет использован FlexSearch.js',
admin: 4,
back: 16,
model: 2,
layout: 4
},
{
title: 'Страница авторизации',
description: 'Скрытая страница для авторизации администраторов',
back: 2,
model: 2,
layout: 1
},
{
title: 'Администрирование',
description: 'Разработка модулей и страниц для управления сайтом',
back: 20,
model: 10,
layout: 10,
front: 10,
admin: 20
},
]
}
]
}
];
var works = document.getElementById('works');
var timeCalculate = function(item, numbers, multiply) {
var costs = {};
for(var n in jobs){
costs[n] = 0;
}
for(var n in jobs){
if(item && n in item)
costs[n] += item[n]*(item.times||1);
}
if(item.items){
for( var i = 0, _i = item.items.length; i < _i; i++ ){
if(!item.items[ i ])
continue;
var subitemCosts = timeCalculate( item.items[ i ], true );
for(var n in jobs){
costs[n]+=subitemCosts[n];
}
}
}
if(numbers)
return costs;
return Object.keys(jobs).map(function(key) {
return {name: jobs[key].name, amount: Math.round(costs[key] * (multiply && multiply[key] ? 1+multiply[key]: 1))}
}).filter(function(cost) {
return cost.amount;
}).map(function(cost) {
return D('div', {cls: 'cost'}, [D('div', {cls: 'cost-name'}, cost.name), D('div', {cls: 'cost-amount'}, cost.amount+' '+L.plural(cost.amount,'час', 'часа', 'часов')), D('div', {cls: 'cost-after'})])
});
};
var toggle = function(e) {
this.childNodes[0].innerText = this.parentNode.parentNode.classList.toggle('collapsed') ? '+':'–';
e.preventDefault();
e.stopPropagation()
};
var builder = function (item) {
if(typeof item === 'string'){
}
};
var build = function(arr, el, depth) {
for( var i = 0, _i = arr.length; i < _i; i++ ){
var item = arr[ i ];
if(!item)
continue;
var sub,
arrEl = D('div', {cls: 'block'}, [
Title({cls: 'clickable title-'+depth, on: {click: toggle}}, [item.items?D('button',{},'–'):null, item.title+(item.times?' x'+item.times:'')]),
D('div', {cls: 'time'}, timeCalculate(item)),
D('div', {cls: 'description'}, builder(item.description))
]);
if(item.items){
sub = D('div', {cls: 'sub'});
build(item.items, sub, depth+1);
arrEl.appendChild(sub);
}
el.appendChild(arrEl)
}
};
build(work, works, 1);
D.appendChild(works, [
Title('title-2', 'Наши подходы'),
Title('title-3', 'Оценка времени'),
Title('title-4', 'Время разработки оценивается с учётом тестирования, отладки, исправления ошибок и менеджмента. Наш опыт позволяет довольно точно прогнозировать сроки.'),
Title('title-3', 'Используемые технологии'),
Title('title-4', list('Обычно мы разрабатываем веб-приложения и у нас соответствующий стек технологий', [
'Node.js — бэкэнд',
'React-vanilla — пользовательский интерфейс и интерактивность',
'SCSS — стили вёрстки',
'NoSQL база данных'
])),
Title('title-3', 'Подходы к разработке'),
Title('title-4', list('', [
'Покрываем код тестами — чтобы при изменении одних частей случайно не сломать другие. На крупных проектах такое происходит постоянно и выливается в многочасовой поиск ошибок. Перестраховываемся',
'Agile — планирование работы недельными спринтами. Позволяет изменить направление реализации проекта в процессе (но план работ и стоимость будут пересчитаны)',
'DRY (Do not Repeat Yourself) приницип не дублирования кода — где возможно переиспользуем код. Исправление ошибки в одном месте — исправляет сразу в разных',
['Пишем техническое задание — стоимость выявления и исправления ошибки растёт на порядки при передвижении по цепочке: ', D('div', {cls: 'nobr'},' «бриф → написание тз → проектирование → разработка → доработка»'),'. Фиксируем список работ в момент подписания договора и вносим изменения только через дополнительные Акты.'],
'Декларативная подход к разработке. Заставляем машину следить за зависимостями данных друг от друга. При традиционном (императивном) подходе программист вынужден держать в голове огромный набор взаимосвязей одних частей приложения от других. Мы стараемся иметь единую точку с данными и автоматическое обновление всего что на неё завязано (как ячейки в экселе)',
])),
comment('Agile и Написание ТЗ противоречат друг-другу. Это два разных варианта работы. В случае Agile мы продаём человеко-часы, в случае ТЗ — результат')
]);
var options = [
{title: 'Поддержка IE 11', layout: 0.5, front: 0.3, admin: 0.2},
{title: 'Мобильная версия', layout: 0.1, front: 0.1, checked: true, admin: 0.2},
{title: 'Адаптив', layout: 0.3, checked: true, integration: 0.1, admin: 0.2},
{title: 'Второй язык', layout: 0.1, front: 0.2, integration: 0.2, model: 0.2, admin: 0.2, checked: true}
];
works.appendChild(
Title('title-1 options', 'Модификаторы времени и стоимости:'));
var calculation, recalculate = function() {
calculation.innerHTML = '';
var addition = {};
for(var i in jobs){
addition[i] = 0;
}
for( var i = 0, _i = options.length; i < _i; i++ ){
var option = options[ i ];
if(option.input.checked){
for(var j in jobs){
addition[j] += option[j]||0;
}
}
}
calculation.appendChild(Title('title-3 sub-options', 'Время работы специалистов:'));
var divs = timeCalculate(work[0], false, addition);
for( var i = 0, _i = divs.length; i < _i; i++ ){
var div = divs[ i ];
calculation.appendChild(div);
}
var costs = timeCalculate(work[0], true), sum = 0;
for(var key in jobs){
costs[key] = Math.round(costs[key] * (addition[key] ? 1+addition[key]: 1));
sum += costs[key]*jobs[key].price
}
var days = (Math.max(costs.front+costs.layout,costs.back+costs.admin+costs.model)+costs.integration+
(costs.front+costs.layout+costs.integration+costs.model+costs.admin+costs.back)/8
)/7,
weeks = days/5+2;
//console.log(days)
calculation.appendChild(Title('title-3 sub-options', 'Время разработки: '+
Math.ceil(weeks)+' '+(L.plural(weeks, 'рабочая неделя', 'рабочие недели', 'рабочих недель'))
));
calculation.appendChild(Title('title-3 sub-options', 'Стоимость работ: $'+numberFormat(sum) +' или '+numberFormat(Math.round(sum*63.57))+' руб' ));
};
for( var i = 0, _i = options.length; i < _i; i++ ){
var option = options[ i ];
works.appendChild(
D('div',{cls: 'option'}, D('label', {cls: 'clickable'}, [option.input = D('input', {on: {change: recalculate, click: recalculate}, attr: {type: 'checkbox', checked: option.checked}, prop: {checked: option.checked}}),option.title])))
}
works.appendChild(Title('title-1 options ', 'Итоговый расчёт'));
works.appendChild(calculation = D('div', {cls: 'calculation sub'}));
recalculate();
</script>
<div></div>
</div>
</div>
</body>
</html>
......@@ -29,7 +29,7 @@ var Checkbox = new Component({
var _self = this;
this.sub([this.value], function (val) {
console.log(val)
// console.log(val)
_self.inputEl.checked = !!val;
});
......
......@@ -24,7 +24,7 @@ var Observable = (function(){
}
},
fire: function( k ){
console.log(k)
//console.log(k)
var listeners = this._listeners[ k ],
listener;
if( listeners === void 0 )
......
......@@ -16,7 +16,7 @@ import { Social } from "posts/Social.jsx";
const {IF, ELSE} = Store;
export default function() {
var krokotau = Store.Value.Boolean(false);
let graph, klaps = true,
let graph, klaps = false,
dom = <div renderTo={document.body} class={[ "page-content", { krokotau } ]}>
{_ => {
setTimeout( function(){
......@@ -50,16 +50,16 @@ export default function() {
</Collapse>
<Collapse label={'Социальная сеть'} value={klaps}>
{/*<Collapse label={'Социальная сеть'} value={klaps}>
<Social/>
</Collapse>
</Collapse>*/}
<Collapse label={'Технологический стэк'} value={false}>
<Collapse label={'Технологический стэк'} value={klaps}>
<Stack/>
</Collapse>
<Collapse label={'План этапов работ'} value={klaps}>
<Collapse label={'План этапов работ'} value={false}>
<Plan/>
</Collapse>
......
......@@ -12,85 +12,924 @@ import "./Plan.scss";
import { Collapse } from "cmp/Collapse/Collapse.jsx";
var Plan = function(){
s.set( {} );
var Plan = (function(h) {
var D = h.h;
D.div = h.div;
D.span = h.span;
D.join = h.join;
D.appendChild = h.appendChild ;
var Plan = function () {
s.set({});
var Title = function (c, ch) {
if (typeof c === 'string') {
return D('div', {cls: 'title-wrap'}, D('div', {cls: 'title ' + c}, ch));
} else {
c.cls += ' title'
return D('div', {cls: 'title-wrap'}, D('div', c, ch));
}
};
var numberFormat = function (num) {
var strNum = num + '';
var i,
out = [],
_i = strNum.length,
count = (_i % 3) || 3,
last = count;
out.push(strNum.substr(0, count));
count = 3;
for (i = last; i < _i; i += 3) {
out.push(strNum.substr(i, count))
}
return out.join(',');
};
(function (w) {
var L = function () {
},
l = L.prototype = {
_plural: function (n) {
return (n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)
},
plural: function (num) {
return arguments[l._plural(num) + 1];
}
};
w.Localizer = w.L = new L;
})(window);
var div = function (cls, data) {
return D('div', {cls: cls || ''}, data);
};
var comment = function (data) {
return D('div', {cls: 'comment'}, data);
};
var list = function (title, items) {
var mapped = [];
for (var i = 0, _i = items.length; i < _i; i++) {
var item = items[i];
mapped.push(D('li', {cls: 'list-item'}, item));
}
return [D('div', {cls: 'list-title'}, title), D('ul', {cls: 'list'}, mapped)];
};
var jobs = {
front: {name: 'Фронтэнд', price: 25},
layout: {name: 'Вёрстка', price: 20},
integration: {name: 'Интеграция', price: 12},
model: {name: 'Архитектура', price: 32},
admin: {name: 'Администрирование', price: 25},
back: {name: 'Бэкэнд', price: 27},
locale: {name: 'Локализация', price: 9},
};
var pages = [
{
title: 'Главная страница',
description: 'Страница со списком проектов доступных пользователю платформы',
front: 24, layout: 16,
integration: 16,
back: 16,
model: 8
},
{
title: 'Страница авторизации и регистрации',
description: 'Адаптивная страница регистрации и авторизации пользователя',
front: 8, layout: 2,
integration: 4,
back: 4,
model: 4
},
{
title: 'Страница раздела',
description: 'Страница внутренней части раздела.',
front: 16, layout: 12,
integration: 16,
back: 16,
model: 4
},
{
title: 'Редактор интерактива',
description: 'Редактор видео позволяющий добавлять интерактивные слои, назначать эффект появления и исчезновения слоёв, определять время и настройки слоёв. Осуществлять предпросмотр результата наложения интерактива.',
front: 8*5*2, layout: 24,
integration: 24,
back: 8,
model: 16
},
{
title: 'Страница видеоролика',
description: 'Страница отображающая видеоролик, список серий, информацию о видео-ролике',
front: 16, layout: 6,
integration: 8,
back: 4,
model: 6
},
var tasks = `
{
title: 'Страница добавления видео',
description: 'Страница содержит компонент загрузки файла и предоставляет интерфейс для задания свойств видеофайла',
tag: '(micro||tiny||small||small2||normal||big)&&(content||moderator||social||socialGroup)',
front: 24, layout: 8,
integration: 16,
back: 32,
model: 4
},
{title: 'Страница отображающая последовательность челленджа', tag: '(micro||tiny||small||small2||normal||big)&&(content||moderator||social||socialGroup)',
front: 16, layout: 16,
integration: 4,
back: 4,
model: 4},
{title: 'Персональная страница в социальной сети', description: 'Личная страница пользователя на которой он может размещать свой контент', tag: '(micro||tiny||small||small2||normal||big)&&(content||moderator||social||socialGroup)',
front: 16, layout: 16,
integration: 8,
back: 4,
model: 16},
{title: 'Страница группы в социальной сети', description: 'Страница группы содержит описание группы, список пользователей добавленных в группу, комментарии группы, список модераторов с правами удалять комментарии и редактировать описание', tag: '(micro||tiny||small||small2||normal||big)&&(content||moderator||social||socialGroup)',
front: 16, layout: 8,
integration: 8,
back: 8,
model: 3},
{title: 'Профиль пользователя', description: 'Информация о пользователе. Если пользователь просматривает свою страницу, то у него есть возможность отредактировать имеющуюся на странице информацию, в том числе загрузить новый аватар', tag: '(micro||tiny||small||small2||normal||big)&&(content||moderator||social||socialGroup)',
front: 8, layout: 4,
integration: 8,
back: 2,
model: 2},
{title: 'Личный кошелёк', description: 'Страница отображает текущий баланс и список из 30 последних транзакций',
front: 4, layout: 2,
integration: 2,
back: 24,
model: 24}
];
var work = [
{
title: 'Разработка ОТТ платформы МИР',
description: [
list('Для разработки OTT платформы требуется реализовать базовые части', [
'Модели данных в которых будет храниться вся информация платформы',
'Backend предоставляющий данные в пользовательские приложения и предоставляющий машинный интерфейс для наполнения и редактирования информации',
'smartTV и мобильные приложения',
'Локализация системы',
"Дизайн макетов",
'Вёрстка дизайна',
'Проработка логики компонентов, групп компонентов и типовых блоков',
'Воспроизведение видео',
'Редактирование интерактивного содержания видео',
'Социальные сервисы'
]),
comment('Дальнейший список задач и предварительное оценочное время их реализации необходимо для долгосрочного планирования проекта.')
],
items: [
{
title: 'Подготовка инфраструктуры для разработки',
description: [
'Работы по настройке серверов должны быть выполнены специалистами знакомыми с практиками CI\CD (непрерывной интеграции и развёртывания).',
list('Работы включают в себя следующие обязательные шаги', [
'развёртывание инфраструктуры для непрерывной интеграции. Jenkins',
'настройка инфраструктуры для запуска автоматических интеграционных тестов',
'создание упрощённой версии инфраструктуры для работы ручных тестировщиков',
'развёртывание и настройка трекера задач. Jira или YouTrack',
'подготовка репозитория с кодом',
'установа вики для хранения всей информации о разработанных частях системы (ввод новых разработчиков и ускорение интеграции частей системы между собой)'
])
],
admin: 24
},
Разбить макеты на типовые блоки
#вёрстка: 6h * $componentCount
#dependency: design
>После получения финального дизайна выделить все присутствующие в макетах компоненты и оценить время необходимое для их вёрстки
{
title: 'Единая точка обработки входящих запросов',
description: 'Все запросы пользователей поступают в единую точку - API для последующей обработки конкретными модулями. Реализация базового backend модуля',
admin: 8,
model: 4,
back: 8,
items: [
{
title: 'Модульная система',
description: 'Модуль должен автоматически регистрироваться в API системе и сообщать ей своё название, список реализуемых, методов и их параменты',
model: 2,
back: 8,
integration: 8,
},
{
title: 'Демонстрационный модуль',
description: 'Модуль являющийся образцом для разработки других модулей. Является живой документацией. Должен реализовывать два метода доступных для вызова: ассинхронный отложенный и синхронный.',
model: 4,
back: 4
}
],
tag: 'api'
},
{
title: 'Микросервисная архитектура',
admin: 32,
back: 24,
integration: 8,
description: 'Каждая система представлена отдельным независимым сервисом использующим шину данных для общения с другими сервисами.',
items: [
{
title: 'Базовый микросервис',
description: 'Разработка демонстрационного модуля реализующего сообщения информации о себе системе через шину данных. Модуль должен реализовывать два метода: один выполняющий действие - запрос на заданный URL, второй - выполняющий запрос и возвращающий результат. Данный модуль нужен в качестве образца для написания других модулей. Любой метод можно рассматривать в качестве узла Remote Procedure Call',
back: 8,
integration: 8,
},
{
title: 'Микросервис для регистрации микросервисов',
description: 'Разработка микросервиса отвечающего за регистрацию других микросервисов и их методов в системе. Произвести реализацию на основе базового микросервиса',
back: 16,
integration: 4,
model: 8
}
],
tag: 'microservice'
},
{
title: 'Контентная часть',
description: 'Социальная сеть и хранение, отображение, изменение всех загружаемых данных',
items: [
{
tag: 'noContent||simple',
title: 'Отображение только предвариательно заведённых нами проектов и контента',
items: [
{
title: 'Модель проекта',
description: 'Проработка структуры для хранения настроек проекта',
model: 40,
back: 8
},
{
title: 'Заведение проектов и их вложенной структуры',
description: 'Ручное наполнение модели проектов структурой согласно мокапам',
integration: 40
},
{
title: 'Перекодирование видео, наполнение разделов',
description: 'Ручное перекодирование видео и его добавление в разделы проектов с форматированным описанием',
integration: 40,
admin: 16,
back: 16,
front: 26
}
]
},
{
tag: 'simple||content||moderator||social||socialGroup',
title: 'Пользователи комментируют и оценивают контент',
items: [
{
title: 'Комментарии',
description: [
list('Модуль отвечает за все операции связанные с комментариями. Абстракция комментариев необходима для',
['отображения комментариев к контенту',
'общения пользователей через внутреннюю почту (личные сообщения)',
'отображения комментариев на личной странице пользователя',
'отображения уведомлений в произвольных разделах',
'связи с техподдержкой'])
],
items: [
{
title: 'Разработка модели комментария',
description: 'Разработка и реализация модели комментариев позволяющей добавлять комментарий к любой SystemID сущности. Комментарий должен иметь возможность добавляться в виде ответа на другой комментарий и тем самым представлять собой древовидную структуру. Комментарий должен содержать в себе как минимум: текст, автора, дату, ссылку на родительский объект.',
back: 8, integration: 8, front: 24, layout: 4
},
{
title: 'Количество непрочитанных комментариев',
description: 'Пользователь должен иметь возможность узнать количество новых комментариев в некоторой сущности, а при заходе в просмотр комментариев - иметь возможность визуально отличить уже просмотренные комментарии от новых',
back: 16, layout: 4, front: 8, model: 16, integration: 8
}
]
},
{
title: 'Оценки',
description: 'Пользователи могут оценивать контент по различным критериям',
items: [
{
title: 'Модель оценки',
description: 'Определить требуемую модель для хранения оценок и реализовать методы добавления и изменения оценки.',
back: 16,
model: 8,
integration: 3
},
{
title: 'Типы оценок',
description: 'Задать возможные типы оценок и реализовать соответствующие формы с пользовательским интерфейсом',
back: 4,
front: 8,
layout: 8,
integration: 3
}
]
}
]
},
{
tag: 'content||moderator||social||socialGroup',
title: 'Пользователи создают контент и доабвляют его',
description: 'Любой пользователь может загрузить в проект свой видеоролик или текстовую статью',
items: [
{
title: 'Видео',
items: [
{
title: 'Загрузка видеофайла из браузера пользователя',
front: 16,
back: 24,
model: 4,
integration: 16,
layout: 2
},
{title: 'Перекодирование видоефайлов', description: 'После загрузки происходит перекодирование видеофайлов в форматы подходящие для просмотра в стандартном стеке веб технологий',
integration: 16,
admin: 8*5},
{title: 'Хранение видео', description: 'Модель для хранение перекодированных видеофайлов связаная с видеоплеером',
back: 8,
model: 8,
integration: 16,
admin: 8*4}
]
},//TODO!!!!
{
tag: 'content||moderator||social||socialGroup',
title: 'Текстовый контент', description: 'What you see is what you get редактор текста для написания статей и заметок',
front: 24,
back: 8,
model: 8,
integration: 8,
layout: 4
}
]
},
{
tag: 'moderator||social||socialGroup',
title: 'Модераторы',
items: [
{title: 'Назначение пользователя модератором',
front: 4, layout: 2,
integration: 4,
back: 4,
model: 2},
{title: 'Интерфейс модерирования',
front: 16, layout: 4,
integration: 4}
]
},
/* {tag: 'social||socialGroup'}, //TODO!!
{tag: 'socialGroup'}// TODO!!*/
]
},
{
title: 'Пользователи',
description: 'Пользователь должен иметь возможность выполнять весь спектр возможных в системе действий',
items: [
{title: 'Модель пользователя',
back: 4,
model: 16},
{title: 'Регистрация пользователя',
back: 4},
{title: 'Авторизация пользователя', back: 6},
{title: 'Сканер QR кодов', front: 8*5, integration: 8*3},
{
title: 'Двуфакторное подтверждение действия пользователя',
tag: 'tuFaSMS||tuFaBoth||tuFaEmail',
items: [
{title: 'SMS', tag: 'tuFaSMS||tuFaBoth',
description: 'Задача включает интеграцию с СМС шлюзом и логику отправки сообщений с авторизационным кодом',
front: 8, layout: 4,
integration: 8,
back: 16,
model: 2},
{title: 'E-mail', tag: 'tuFaEmail||tuFaBoth',
description: 'Для отправки e-mail будет проведена интеграция с сервисом для отправки email. В дальнейшем эту интеграцию можно будет убрать, но в первое время она позволит обойти спам фильтры.',
front: 8, layout: 4,
integration: 8,
back: 4,
model: 2},
]
},
{
title: 'Слияние пользовательских аккаунтов',
items: [
{title: 'Алгоритм поиска различий', description: 'Система будет предлагать пользователю объединение данных двух аккаунтов в момент слияния',
back: 6,
model: 2},
{title: 'Интерфейс слияния', front: 6, layout: 4,
integration: 4}
]
},
{
title: 'Права доступа', tag: 'moderator||social||socialGroup',
items: [
{title: 'Модель прав дрступа',
model: 8*4},
{title: 'Группы прав',
model: 8*3},
{title: 'Модуль проверки прав', front: 8, layout: 4,
integration: 8*2,
back: 8*4},
{title: 'Пользовательский интерфейс настройки прав доступа', front: 8*2, layout: 8,
integration: 8*2},
]
},
]
},
{
title: 'Кроссплатформенность',
description: 'На рынке представлен широкий ассортимент smartTV платформ. Каждый производитель считает своим долгом сделать уникальное решение.',
items: [
{tag: 'WebOS', title: 'LG', description: 'Поддержка SmartTV телевизоров LG c FullHD разрешением произведённых после 2019 года',
front: 8*5*2,layout: 8*3
},
{tag: 'Tizen', title: 'Samsung', description: 'Поддержка SmartTV телевизоров Samsung c FullHD разрешением произведённых после 2019 года', front: 8*5*2,layout: 8*3},
{tag: 'Chromecast',title: 'Google', description: 'Поддержка телевизоров с приставкой от Google c FullHD разрешением. Android >= 8.0', front: 8*5*2,layout: 8*3},
{tag: 'Amazon', title: 'Amazon', description: 'Поддержка телевизоров с приставкой от Amazon c FullHD разрешением. Android >= 8.0', front: 8*5*2,layout: 8*3},
{tag: 'Browser', title: 'Web', description: `Поддержка отображение контента в браузерах Google Chrome, Firefox и Microsoft Edge с присутствием на рынке >1%.
Вёрстка осуществляется адаптивно, т.е. сайт будет хорошо смотреться как на десктопе, так и на планшете`, front: 8*5*4,layout: 8*6}
]
},
{
title: 'Локализация',
description: 'Тексты локализации в платформе',
items: [
{title: 'Русский язык', tag: 'rus||rusEnSystem||rusSystem', description: 'Платформа имеет встроенную поддержку русского языка',
locale: 4*5*4}, // месяц парттайма
{title: 'Английский язык', tag: 'en||rusEnSystem', description: 'Платформа имеет встроенную поддержку английского языка',
locale: 4*5*4}, // месяц парттайма
{title: 'Утилиты локализатора',
tag: 'rusSystem||system',
description: `Платформа будет включать в себя инструменты для заведения любого количества языковых локалей. Инструменты будут позволять выстроить древовидную модель локализационных констант. Текстовый шаблонизатор позволяющий как делать вставки других констант, так и вставлять переменные. Инструменты должны предоставлять возможность корректной работы с числительными (для примера, в русском языке: "1 фильм, 2 фильма, 5 фильмов)".`,
front: 8, layout: 8,
integration: 8,
back: 8*2,
model: 6},
]
},
{
title: 'Типовые блоки', description: 'Стандартные блоки из которых будут строиться страницы', items: [
{
title: 'Типы обложек разделов',
description: 'В макетах существует несколько похожих компонентов обложки раздела, подраздела, списка видеофайлов и топа. Все эти блоки будут собраны на основе обложки раздела и будут доступны для переиспользования.',
front: 8, layout: 4,
integration: 4,
back: 2,
model: 2
},
{
title: 'Интерактивный слой', description: 'Слой доступный для наложения на поток видео. Любой слой предоставляет возможность назначить время появления, продолжительность присутствия, эффект появления и скрытия.',
front: 16, layout: 4,
integration: 2,
back: 2,
model: 2,
items: [
{
title: 'Опрос', description: `Выбор ответа из предложенных вариантов.
Вёрстка страниц макетов
#id:markup-full
$constructor.design===as-is
#вёрстка: 8h * $pagesCount
#dependency: design
>Вёрстка макетов всех отрисованных страниц
Действие доступно как из приложения smartTV с помощью пульта, так и с мобильного телефона.
Вёрстка страниц макетов
#id:markup-full
$constructor.design===components
#вёрстка: 3h * $pagesCount
#dependency: design, design-grid
>Вёрстка макетов всех отрисованных страниц с использованием заготовленных дизайн-сеток
Настраивается позиция блока, количество и текст вариантов. При включенной опции «Свой вариант» — зритель получает возможность ввести свой текст.`,
front: 16, layout: 6,
integration: 4,
back: 8,
model: 4
},
{
title: 'Вопрос', description: `Предлагает зрителю ответить на вопрос. Без вариантов ответов. Как и опрос, можно реализовать набор фразы из кусочков.
Настраивается максимальная и минимальная длина ответа.`,
front: 8, layout: 4,
integration: 4,
back: 4,
model: 2
},
{
title: 'Загадка',
description: `Ввод букв в заранее спозиционированные клетки. Позволяет организовывать викторины, онлайн-кроссворды, судоку. Ввод доступен как с мобильного, там и со smartTV приложений. Настраивается стиль клеток, их количество, расположение относительно друг друга и доступные для ввода символы, цвета или даже звуки (конкурс с подбором мелодии!)`,
front: 24, layout: 12,
integration: 8,
back: 4,
model: 4
},
{
title: 'Переход',
description: `Реализовать элемент навигации, работающий как гиперссылка. Элемент должен выполнять переход в назначенное место. В том числе, позволять перейти к другому времени в текущем видео.`,
front: 2, layout: 1,
integration: 1,
back: 1,
model: 1
},
{
title: 'Разветвление сюжета',
description: 'Элемент навигации аналогичный переходу, но с остановкой подачи основного контента. Должен реализовывать настройку фона во время ожидания реакции пользователя.',
front: 2, layout: 1,
integration: 1,
back: 1,
model: 1
},
{
title: 'Результат опроса',
description: 'Не интерактивный элемент. Должен отображать данные существующего опроса.',
integration: 2,
back: 2,
model: 4,
items: [
{title: 'Столбчатый график',
front: 4, layout: 1,
integration: 1},
{title: 'Pie chart',
front: 4, layout: 1,
integration: 1,
}
]
},
{
title: 'Виджет',
description: 'Не интерактивный элемент позволяющий отобразить информационный компонент',
model: 1,
back: 1,
items: [
{
title: 'Прогноз погоды',
description: 'Задаётся длительность прогноза до 14 дней. Для реализации требуется интеграция с погодным сервисом',
front: 8, layout: 4,
integration: 16,
back: 1,
model: 1
},
{
title: 'Текущую погоду',
description: 'Должен настраиваться город для отображения погодных данных. По-умолчанию должна быть установлена текущая гео-позиция зрителя',
front: 4, layout: 2,
integration: 16,
back: 1,
model: 1
},
{
title: 'Курс валют',
description: 'Задаётся пара валют. Для реализации требуется интеграция с одной из бирж',front: 4, layout: 1,
integration: 8,
back: 2,
model: 1
},
{title: 'Изображение',front: 1, layout: 1,
integration: 1,
back: 1,
model: 1},
{
title: 'Текстовый блок',
description: 'Блок можно использовать как для встраиваемой рекламы, так и для реализации субтитров. Блок должен позволять настроить шрифт, размер и цвет текста',front: 16, layout: 4,
integration: 1,
back: 1,
model: 1
},
]
},
]
}
]
},
{
title: 'UI-система',
description: 'Все страницы будут строиться из заранее подготовленных качественных компонентов выполненных в полном соответствии с макетов',
items: [
{
title: 'Текстовое поле',
description: 'Текстовое поле с синхронизацией и возможностью одновременного ввода текста со smartTV и мобильного устройства',
front: 8,
back: 8,
integration: 16,
model: 8,
admin: 4
},
{
title: 'Slider',
description: 'Компонент позволяющий выбрать число из диапазона. Ввод значения должен осуществляться как из smartTV платформы, так и с мобильного устройства',
front: 8,
back: 8,
integration: 16,
model: 8,
admin: 4
},
{
title: 'Обложка раздела',
description: 'Компонент показывающий список других компонентов. Позволяет задавать фон, заголовок, тизерный элемент и список дочерних компонентов',
front: 16,
layout: 12,
back: 4,
integration: 4,
model: 4
},
{
title: 'Button',
description: 'Кнопка с возможностью нажатия со smartTV и мобильного устройства. Должна иметь возможность принимать недоступное для нажатия состояние. Должна содержать свойства: текст, иконка, стиль',
front: 2,
layout: 1,
back: 2,
integration: 2
},
{
title: 'Древовидная горизонтальная последовательность',
description: 'Компонент отображает список других компонентов с заданными характеристиками позиций и связей. Используется для отображения череды серий',
front: 24,
layout: 16,
back: 8,
integration: 8,
model: 8
},
{title: 'Список', description: 'Список элементов с возможностью добавления и удаления элементов',
front: 8,
layout: 2},
{
title: 'Видеоплеер',
description: 'Адаптированный под наш дизайн видеоплеер с интегрированной возможностью отображения интерактивного слоя',
front: 24,
layout: 2,
back: 8,
integration: 16
},
Построить адаптивную систему сеток для позицонирования компонентов
#id:design-grid
$constructor.design===components
#вёрстка: 4d
#dependency: design
#tag: ui
>Определить и унифицировать гайдлайны для построения всех экранов. Гайдлайны должны представлять собой дизайн-систему позволяющую добавлять компоненты на страницу, сохраняя результат соответствующим дизайну. Сетка должна адекватно отображаться как на телевизоре, так и на мобильном устройстве.
{
title: 'Страницы', description: 'Сборка страниц из компонентов',
items: pages
},
],
tag: 'components'
},
{
title: 'Вёрстка макетов',
description: 'Все страницы будут свёрстаны в соответствии с макетами',
items: pages.map(a=>{
return {...a, front: a.front*1.7, layout: a.layout*1.7};
}),
tag: 'asIs'
}
]
}
];
var works = D.div({prop: {id: 'works'}});
Построить инфраструктуру для перекодирования видео
var timeCalculate = function (item, numbers, multiply, cfg) {
var costs = {};
for (var n in jobs) {
costs[n] = 0;
}
for (var n in jobs) {
if (item && n in item)
costs[n] += item[n] * (item.times || 1);
}
Развернуть очередь сообщений
if (item.items) {
for (var i = 0, _i = item.items.length; i < _i; i++) {
if (!item.items[i])
continue;
if (item.items[i].tag){
if(!checkTag(cfg, item.items[i].tag))
continue ;
}
var subitemCosts = timeCalculate(item.items[i], true, null, cfg);
for (var n in jobs) {
costs[n] += subitemCosts[n];
}
}
}
if (numbers)
return costs;
return Object.keys(jobs).map(function (key) {
return {
name: jobs[key].name,
amount: Math.round(costs[key] * (multiply && multiply[key] ? 1 + multiply[key] : 1))
}
}).filter(function (cost) {
return cost.amount;
}).map(function (cost) {
return D('div', {cls: 'cost'}, [D('div', {cls: 'cost-name'}, cost.name), D('div', {cls: 'cost-amount'}, cost.amount + ' ' + L.plural(cost.amount, 'час', 'часа', 'часов')), D('div', {cls: 'cost-after'})])
});
};
var toggle = function (e) {
this.childNodes[0].innerText = this.parentNode.parentNode.classList.toggle('collapsed') ? '+' : '–';
e.preventDefault();
e.stopPropagation()
};
var builder = function (item) {
if (typeof item === 'string') {
/*var tokens = {}, id = 0, uniq = function(data){
var uuid = id++;
var str = '%%%@@@a'+uuid+'x@@@%%%';
tokens[str] = {type, data};
return str;
};
var list = [];
item.replace(/\*([^\*]+)\*!/g, function(a,b, c){
return uniq('bold', b)
})*/
var rows = item.split('\n');
if(rows.length>1){
return D.join(rows,D('br'));
}else{
return item;
}
}else{
return item;
}
};
// var tag = '(micro||tiny||small||small2||normal||big)&&(content||moderator||social||socialGroup)';
var checkTag = function(cfg, tag){
return (new Function('cfg', 'with(cfg){return '+tag+';}'))(cfg)
}
Пользователи
Сервис отвечающий за регистрацию и авторизацию пользователей #микросервисы
Сервис проверяющий права пользователя X на совершения действия Y над объектом Z #микросервисы
Реализация API регистрации и авторизации пользователей #монолит
Реализация API проверки прав пользователя X на совершения действия Y над объектом Z #монолит
var build = function (arr, el, depth, cfg) {
for (var i = 0, _i = arr.length; i < _i; i++) {
var item = arr[i];
if (!item)
continue;
if(item.tag){
if(!checkTag(cfg, item.tag))
continue ;
}
var sub,
arrEl = D('div', {cls: 'block'}, [
Title({
cls: 'clickable title-' + depth,
on: {click: toggle}
}, [item.items ? D('button', {}, '–') : null, item.title + (item.times ? ' x' + item.times : '')]),
D('div', {cls: 'time'}, timeCalculate(item, null,null,cfg)),
D('div', {cls: 'description'}, builder(item.description))
]);
if (item.items) {
sub = D('div', {cls: 'sub'});
build(item.items, sub, depth + 1, cfg);
arrEl.appendChild(sub);
}
el.appendChild(arrEl)
}
};
var buildHolder = D.div({});
D.appendChild(works, buildHolder);
D.appendChild(works, [
Title('title-2', 'Наши подходы'),
Title('title-3', 'Оценка времени'),
Title('title-4', 'Время разработки оценивается с учётом тестирования, отладки, исправления ошибок и менеджмента. Наш опыт позволяет довольно точно прогнозировать сроки.'),
Title('title-3', 'Используемые технологии'),
Title('title-4', list('Обычно мы разрабатываем веб-приложения и у нас соответствующий стек технологий', [
'Node.js — бэкэнд',
'React-vanilla — пользовательский интерфейс и интерактивность',
'SCSS — стили вёрстки',
'NoSQL база данных'
])),
Title('title-3', 'Подходы к разработке'),
Title('title-4', list('', [
'Покрываем код тестами — чтобы при изменении одних частей случайно не сломать другие. На крупных проектах такое происходит постоянно и выливается в многочасовой поиск ошибок. Перестраховываемся',
'Agile — планирование работы недельными спринтами. Позволяет изменить направление реализации проекта в процессе (но план работ и стоимость будут пересчитаны)',
'DRY (Do not Repeat Yourself) приницип не дублирования кода — где возможно переиспользуем код. Исправление ошибки в одном месте — исправляет сразу в разных',
['Пишем техническое задание — стоимость выявления и исправления ошибки растёт на порядки при передвижении по цепочке: ', D('div', {cls: 'nobr'}, ' «бриф → написание тз → проектирование → разработка → доработка»'), '. Фиксируем список работ в момент подписания договора и вносим изменения только через дополнительные Акты.'],
'Декларативная подход к разработке. Заставляем машину следить за зависимостями данных друг от друга. При традиционном (императивном) подходе программист вынужден держать в голове огромный набор взаимосвязей одних частей приложения от других. Мы стараемся иметь единую точку с данными и автоматическое обновление всего что на неё завязано (как ячейки в экселе)',
])),
comment('Agile и Написание ТЗ противоречат друг-другу. Это два разных варианта работы. В случае Agile мы продаём человеко-часы, в случае ТЗ — результат')
]);
var options = [/*
{title: 'Написа'}
{title: 'Поддержка IE 11', layout: 0.5, front: 0.3, admin: 0.2},
{title: 'Мобильная версия', layout: 0.1, front: 0.1, checked: true, admin: 0.2},
{title: 'Адаптив', layout: 0.3, checked: true, integration: 0.1, admin: 0.2},
{title: 'Второй язык', layout: 0.1, front: 0.2, integration: 0.2, model: 0.2, admin: 0.2, checked: true}
*/];
/*works.appendChild(
Title('title-1 options', 'Модификаторы времени и стоимости:'));*/
var calculation, recalculate = function (obj) {
buildHolder.innerHTML = '';
build(work, buildHolder, 1, obj);
calculation.innerHTML = '';
var addition = {};
for (var i in jobs) {
addition[i] = 0.4;
}
for (var i = 0, _i = options.length; i < _i; i++) {
var option = options[i];
if (option.input.checked) {
for (var j in jobs) {
addition[j] += option[j] || 0;
}
}
}
calculation.appendChild(Title('title-3 sub-options', 'Время работы специалистов:'));
Структура пользователя должна включать в себя, но не ограничиваться следующими полями: Фамилия, Имя, отчество, Телефон, Адрес электронной почты, Дату регистрации, Дату последней авторизации, Сессионный ключ, Хэш пароля.
var divs = timeCalculate(work[0], false, addition, obj);
for (var i = 0, _i = divs.length; i < _i; i++) {
var div = divs[i];
calculation.appendChild(div);
}
var costs = timeCalculate(work[0], true, null, obj), sum = 0;
for (var key in jobs) {
costs[key] = Math.round(costs[key] * (addition[key] ? 1 + addition[key] : 1));
sum += costs[key] * jobs[key].price
}
Структура записи права доступа включает в себя поля:
Актор, Объект, Действие, Кем выдано право, Дата выдачи права
var days = (Math.max(costs.front + costs.layout, costs.back + costs.admin + costs.model) + costs.integration +
(costs.front + costs.layout + costs.integration + costs.model + costs.admin + costs.back) / 8
) / 7,
weeks = days / 5 + 2;
//console.log(days)
Структура последних просмотренных
calculation.appendChild(Title('title-3 sub-options', 'Время разработки: ' +
Math.ceil(weeks) + ' ' + (L.plural(weeks, 'рабочая неделя', 'рабочие недели', 'рабочих недель'))
));
calculation.appendChild(Title('title-3 sub-options', 'Стоимость работ: $' + numberFormat(sum) + ' или ' + numberFormat(Math.round(sum * 63.57)) + ' руб'));
`;
var tasks = [
{
title: '',
description: [
'Разбиваем макеты на типовые блоки. Разработка системы сеток. Сборка страниц из блоков с использованием сетки и компонентов.'
],
parents: [],
work: {}
};
for (var i = 0, _i = options.length; i < _i; i++) {
var option = options[i];
works.appendChild(
D('div', {cls: 'option'}, D('label', {cls: 'clickable'}, [option.input = D('input', {
on: {
change: recalculate,
click: recalculate
}, attr: {type: 'checkbox', checked: option.checked}, prop: {checked: option.checked}
}), option.title])))
}
];
works.appendChild(Title('title-1 options ', 'Итоговый расчёт'));
works.appendChild(calculation = D('div', {cls: 'calculation sub'}));
s.sub([
'constructor.Microservice', 'constructor.Portal',
'constructor.Video', 'constructor.smartTV',
'constructor.Mobile', 'constructor.Locale',
'constructor.design','constructor.2fa'], function(
micro, portal, video, smart, mobile, locale, design,tufa
){
var obj = {};
`noContent,simple,content,moderator,social,socialGroup,
micro,tiny,nano,small,small2,normal,big,
WebOS,Tizen,Chromecast,Amazon,Browser,
web,app,
rus,en,rusSystem,rusEnSystem,system,
no2fa,tuFaSMS,tuFaEmail,tuFaBoth
asIs,components`.split(/[\s\n,]/).map(a=>a.trim()).filter(String).forEach(a=>obj[a]=false);
obj.api = !micro;
obj.microservice = micro;
obj[portal] = true;
obj[video] = true;
smart.forEach(function (s) {
obj[s] = true;
});
obj[mobile] = true;
obj[locale] = true;
obj[design] = true;
obj[design] = true;
obj[tufa] = true;
var dom = <div class={'plan'}>
<Header>Этапы разработки</Header>
console.log(obj)
/*console.log(arguments)
*/
recalculate(obj);
});
</div>;
return dom;
};
return works;
};
return Plan;
})(D)
export {Plan};
\ No newline at end of file
.content {
margin:0 0 0 64px;
position: relative;
}
body {
margin: 0 0 64px 0;
padding: 0;
font-family: 'Yanone Kaffeesatz', sans-serif;
font-weight: 300;
font-size: 24px;
/*line-height: 1.3;*/
}
.sub {
padding: 0 0 0 32px;
}
.description {
font-size: 20px;
}
.h2 {
margin: 0 0 8px 8px;
font-size: 20px;
}
.title {
display: inline-block;
vertical-align: top;
margin: 0.5em 0;
padding: 4px 4px 0 0;
position: relative;
}
.title-1 {
font-size: 56px;
line-height: 1;
}
.title-2 {
font-size: 48px;
}
.title-25 {
font-size: 40px;
}
.title-3 {
font-size: 32px;
}
.title-4 {
font-size: 24px;
}
.time {
display: inline;
font-size: 16px;
color: #777;
}
.cost {
display: inline;
padding: 0 16px 0 0;
}
.cost-name, .cost-amount {
padding: 0 4px 0 0;
display: inline;
white-space: nowrap;
}
.cost-amount {
padding-left: 8px;
}
.cost-after {
display: inline-block;
}
.collapsed .sub, .collapsed .description{
display: none;
}
#works button {
border: 0;
background: transparent;
font-size: 32px;
vertical-align: middle;
padding: 0;
margin: -8px 0 0 -32px;
width: 32px;
font-family: monospace;
}
.option {
padding: 16px 0 0 0;
font-size:32px;
}
.option input {
width: 24px;
height: 24px;
margin: 0 8px 0 -32px;
padding: 0;
}
.clickable {
cursor: pointer
}
.calculation .cost {
display: block;
}
.title.sub-options {
padding: 8px 0 8px 0;
margin-left: -32px;
}
.title-1:before, .title-2:before, .title-3:before {
content: '';
left: -135px;
width: 128px;
height: 8px;
position: absolute;
z-index: -1;
top: 35px;
}
.title-1, .title-2, .title-3 {
background-repeat: no-repeat;
background-size: 100% 56%;
background-position: 35% 55%;
}
.title-2 {
color: #104361;
/*background-image: linear-gradient(14deg, #184f6b 0%, #092839 100%);*/
/*text-shadow: 0px 1px 3px #092839;*/
}
.title-3 {
color: #870036;
/*
background-image: linear-gradient(179deg, #76002c 0%, #f5a94b 100%);
background-size: 100% 40%;
text-shadow: 0px 1px 3px #76002c;*/
}
.title-1 {
color: #334808;
/*
background-image: linear-gradient(179deg, #666f3b 0%, #182103 100%);
background-size: 100% 40%;
background-position: 35% 68%;
text-shadow: 0px 1px 3px #182103;*/
}
#works .options {
margin: 64px 0 0 0;
margin-left: -3px;
}
.pre-options {
margin-left: -32px;
}
.description {
white-space: pre-line;
max-width: 640px;
}
.comment {
color: #559405;
font-style: italic;
padding: 16px 0 8px 0;
}
.list {
margin: 4px 0 16px 0;
padding-inline-start: 13px;
/*list-style-image: linear-gradient(14deg, #997ab5 0%, #75848a 100%);*/
}
ul {
list-style: square; /* Remove default bullets */
}
.nobr {
white-space: nowrap;
display: inline-block;
}
\ No newline at end of file
import {Info, Content, Note, Header, SubHeader, Field} from '/cmp/Blocks.jsx';
import { PlainSelect } from "cmp/PlainSelect/PlainSelect.jsx";
import { Checkbox } from "cmp/Checkbox/Checkbox.jsx";
import { Radio } from "cmp/Radio/Radio.jsx";
import { Input } from "cmp/Input/Input.jsx";
import { TimeInput } from "cmp/TimeInput/TimeInput.jsx";
import { NumberInput } from "cmp/NumberInput/NumberInput.jsx";
import Add from "/svg/ic_add.svg";
import "./Plan.scss";
import { Collapse } from "cmp/Collapse/Collapse.jsx";
var PlanOld = function(){
s.set( {} );
var tasks = `
Разбить макеты на типовые блоки
#вёрстка: 6h * $componentCount
#dependency: design
>После получения финального дизайна выделить все присутствующие в макетах компоненты и оценить время необходимое для их вёрстки
Вёрстка страниц макетов
#id:markup-full
$constructor.design===as-is
#вёрстка: 8h * $pagesCount
#dependency: design
>Вёрстка макетов всех отрисованных страниц
Вёрстка страниц макетов
#id:markup-full
$constructor.design===components
#вёрстка: 3h * $pagesCount
#dependency: design, design-grid
>Вёрстка макетов всех отрисованных страниц с использованием заготовленных дизайн-сеток
Построить адаптивную систему сеток для позицонирования компонентов
#id:design-grid
$constructor.design===components
#вёрстка: 4d
#dependency: design
#tag: ui
>Определить и унифицировать гайдлайны для построения всех экранов. Гайдлайны должны представлять собой дизайн-систему позволяющую добавлять компоненты на страницу, сохраняя результат соответствующим дизайну. Сетка должна адекватно отображаться как на телевизоре, так и на мобильном устройстве.
Построить инфраструктуру для перекодирования видео
Развернуть очередь сообщений
Пользователи
Сервис отвечающий за регистрацию и авторизацию пользователей #микросервисы
Сервис проверяющий права пользователя X на совершения действия Y над объектом Z #микросервисы
Реализация API регистрации и авторизации пользователей #монолит
Реализация API проверки прав пользователя X на совершения действия Y над объектом Z #монолит
Структура пользователя должна включать в себя, но не ограничиваться следующими полями: Фамилия, Имя, отчество, Телефон, Адрес электронной почты, Дату регистрации, Дату последней авторизации, Сессионный ключ, Хэш пароля.
Структура записи права доступа включает в себя поля:
Актор, Объект, Действие, Кем выдано право, Дата выдачи права
Структура последних просмотренных
`;
var tasks = [
{
title: '',
description: [
'Разбиваем макеты на типовые блоки. Разработка системы сеток. Сборка страниц из блоков с использованием сетки и компонентов.'
],
parents: [],
work: {}
}
];
var dom = <div class={'plan'}>
<Header>Этапы разработки</Header>
</div>;
return dom;
};
export {PlanOld};
\ No newline at end of file
......@@ -19,7 +19,9 @@ var Stack = function(){
'constructor.Video': 'micro',
'constructor.smartTV': ['WebOS'],
'constructor.Mobile': 'web',
'constructor.Locale': 'rus'
'constructor.Locale': 'rus',
'constructor.design': 'asIs',
'constructor.2fa': 'no2fa'
} );
......@@ -193,6 +195,13 @@ var Stack = function(){
Fire TV Amazon
</button>
<button onClick={()=>toggle('constructor.smartTV', 'Browser') }
cls={D.cls('option', {
active: _=>s.sub('constructor.smartTV', val=> _(val.indexOf('Browser')>-1))
})}>
Use Web Browser to view videos
</button>
</Content>
<SubHeader>Мобильная платформа</SubHeader>
......@@ -214,7 +223,10 @@ var Stack = function(){
cls={D.cls('option', {active: s.valEqual('constructor.Locale', 'rus')})}>
Русский язык
</button>
<button onClick={()=>s.set('constructor.Locale', 'en')}
cls={D.cls('option', {active: s.valEqual('constructor.Locale', 'en')})}>
Английский язык
</button>
<button onClick={()=>s.set('constructor.Locale', 'rusSystem')}
cls={D.cls('option', {active: s.valEqual('constructor.Locale', 'rusSystem')})}>
Русский язык с системой локализации
......@@ -233,8 +245,8 @@ var Stack = function(){
<SubHeader>Дизайн-система</SubHeader>
<Content>
<button onClick={()=>s.set('constructor.design', 'as-is')}
cls={D.cls('option', {active: s.valEqual('constructor.design', 'as-is')})}>
<button onClick={()=>s.set('constructor.design', 'asIs')}
cls={D.cls('option', {active: s.valEqual('constructor.design', 'asIs')})}>
Вёрстка макетов
</button>
......@@ -244,6 +256,28 @@ var Stack = function(){
</button>
</Content>
<SubHeader>Двуфакторная авторизация</SubHeader>
<Content>
<button onClick={()=>s.set('constructor.2fa', 'no2fa')}
cls={D.cls('option', {active: s.valEqual('constructor.2fa', 'no2fa')})}>
Не интегрируем двуфакторную авторизаци
</button>
<button onClick={()=>s.set('constructor.2fa', 'tuFaSMS')}
cls={D.cls('option', {active: s.valEqual('constructor.2fa', 'tuFaSMS')})}>
Двуфакторная авторизация по смс
</button>
<button onClick={()=>s.set('constructor.2fa', 'tuFaEmail')}
cls={D.cls('option', {active: s.valEqual('constructor.2fa', 'tuFaEmail')})}>
Двуфакторная авторизация по e-mail
</button>
<button onClick={()=>s.set('constructor.2fa', 'tuFaBoth')}
cls={D.cls('option', {active: s.valEqual('constructor.2fa', 'tuFaBoth')})}>
Двуфакторная авторизация по e-mail и смс
</button>
</Content>
</div>;
return dom;
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment