[{"data":1,"prerenderedAt":805},["ShallowReactive",2],{"navigation_docs_ru":3,"-ru-development-date-utilities":167,"-ru-development-date-utilities-surround":800},[4,61,101,146],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":60},"Руководство","i-lucide-book-open","\u002Fru\u002Fguide","ru\u002F1.guide",[10,15,20,25,30,35,40,45,50,55],{"title":11,"path":12,"stem":13,"icon":14},"Введение","\u002Fru\u002Fguide\u002Fintroduction","ru\u002F1.guide\u002F01.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Установка","\u002Fru\u002Fguide\u002Finstallation","ru\u002F1.guide\u002F02.installation","i-lucide-smartphone",{"title":21,"path":22,"stem":23,"icon":24},"Авторизация","\u002Fru\u002Fguide\u002Fauth","ru\u002F1.guide\u002F03.auth","i-lucide-lock",{"title":26,"path":27,"stem":28,"icon":29},"Кошельки","\u002Fru\u002Fguide\u002Fwallets","ru\u002F1.guide\u002F04.wallets","i-lucide-wallet",{"title":31,"path":32,"stem":33,"icon":34},"Категории","\u002Fru\u002Fguide\u002Fcategories","ru\u002F1.guide\u002F05.categories","i-lucide-tags",{"title":36,"path":37,"stem":38,"icon":39},"Транзакции","\u002Fru\u002Fguide\u002Ftransactions","ru\u002F1.guide\u002F06.transactions","i-lucide-receipt",{"title":41,"path":42,"stem":43,"icon":44},"Переводы","\u002Fru\u002Fguide\u002Ftransfers","ru\u002F1.guide\u002F07.transfers","i-lucide-arrow-left-right",{"title":46,"path":47,"stem":48,"icon":49},"Статистика","\u002Fru\u002Fguide\u002Fstatistics","ru\u002F1.guide\u002F08.statistics","i-lucide-bar-chart-3",{"title":51,"path":52,"stem":53,"icon":54},"Тема","\u002Fru\u002Fguide\u002Ftheme","ru\u002F1.guide\u002F09.theme","i-lucide-palette",{"title":56,"path":57,"stem":58,"icon":59},"Настройки","\u002Fru\u002Fguide\u002Fsettings","ru\u002F1.guide\u002F10.settings","i-lucide-settings",false,{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":60},"Разработка","i-lucide-code","\u002Fru\u002Fdevelopment","ru\u002F2.development",[67,71,76,81,86,91,96],{"title":16,"path":68,"stem":69,"icon":70},"\u002Fru\u002Fdevelopment\u002Finstallation","ru\u002F2.development\u002F01.installation","i-lucide-download",{"title":72,"path":73,"stem":74,"icon":75},"Граф кодовой базы","\u002Fru\u002Fdevelopment\u002Funderstand-anything","ru\u002F2.development\u002F02.understand-anything","i-lucide-network",{"title":77,"path":78,"stem":79,"icon":80},"Офлайн и PWA","\u002Fru\u002Fdevelopment\u002Foffline","ru\u002F2.development\u002F03.offline","i-lucide-wifi-off",{"title":82,"path":83,"stem":84,"icon":85},"История миграций данных","\u002Fru\u002Fdevelopment\u002Fmigration","ru\u002F2.development\u002F04.migration","i-lucide-database",{"title":87,"path":88,"stem":89,"icon":90},"Деплой","\u002Fru\u002Fdevelopment\u002Fdeployment","ru\u002F2.development\u002F05.deployment","i-lucide-rocket",{"title":92,"path":93,"stem":94,"icon":95},"Тестирование","\u002Fru\u002Fdevelopment\u002Ftesting","ru\u002F2.development\u002F06.testing","i-lucide-flask-conical",{"title":97,"path":98,"stem":99,"icon":100},"Дата-утилиты","\u002Fru\u002Fdevelopment\u002Fdate-utilities","ru\u002F2.development\u002F07.date-utilities","i-lucide-calendar",{"title":102,"icon":103,"path":104,"stem":105,"children":106,"page":60},"Справочник","i-lucide-file-code","\u002Fru\u002Freference","ru\u002F3.reference",[107,112,116,121,126,131,136,141],{"title":108,"path":109,"stem":110,"icon":111},"Архитектура","\u002Fru\u002Freference\u002Farchitecture","ru\u002F3.reference\u002F01.architecture","i-lucide-boxes",{"title":113,"path":114,"stem":115,"icon":44},"Типы транзакций","\u002Fru\u002Freference\u002Ftransaction-types","ru\u002F3.reference\u002F02.transaction-types",{"title":117,"path":118,"stem":119,"icon":120},"Синхронизация","\u002Fru\u002Freference\u002Fsync","ru\u002F3.reference\u002F03.sync","i-lucide-refresh-cw",{"title":122,"path":123,"stem":124,"icon":125},"Офлайн-first","\u002Fru\u002Freference\u002Foffline-first","ru\u002F3.reference\u002F04.offline-first","i-lucide-list-ordered",{"title":127,"path":128,"stem":129,"icon":130},"Тех. решения","\u002Fru\u002Freference\u002Ftech-decisions","ru\u002F3.reference\u002F05.tech-decisions","i-lucide-lightbulb",{"title":132,"path":133,"stem":134,"icon":135},"Валидация","\u002Fru\u002Freference\u002Fvalidation-strategy","ru\u002F3.reference\u002F06.validation-strategy","i-lucide-shield-check",{"title":137,"path":138,"stem":139,"icon":140},"Что изменилось со времён Firebase","\u002Fru\u002Freference\u002Ffirebase-migration","ru\u002F3.reference\u002F07.firebase-migration","i-lucide-hamburger",{"title":142,"path":143,"stem":144,"icon":145},"Производительность","\u002Fru\u002Freference\u002Fperformance","ru\u002F3.reference\u002F08.performance","i-lucide-gauge",{"title":147,"icon":148,"path":149,"stem":150,"children":151,"page":60},"Премиум","i-lucide-star","\u002Fru\u002Fpremium","ru\u002F4.premium",[152,157,162],{"title":153,"path":154,"stem":155,"icon":156},"Обзор","\u002Fru\u002Fpremium\u002Foverview","ru\u002F4.premium\u002F01.overview","i-lucide-layers",{"title":158,"path":159,"stem":160,"icon":161},"Telegram-бот","\u002Fru\u002Fpremium\u002Ftelegram-bot","ru\u002F4.premium\u002F02.telegram-bot","i-lucide-send",{"title":163,"path":164,"stem":165,"icon":166},"AI Chat","\u002Fru\u002Fpremium\u002Fai-chat","ru\u002F4.premium\u002F03.ai-chat","i-lucide-sparkles",{"id":168,"title":97,"body":169,"description":792,"extension":793,"links":794,"meta":795,"navigation":796,"path":98,"seo":797,"stem":99,"__hash__":799},"docs_ru\u002Fru\u002F2.development\u002F07.date-utilities.md",{"type":170,"value":171,"toc":777},"minimark",[172,176,181,285,289,295,391,397,449,456,461,469,553,557,569,577,599,603,609,615,618,621,648,652,658,689,692,696,702,708,715,719,740,744,754,758,773],[173,174,175],"p",{},"Дата-утилиты отвечают за выбор периодов, вычисление диапазонов, генерацию интервалов и форматирование дат в статистике.",[177,178,180],"h2",{"id":179},"ключевые-файлы","Ключевые файлы",[182,183,184,197],"table",{},[185,186,187],"thead",{},[188,189,190,194],"tr",{},[191,192,193],"th",{},"Файл",[191,195,196],{},"Назначение",[198,199,200,223,245,255,265,275],"tbody",{},[188,201,202,209],{},[203,204,205],"td",{},[206,207,208],"code",{},"app\u002Fcomponents\u002Fdate\u002Futils.ts",[203,210,211,212,215,216,215,219,222],{},"Базовые утилиты: ",[206,213,214],{},"toDuration",", ",[206,217,218],{},"getStartOf",[206,220,221],{},"getEndOf",", вычисление интервалов",[188,224,225,230],{},[203,226,227],{},[206,228,229],{},"app\u002Fcomponents\u002Fdate\u002Ftypes.ts",[203,231,232,233,215,236,215,239,215,242],{},"Типы: ",[206,234,235],{},"Period",[206,237,238],{},"Range",[206,240,241],{},"StatDateParams",[206,243,244],{},"IntervalsInRangeProps",[188,246,247,252],{},[203,248,249],{},[206,250,251],{},"app\u002Fcomponents\u002Fdate\u002FstatDateParams.ts",[203,253,254],{},"Вычисление диапазона из параметров, парсинг query-параметров",[188,256,257,262],{},[203,258,259],{},[206,260,261],{},"app\u002Fcomponents\u002Fdate\u002FuseStatDate.ts",[203,263,264],{},"Composable: реактивное состояние дат с персистенцией в localStorage",[188,266,267,272],{},[203,268,269],{},[206,270,271],{},"app\u002Fcomponents\u002Fstat\u002Fdate\u002FuseGetDateRange.ts",[203,273,274],{},"Человекочитаемые подписи (\"Сегодня\", \"Последние 3 месяца\", \"Мар - Июн\")",[188,276,277,282],{},[203,278,279],{},[206,280,281],{},"app\u002Fcomponents\u002Fstat\u002Fintervals.ts",[203,283,284],{},"Распределение транзакций по интервалам, средние значения",[177,286,288],{"id":287},"система-периодов","Система периодов",[173,290,291,292,294],{},"Все операции с датами используют union-тип ",[206,293,235],{},":",[296,297,302],"pre",{"className":298,"code":299,"language":300,"meta":301,"style":301},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const periods = ['day', 'week', 'month', 'year'] as const\ntype Period = typeof periods[number]\n","ts","",[206,303,304,372],{"__ignoreMap":301},[305,306,309,313,317,321,324,327,331,333,336,339,342,344,346,348,351,353,355,357,360,362,365,369],"span",{"class":307,"line":308},"line",1,[305,310,312],{"class":311},"spNyl","const",[305,314,316],{"class":315},"sTEyZ"," periods ",[305,318,320],{"class":319},"sMK4o","=",[305,322,323],{"class":315}," [",[305,325,326],{"class":319},"'",[305,328,330],{"class":329},"sfazB","day",[305,332,326],{"class":319},[305,334,335],{"class":319},",",[305,337,338],{"class":319}," '",[305,340,341],{"class":329},"week",[305,343,326],{"class":319},[305,345,335],{"class":319},[305,347,338],{"class":319},[305,349,350],{"class":329},"month",[305,352,326],{"class":319},[305,354,335],{"class":319},[305,356,338],{"class":319},[305,358,359],{"class":329},"year",[305,361,326],{"class":319},[305,363,364],{"class":315},"] ",[305,366,368],{"class":367},"s7zQu","as",[305,370,371],{"class":311}," const\n",[305,373,375,378,382,385,388],{"class":307,"line":374},2,[305,376,377],{"class":311},"type",[305,379,381],{"class":380},"sBMFI"," Period",[305,383,384],{"class":319}," =",[305,386,387],{"class":319}," typeof",[305,389,390],{"class":315}," periods[number]\n",[173,392,393,394,396],{},"Три базовых хелпера преобразуют ",[206,395,235],{}," в операции date-fns:",[296,398,400],{"className":298,"code":399,"language":300,"meta":301,"style":301},"toDuration(period, value)   \u002F\u002F Period -> Duration ({ days: 3 }, { months: 1 })\ngetStartOf(date, period)    \u002F\u002F Period -> начало периода (startOfMonth, startOfWeek, ...)\ngetEndOf(date, period)      \u002F\u002F Period -> конец периода (endOfMonth, endOfWeek, ...)\n",[206,401,402,419,434],{"__ignoreMap":301},[305,403,404,407,410,412,415],{"class":307,"line":308},[305,405,214],{"class":406},"s2Zo4",[305,408,409],{"class":315},"(period",[305,411,335],{"class":319},[305,413,414],{"class":315}," value)   ",[305,416,418],{"class":417},"sHwdD","\u002F\u002F Period -> Duration ({ days: 3 }, { months: 1 })\n",[305,420,421,423,426,428,431],{"class":307,"line":374},[305,422,218],{"class":406},[305,424,425],{"class":315},"(date",[305,427,335],{"class":319},[305,429,430],{"class":315}," period)    ",[305,432,433],{"class":417},"\u002F\u002F Period -> начало периода (startOfMonth, startOfWeek, ...)\n",[305,435,437,439,441,443,446],{"class":307,"line":436},3,[305,438,221],{"class":406},[305,440,425],{"class":315},[305,442,335],{"class":319},[305,444,445],{"class":315}," period)      ",[305,447,448],{"class":417},"\u002F\u002F Period -> конец периода (endOfMonth, endOfWeek, ...)\n",[173,450,451,452,455],{},"Все три используют exhaustive ",[206,453,454],{},"switch"," - добавление нового варианта периода вызовет ошибку компиляции, а не молчаливый fallback.",[457,458,460],"h3",{"id":459},"типобезопасный-duration","Типобезопасный Duration",[173,462,463,465,466,294],{},[206,464,214],{}," заменяет динамические ключи свойств типизированным возвратом ",[206,467,468],{},"Duration",[296,470,472],{"className":298,"code":471,"language":300,"meta":301,"style":301},"\u002F\u002F Раньше: теряет типобезопасность, TS видит Record\u003Cstring, number>\nsub(date, { [`${period}s`]: value })\n\n\u002F\u002F Сейчас: возвращает типизированный Duration\nsub(date, toDuration(period, value))\n",[206,473,474,479,522,528,534],{"__ignoreMap":301},[305,475,476],{"class":307,"line":308},[305,477,478],{"class":417},"\u002F\u002F Раньше: теряет типобезопасность, TS видит Record\u003Cstring, number>\n",[305,480,481,484,486,488,491,494,497,500,503,506,509,512,514,517,519],{"class":307,"line":374},[305,482,483],{"class":406},"sub",[305,485,425],{"class":315},[305,487,335],{"class":319},[305,489,490],{"class":319}," {",[305,492,323],{"class":493},"swJcz",[305,495,496],{"class":319},"`${",[305,498,499],{"class":315},"period",[305,501,502],{"class":319},"}",[305,504,505],{"class":493},"s",[305,507,508],{"class":319},"`",[305,510,511],{"class":493},"]",[305,513,294],{"class":319},[305,515,516],{"class":315}," value ",[305,518,502],{"class":319},[305,520,521],{"class":315},")\n",[305,523,524],{"class":307,"line":436},[305,525,527],{"emptyLinePlaceholder":526},true,"\n",[305,529,531],{"class":307,"line":530},4,[305,532,533],{"class":417},"\u002F\u002F Сейчас: возвращает типизированный Duration\n",[305,535,537,539,541,543,546,548,550],{"class":307,"line":536},5,[305,538,483],{"class":406},[305,540,425],{"class":315},[305,542,335],{"class":319},[305,544,545],{"class":406}," toDuration",[305,547,409],{"class":315},[305,549,335],{"class":319},[305,551,552],{"class":315}," value))\n",[177,554,556],{"id":555},"вычисление-диапазона-дат","Вычисление диапазона дат",[173,558,559,562,563,565,566,294],{},[206,560,561],{},"computeDateRange()"," преобразует ",[206,564,241],{}," в конкретный ",[206,567,568],{},"{ start, end }",[296,570,575],{"className":571,"code":573,"language":574},[572],"language-text","Приоритет:\n1. customDate (выбор в календаре)      -> возвращается как есть\n2. isShowMaxRange                      -> maxRange.start до конца текущего периода\n3. isShowMaxRange + isSkipEmpty        -> maxRange как есть\n4. вычисленный                         -> от Date.now() по rangeBy\u002FrangeDuration\u002FrangeOffset\n","text",[206,576,573],{"__ignoreMap":301},[173,578,579,580,583,584,587,588,590,591,594,595,598],{},"Диапазон - это Vue ",[206,581,582],{},"computed",", использующий ",[206,585,586],{},"Date.now()"," как базу. Поскольку ",[206,589,586],{}," не является реактивной зависимостью, диапазон обновляется только при изменении ",[206,592,593],{},"params"," или ",[206,596,597],{},"maxRange"," (не автоматически в полночь). Обновление страницы или любое изменение параметров запускает перевычисление.",[177,600,602],{"id":601},"генерация-интервалов","Генерация интервалов",[173,604,605,608],{},[206,606,607],{},"getIntervalsInRange()"," разбивает диапазон дат на подинтервалы:",[296,610,613],{"className":611,"code":612,"language":574},[572],"range: 1 мар - 31 мар, intervalsBy: 'week'\n-> [1-2 мар, 3-9 мар, 10-16 мар, 17-23 мар, 24-30 мар, 31 мар]\n",[206,614,612],{"__ignoreMap":301},[173,616,617],{},"Крайние интервалы обрезаются по границам диапазона.",[457,619,142],{"id":620},"производительность",[173,622,623,624,627,628,631,632,635,636,639,640,643,644,647],{},"Интервалы собираются через ",[206,625,626],{},"push()"," с последующим ",[206,629,630],{},"reverse()"," вместо ",[206,633,634],{},"unshift()"," в цикле. ",[206,637,638],{},"unshift"," сдвигает все элементы при каждом вызове (O(n^2) на весь цикл), тогда как ",[206,641,642],{},"push"," + ",[206,645,646],{},"reverse"," работает за O(n).",[177,649,651],{"id":650},"распределение-транзакций","Распределение транзакций",[173,653,654,657],{},[206,655,656],{},"bucketTrnsByIntervals()"," распределяет транзакции по интервалам бинарным поиском:",[296,659,661],{"className":298,"code":660,"language":300,"meta":301,"style":301},"\u002F\u002F O(N log M), где N = транзакции, M = интервалы\nfor each transaction:\n  бинарный поиск по интервалам\n  добавление в нужный бакет\n",[206,662,663,668,679,684],{"__ignoreMap":301},[305,664,665],{"class":307,"line":308},[305,666,667],{"class":417},"\u002F\u002F O(N log M), где N = транзакции, M = интервалы\n",[305,669,670,673,676],{"class":307,"line":374},[305,671,672],{"class":315},"for each ",[305,674,675],{"class":380},"transaction",[305,677,678],{"class":319},":\n",[305,680,681],{"class":307,"line":436},[305,682,683],{"class":315},"  бинарный поиск по интервалам\n",[305,685,686],{"class":307,"line":530},[305,687,688],{"class":315},"  добавление в нужный бакет\n",[173,690,691],{},"Интервалы отсортированы и не пересекаются, что позволяет использовать бинарный поиск. Транзакции вне любого интервала молча пропускаются.",[177,693,695],{"id":694},"форматирование-дат","Форматирование дат",[173,697,698,701],{},[206,699,700],{},"useGetDateRange()"," создает человекочитаемые подписи. Каждая функция форматирования возвращает полную строку (без разделения на \"начало\"\u002F\"конец\" с последующей конкатенацией):",[296,703,706],{"className":704,"code":705,"language":574},[572],"duration=1, текущий период   -> \"Сегодня\", \"Этот месяц\", \"Этот год\"\nduration=1, предыдущий       -> \"Вчера\", \"Прошлый месяц\", \"Прошлый год\"\nduration=1, этот год         -> \"15 марта\"\nduration>1, текущий период   -> \"Последние 3 дня\", \"Последние 6 месяцев\"\nостальные                    -> \"Мар - Июн\", \"10-15 мар 2024\"\n",[206,707,705],{"__ignoreMap":301},[173,709,710,711,714],{},"Сравнение периодов использует ",[206,712,713],{},"isSamePeriod(date1, date2, period)"," - один хелпер вместо повторяющихся switch-блоков.",[177,716,718],{"id":717},"парсинг-query-параметров","Парсинг query-параметров",[173,720,721,724,725,727,728,731,732,735,736,739],{},[206,722,723],{},"parseStatDateQueryParams()"," мержит URL query-параметры в ",[206,726,241],{}," через Zod-валидацию. Все поля проверяются через ",[206,729,730],{},"!== undefined"," (не falsy), чтобы ",[206,733,734],{},"rangeOffset=0"," и ",[206,737,738],{},"intervalSelected=0"," применялись корректно.",[177,741,743],{"id":742},"вычисление-средних","Вычисление средних",[173,745,746,749,750,753],{},[206,747,748],{},"computeAverageTotal()"," считает средние за день\u002Fнеделю\u002Fмесяц для диапазона дат. Возвращает ",[206,751,752],{},"undefined"," для однодневных диапазонов или нулевых сумм (ранний выход до вычисления интервалов). Среднее за день всегда присутствует при возврате результата (диапазон >= 2 дней гарантирован), поэтому проверка на пустой объект не нужна.",[177,755,757],{"id":756},"далее","Далее",[759,760,761,768],"ul",{},[762,763,764,767],"li",{},[765,766,108],"a",{"href":109}," - структура проекта и паттерн хранилищ",[762,769,770,772],{},[765,771,127],{"href":128}," - обоснование ключевых решений",[774,775,776],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":301,"searchDepth":374,"depth":374,"links":778},[779,780,783,784,787,788,789,790,791],{"id":179,"depth":374,"text":180},{"id":287,"depth":374,"text":288,"children":781},[782],{"id":459,"depth":436,"text":460},{"id":555,"depth":374,"text":556},{"id":601,"depth":374,"text":602,"children":785},[786],{"id":620,"depth":436,"text":142},{"id":650,"depth":374,"text":651},{"id":694,"depth":374,"text":695},{"id":717,"depth":374,"text":718},{"id":742,"depth":374,"text":743},{"id":756,"depth":374,"text":757},"Вычисление диапазонов, форматирование периодов и распределение транзакций по интервалам.","md",null,{},{"icon":100},{"title":97,"description":798},"Дата-утилиты Финапки - типобезопасная работа с периодами, вычисление интервалов, форматирование дат и распределение транзакций бинарным поиском.","PMIUE9hlIe-bQurJj6Ny78W5QEbGeGp9CIEYyUSw018",[801,803],{"title":92,"path":93,"stem":94,"description":802,"icon":95,"children":-1},"Скрипты для unit- и E2E-тестов.",{"title":108,"path":109,"stem":110,"description":804,"icon":111,"children":-1},"Инициализация, структура проекта, паттерн хранилищ, авторизация - Supabase + PowerSync.",1782114340102]