{"id":20434188,"url":"https://github.com/ketrindorofeeva/to-do-list","last_synced_at":"2026-04-08T11:32:40.037Z","repository":{"id":161568833,"uuid":"636246050","full_name":"KetrinDorofeeva/to-do-list","owner":"KetrinDorofeeva","description":"Список дел","archived":false,"fork":false,"pushed_at":"2023-08-18T06:52:50.000Z","size":33737,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-03T15:29:58.847Z","etag":null,"topics":["bootstrap5","html","iconify","javascript","moment-js","pinia","scss","vite","vue-router","vue3"],"latest_commit_sha":null,"homepage":"https://ketrindorofeeva.github.io/to-do-list/","language":"Vue","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/KetrinDorofeeva.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-04T12:26:52.000Z","updated_at":"2025-04-20T19:31:53.000Z","dependencies_parsed_at":null,"dependency_job_id":"def1bede-0b1f-4df0-b850-c9e863d76ce4","html_url":"https://github.com/KetrinDorofeeva/to-do-list","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/KetrinDorofeeva/to-do-list","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KetrinDorofeeva%2Fto-do-list","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KetrinDorofeeva%2Fto-do-list/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KetrinDorofeeva%2Fto-do-list/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KetrinDorofeeva%2Fto-do-list/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KetrinDorofeeva","download_url":"https://codeload.github.com/KetrinDorofeeva/to-do-list/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KetrinDorofeeva%2Fto-do-list/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31554091,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T10:21:54.569Z","status":"ssl_error","status_checked_at":"2026-04-08T10:21:38.171Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bootstrap5","html","iconify","javascript","moment-js","pinia","scss","vite","vue-router","vue3"],"created_at":"2024-11-15T08:24:48.966Z","updated_at":"2026-04-08T11:32:40.013Z","avatar_url":"https://github.com/KetrinDorofeeva.png","language":"Vue","funding_links":[],"categories":[],"sub_categories":[],"readme":"# to-do-list\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003cb\u003eТехнологии разработки\u003c/b\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cb\u003eЯзыки программирования\u003c/b\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cb\u003eФреймворки\u003c/b\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cb\u003eСервер разработки\u003c/b\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cb\u003eБиблиотеки\u003c/b\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cb\u003eИконки\u003c/b\u003e\u003c/td\u003e \n  \u003c/tr\u003e\n  \n   \u003ctr\u003e\n    \u003ctd\u003e\u003cimg src = \"https://img.shields.io/badge/-HTML-e44d25?style=for-the-badge\u0026logo=HTML5\u0026labelColor=fcede9\u0026logoColor=e44d25\" alt = \"HTML\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src = \"https://img.shields.io/badge/-JavaScript-f7df1e?style=for-the-badge\u0026logo=JavaScript\u0026labelColor=fefce9\u0026logoColor=f7df1e\" alt = \"JavaScript\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src = \"https://img.shields.io/badge/-Vue3-3fb27f?style=for-the-badge\u0026logo=Vue.js\u0026labelColor=ecf7f2\u0026logoColor=3fb27f\" alt = \"Vue3.js\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src = \"https://img.shields.io/badge/-Vite-0e2eee?style=for-the-badge\u0026logo=Vite\u0026labelColor=e7eafd\u0026logoColor=0e2eee\" alt = \"Vite\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src = \"https://img.shields.io/badge/-Pinia-ffe165?style=for-the-badge\u0026logo=Pinia\u0026labelColor=fffcf0\u0026logoColor=ffe165\" alt = \"Pinia\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src = \"https://img.shields.io/badge/-Iconify-026c9c?style=for-the-badge\u0026logo=Iconify\u0026labelColor=e6f0f5\u0026logoColor=026c9c\" alt = \"Iconify\"\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \n   \u003ctr\u003e\n    \u003ctd\u003e\u003cimg src = \"https://img.shields.io/badge/-SCSS-214ce5?style=for-the-badge\u0026logo=CSS3\u0026labelColor=e9edfc\u0026logoColor=214ce5\" alt = \"SCSS\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src = \"https://img.shields.io/badge/-Bootstrap-860afb?style=for-the-badge\u0026logo=Bootstrap\u0026labelColor=f3e7ff\u0026logoColor=860afb\" alt = \"Bootstrap4\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src = \"https://img.shields.io/badge/-vuerouter-41b883?style=for-the-badge\u0026logo=vue-router\u0026labelColor=f0f8f7\u0026logoColor=41b883\" alt = \"vue-router\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src = \"https://img.shields.io/badge/-moment.js-6cb5ab?style=for-the-badge\u0026logo=moment.js\u0026labelColor=f0f8f7\u0026logoColor=6cb5ab\" alt = \"moment.js\"\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n## \u003cp id = \"table-of-contents\"\u003eОглавление\u003c/p\u003e\n- \u003ca href = \"#documentation\"\u003eДокументация\u003c/a\u003e\n- \u003ca href = \"#technical-task\"\u003eТехническое задание\u003c/a\u003e\n- \u003ca href = \"#vue3-framework\"\u003eУстановка и запуск фреймворка Vue 3 (Vite)\u003c/a\u003e\n- \u003ca href = \"#installation-pinia\"\u003eУстановка Pinia\u003c/a\u003e\n- \u003ca href = \"#installation-vue-router\"\u003eУстановка Vue Router\u003c/a\u003e\n- \u003ca href = \"#implementation-software-product\"\u003eРеализация программного продукта\u003c/a\u003e\n  - \u003ca href = \"#authorization\"\u003eАвторизация\u003c/a\u003e\n  - \u003ca href = \"#tasks-page\"\u003eСтраница задач\u003c/a\u003e\n    - \u003ca href = \"#tasks-folders\"\u003eПапки с задачами\u003c/a\u003e\n    - \u003ca href = \"#search-for-tasks-by-name\"\u003eПоиск задач по описанию\u003c/a\u003e\n    - \u003ca href = \"#tasks-sorting\"\u003eСортировка задач\u003c/a\u003e\n    - \u003ca href = \"#final-template\"\u003eИтоговый шаблон задач\u003c/a\u003e\n    - \u003ca href = \"#add-task\"\u003eДобавить задачу\u003c/a\u003e\n    - \u003ca href = \"#update-task\"\u003eРедактировать задачу\u003c/a\u003e\n    - \u003ca href = \"#delete-task\"\u003eУдалить задачу\u003c/a\u003e\n- \u003ca href = \"#project-deployment\"\u003eРазвертывание проекта на GitHub\u003c/a\u003e\n- \u003ca href = \"#project-problems\"\u003eНеполадки с проектом на GitHub\u003c/a\u003e\n\n_________________________________________________________________________________________________________________________________________________________________\n## \u003cp id = \"documentation\"\u003eДокументация\u003c/p\u003e\n- [Vite](https://vitejs.dev/)\n- [Vue 3](https://v3.ru.vuejs.org/)\n- [Pinia](https://pinia.vuejs.org/)\n- [Vue Router](https://router.vuejs.org/)\n- [Moment.js](https://momentjs.com/)\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n## \u003cp id = \"technical-task\"\u003eТехническое задание\u003c/p\u003e\nЗадача: Разработать клиентское приложение для ведение списка дел, используя заданные технологии.\n\nФункционал:\n- Создание задачи:\n  - Название задачи;\n  - Приоритет (низкий / средний / высокий);\n  - Дата создания (указывается автоматически).\n- Пометка задач выполненными;\n- Поиск задач по названию;\n- Сорировка задач по:\n  - Дате;\n  - Приоритету;\n  - Алфавиту;\n  - Изменение порядка сортировки.\n- Редактирование и удаление задач;\n- (Дополнительно) дополнить задачи пунктом \"Срок выполнения\" (с точностью до дня), выделять просроченные задачи;\n- (Дополнительно) добавить функционал группировки задач (подсписки):\n  - Пользователь может создать подсписок задач и, либо указать его при создании новой задачи, либо переместить в него существующую задачу.\n- (Дополнительно) возможность авторизации:\n\t- Регистрация не нужна, используются готовые учётки, записанные где-то в коде приложения;\n\t- Неавторизированный пользователь видит только форму авторизации;\n\t- Авторизованный пользователь видит своё имя, может разлогиниться;\n\t- Авторизованный пользователь автоматически авторизуется после перезагрузки приложения;\n\t- Разграничение того, какой пользователь какие задачи создаёт и видит делать НЕ нужно.\n\nТехнические требования:\n- Требуется только клиентская часть приложения, все необходимые данные должны храниться в браузере;\n- (Дополнительно) Использовать Vite вместо Webpack:\n\t- Примечание: делать это следует в самую первую очередь.\n- (Дополнительно) Поддержка препроцессора CSS (SCSS / Sass / Less):\n\t- Примечание: важно чтобы просто была возможность пользоваться препроцессором, изучать его синтаксис и активно им пользоваться необязательно.\n- Vue 3;\n- Pinia;\n- Библиотека для иконок (предпочтительно Iconify);\n- Использование дополнительных библиотек не возбраняется.\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n## \u003cp id = \"vue3-framework\"\u003eУстановка и запуск фреймворка Vue 3 (Vite)\u003c/p\u003e\nДля начала нужно [установить и настроить Vite](https://vitejs.dev/guide/)  \n**Vite** — это инструмент сборки, цель которого — обеспечить более быструю и экономичную разработку современных веб-проектов.\n\n**1. Установка Vite**  \n```js\nnpm create vite@latest\n```\n**2. Дать название проекту**  \n\n\u003cimg src=\"https://github.com/ketrindorofeeva/to-do-list/raw/main/for-readme/name-project.png\" width=\"70%\" alt = \"Название проекта\"/\u003e\n\n**3. Выбрать фреймворк Vue**  \n\n\u003cimg src=\"https://github.com/ketrindorofeeva/to-do-list/raw/main/for-readme/select-framework.png\" width=\"70%\" alt = \"Выбор фреймворка\"/\u003e\n\n**4. Выбрать язык программирования JavaScript**  \n\n\u003cimg src=\"https://github.com/ketrindorofeeva/to-do-list/raw/main/for-readme/programming-language.png\" width=\"70%\" alt = \"Язык программирования\"/\u003e\n\nИтог:  \n\n\u003cimg src=\"https://github.com/ketrindorofeeva/to-do-list/raw/main/for-readme/installation-summary.png\" width=\"70%\" alt = \"Итог\"/\u003e\n\n**5. Завершить настройку проекта**  \nПерейти в проект:\n```js\ncd to-do-list\n```\n\nУстановить соответствующие пакеты:\n```js\nnpm i\n```\n\nИтог:\n\n\u003cimg src=\"https://github.com/ketrindorofeeva/to-do-list/raw/main/for-readme/total.png\" width=\"70%\" alt = \"Итог\"/\u003e\n\n**6. Запустить проект** \n```js\nnpm run dev\n```\nИтог:\n\n\u003cimg src=\"https://github.com/ketrindorofeeva/to-do-list/raw/main/for-readme/launch-project.png\" width=\"70%\" alt = \"Запуск проекта\"/\u003e\n\nПерейдите по ссылке Local, чтобы увидеть проект в браузере.\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n## \u003cp id = \"installation-pinia\"\u003eУстановка Pinia\u003c/p\u003e \n**[Pinia](https://pinia.vuejs.org/introduction.html)** — это легковесная библиотека хранилища и структура управления состоянием для Vue.js. Разработанный в первую очередь для создания интерфейсных веб-приложений, он использует декларативный синтаксис и предлагает собственный API управления состоянием.\n\n**1. Установка pinia**\n```js\nnpm install pinia\n```\n\n**2. Создать экземпляр pinia в ```/src/main.js``` и передать его в приложение как плагин**\n```js\nimport {createApp} from 'vue'\nimport {createPinia} from \"pinia\" //!\nimport router from \"./router.js\";\n\nimport './style.scss'\nimport \"bootstrap/dist/css/bootstrap.min.css\";\n\nimport App from './App.vue'\n\ncreateApp(App)\n  .use(createPinia()) //!\n  .use(router)\n  .mount('#app')\n```\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n## \u003cp id = \"installation-vue-router\"\u003eУстановка Vue Router\u003c/p\u003e \n**[Vue Router](https://router.vuejs.org/installation.html)** — маршрутизатор для Vue.js.\n\n**1. Установка vue-router**\n```js\nnpm install vue-router@4\n```\n\n**2. Создадим ```router.js``` в папке ```/src/```**  \nСоздается объект маршрутизатора с помощью функции ```createRouter()```, которая поставляется библиотекой ```vue-router```. Затем в массиве ```routes``` определяются маршруты, которые сопоставляют пути запроса и компоненты. Каждый маршрут определяет свойство ```path```, которое представляет путь запроса, и свойство ```component``` - компонент, который будет обрабатывать запрос по этому пути.\n```js\nimport {createRouter} from \"vue-router\";\nimport Tasks from \"./components/Tasks.vue\";\nimport AuthForm from \"./components/AuthForm.vue\";\n\nconst routes = [\n  {\n    path: '/',\n    component: AuthForm\n  },\n  {\n    path: '/tasks',\n    component: Tasks\n  }\n]\n\nconst router = createRouter({\n  history: createWebHistory(process.env.NODE_ENV === 'production' ? '/to-do-list/' : '/'),\n  routes\n})\n\nexport default router\n```\n\n**3. Подключение ```router.js``` в ```/src/main.js```**\n```js\nimport {createApp} from 'vue'\nimport {createPinia} from \"pinia\"\nimport router from \"./router.js\"; //!\n\nimport './style.scss'\nimport \"bootstrap/dist/css/bootstrap.min.css\";\n\nimport App from './App.vue'\n\ncreateApp(App)\n  .use(createPinia())\n  .use(router) //!\n  .mount('#app')\n```\n\n**4. Подключение ```router-view``` в ```/src/App.vue```**  \n**router-view** - отобразит компонент, соответствующий URL-адресу. Его можно разместить в любом месте, чтобы адаптировать к макету.\n```vue\n\u003ctemplate\u003e\n  \u003cdiv class=\"main-container\"\u003e\n    \u003cAuthForm v-if=\"userStore.auth === false \u0026\u0026 localStorage != null\"\u003e\u003c/AuthForm\u003e\n    \u003crouter-view v-else\u003e\u003c/router-view\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n## \u003cp id = \"implementation-software-product\"\u003eРеализация программного продукта\u003c/p\u003e\n### \u003cp id = \"authorization\"\u003eАвторизация\u003c/p\u003e\nПоля и их заполнение:\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003cb\u003eПоля\u003c/b\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cb\u003eОбязательность заполнения\u003c/b\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cb\u003eПравила заполнения\u003c/b\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eЛогин\u003c/td\u003e\n    \u003ctd\u003eДа\u003c/td\u003e\n    \u003ctd\u003etest\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eПароль\u003c/td\u003e\n    \u003ctd\u003eДа\u003c/td\u003e\n    \u003ctd\u003etest\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nСоздадим хранилище ```UserStore.js``` в папке ```/src/store/```.  \nХранилище определяется с помощью ```defineStore()``` и что для него требуется уникальное имя, передаваемое в качестве первого аргумента.\n\nВ хранилище ```UserStore.js``` указаны:\n\n1. Реактивный ref-объект ```users```, содержащий в себе свойства ```id```, ```login```, ```password``` и ```name```. Значения логина и пароля используются при авторизации;\n2. Реактивная переменная ```auth```, указывающая на состояние авторизованного пользователя;\n3. Функция ```logout()```, использующаяся для выхода авторизованного пользователя из аккаунта.\n\n```js\nimport {defineStore} from \"pinia\";\n\nexport const useUserStore = defineStore('userStore', () =\u003e {\n  const users = ref({\n    id: 1,\n    login: 'test',\n    password: 'test',\n    name: 'Екатерина'\n  })\n\n  const auth = ref(false)\n\n  const logout = () =\u003e {\n    router.push(`/`)\n    auth.value = false\n    localStorage.removeItem('users')\n  }\n\n  return {users, auth, logout}\n})\n```\n\nДалее, создадим компонент ```AuthForm.vue``` в папке ```/src/components/```  \nОпределяем хранилище, потому что хранилище не будет создано до тех пор, пока ```use...Store()``` не вызвано внутри компонента ```\u003cscript setup\u003e``` (или внутри ```setup()```).  \nВ функции ```data()```, которая возвращает объект данных для экземпляра компонента, хранятся свойства ```login``` и ```password```, которые будут использоваться в шаблоне ```template``` компонента. Также в скрипте подключаем ```useVuelidate``` для валидации форм. В ```methods``` прописываем функцию ```signIn()```, используемая для авторизации. Рассмотрим ее поподробнее.\n\nЕсли поля логина и пароля не пустые и введенные данные совпадают с данными, хранящимися в объекте ```users``` хранилища ```UserStore.js```, то информация о пользователе сохраняется в ```localStorage```.  \nОбъекты веб-хранилища ```localStorage``` позволяют хранить пары ключ/значение в браузере. Данные, которые записаны в ```localStorage```, сохраняются после перезапуска браузера.\n\nПосле идет проверка, если ```localStorage``` не пустой, то пользователя перенаправляет на страницу задач.\n\n```vue\n\u003cscript\u003e\n  import {useUserStore} from \"../store/UserStore.js\";\n\n  import useVuelidate from \"@vuelidate/core\";\n  import {required} from \"@vuelidate/validators\";\n\n  export default {\n      name: 'AuthForm',\n      setup() {\n          const userStore = useUserStore();\n          return {userStore}\n      },\n      data() {\n          return {\n              v$: useVuelidate(),\n              login: \"\",\n              password: \"\"\n          };\n      },\n      validations() {\n          return {\n              login: {required},\n              password: {required},\n          }\n      },\n      methods: {\n          signIn() {\n              if(this.login \u0026\u0026 this.password) {\n                  if (this.login === this.userStore.users.login \u0026\u0026 this.password === this.userStore.users.password) {\n                      const parsed = JSON.stringify(this.userStore.users);\n                      localStorage.setItem('users', parsed);\n\n                      if (localStorage.getItem(\"users\") != null) {\n                          this.$router.push(`/tasks`)\n                          this.userStore.auth = true\n                      }\n                  }\n              }\n          }\n      }\n  }\n\u003c/script\u003e\n\n```\n\nДалее, прописываем шаблон ```template```.\n```vue\n\u003ctemplate\u003e\n    \u003cdiv class=\"modal d-block py-5\" id=\"modalSignin\"\u003e\n        \u003cdiv class=\"modal-dialog\"\u003e\n            \u003cdiv class=\"modal-content rounded-4 shadow\"\u003e\n                \u003cdiv class=\"modal-header pb-4 border-bottom-0\"\u003e\n                    \u003ch3 class=\"fw-bold mb-0\"\u003eАвторизация\u003c/h3\u003e\n                \u003c/div\u003e\n\n                \u003cdiv class=\"modal-body pt-0\"\u003e\n                    \u003cform @submit.prevent=\"signIn\"\u003e\n                        \u003cdiv class=\"form-floating mb-3\"\u003e\n                            \u003cinput type=\"text\" v-model=\"login\" :class=\"v$.login.required.$invalid ? 'is-invalid' : ''\" class=\"form-control rounded-3\" id=\"floatingInput\"\u003e\n                            \u003clabel for=\"floatingInput\" :class=\"v$.login.required.$invalid ? 'is-invalid-label' : ''\"\u003eЛогин\u003c/label\u003e\n\n                            \u003cp v-if=\"v$.login.required.$invalid\" class=\"invalid-feedback\"\u003eВведите логин\u003c/p\u003e\n                        \u003c/div\u003e\n                        \u003cdiv class=\"form-floating mb-3\"\u003e\n                            \u003cinput type=\"password\" v-model=\"password\" :class=\"v$.password.required.$invalid ? 'is-invalid' : ''\" class=\"form-control rounded-3\" id=\"floatingPassword\"\u003e\n                            \u003clabel for=\"floatingPassword\" :class=\"v$.password.required.$invalid ? 'is-invalid-label' : ''\"\u003eПароль\u003c/label\u003e\n\n                            \u003cp v-if=\"v$.password.required.$invalid\" class=\"invalid-feedback\"\u003eВведите пароль\u003c/p\u003e\n                        \u003c/div\u003e\n                        \u003cbutton type=\"submit\" @click=\"signIn\" class=\"w-100 mb-2 btn btn-lg rounded-3\"\u003eВойти\u003c/button\u003e\n                    \u003c/form\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n\u003c/template\u003e\n```\n\nВ итоге, в компонент ```App.vue``` подключаем компонент ```AuthForm.vue``` и хранилище ```UserStore.js``` и прописываем условия в шаблоне ```template```, при которых при запуске приложения проверяется, есть ли информация о пользователе. Если данных нет, то пользователя перенапрявляет на авторизацию, если данные есть - то пользователь при перезагрузке страницы остается на странице с задачами.\n```vue\n\u003ctemplate\u003e\n  \u003cdiv class=\"main-container\"\u003e\n    \u003cAuthForm v-if=\"userStore.auth === false \u0026\u0026 localStorage != null\"\u003e\u003c/AuthForm\u003e\n    \u003crouter-view v-else\u003e\u003c/router-view\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n```js\n\u003cscript\u003e\n  import AuthForm from \"./components/AuthForm.vue\";\n  import {useUserStore} from \"./store/UserStore.js\";\n\n  export default {\n      name: 'App',\n      setup() {\n          const userStore = useUserStore();\n          return {userStore};\n      },\n      components: {\n          AuthForm\n      }\n  }\n\u003c/script\u003e\n```\n\n\u003cimg src=\"https://github.com/ketrindorofeeva/to-do-list/raw/main/for-readme/blank-authorization.png\" alt = \"Незаполненная авторизация\"/\u003e\n\n\u003cimg src=\"https://github.com/ketrindorofeeva/to-do-list/raw/main/for-readme/completed-authorization.png\" alt = \"Заполненная авторизация\"/\u003e\n\nhttps://user-images.githubusercontent.com/93386515/236624246-f6233fca-ed01-47fd-84e0-c4e4896b7cf6.mp4\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n### \u003cp id = \"tasks-page\"\u003eСтраница задач\u003c/p\u003e\nСоздадим хранилище ```TasksStore.js``` в папке ```/src/store/```.  \nХранилище определяется с помощью ```defineStore()``` и что для него требуется уникальное имя, передаваемое в качестве первого аргумента.\n\nВ хранилище ```TasksStore.js``` указаны:\n\n1. Реактивный ref-массив ```tasks```, содержащий в себе объекты со свойствами ```id```, ```description```, ```priority```, ```date_creation```, ```date_completion``` и ```folder```;  \n2. Реактивная переменная ```dialogVisible```, указывающая на статус открытия модального окна;  \n3. Реактивный ref-массив ```prioritys```, содержащий в себе объекты со свойствами ```id```, ```label``` и ```value```;  \n4. Функция ```showModal()``` для открытия модального окна;  \n5. Функция ```closeModal()``` для закрытия модального окна;  \n6. Функция ```deleteTask()``` для удаления задачи;  \n7. Функция ```watch()```, в которой информация о задачах сохраняется в ```localStorage```. При перезагрузке страницы созданные задачи не пропадут.\n\n```js\nimport {defineStore} from \"pinia\";\nimport {ref, watch} from \"vue\";\n\nexport const useTasksStore = defineStore('tasksStore', () =\u003e {\n  //Массив задач\n  const tasks = ref([\n    {\n      id: 1,\n      description: \"Размещение новостей на сайте\",\n      priority: \"Низкий\",\n      date_creation: \"22.04.2023\",\n      date_completion: \"23.06.2023\",\n      folder: 'Все задачи'\n    },\n    {\n      id: 2,\n      description: \"Внедрить Wi-fi для читателей\",\n      priority: \"Средний\",\n      date_creation: \"25.03.2023\",\n      date_completion: \"22.06.2023\",\n      folder: 'Все задачи'\n    },\n    {\n      id: 3,\n      description: \"Отредактировать раздел 'Доступная среда'\",\n      priority: \"Высокий\",\n      date_creation: \"15.03.2023\",\n      date_completion: \"21.06.2023\",\n      folder: 'Все задачи'\n    },\n    {\n      id: 4,\n      description: \"Презентация 'Информационные технологии'\",\n      priority: \"Средний\",\n      date_creation: \"15.03.2023\",\n      date_completion: \"20.06.2023\",\n      folder: 'Все задачи'\n    },\n    {\n      id: 5,\n      description: \"Счетчики - внедрить дизайн\",\n      priority: \"Средний\",\n      date_creation: \"09.03.2023\",\n      date_completion: \"10.03.2023\",\n      folder: 'Все задачи'\n    },\n    {\n      id: 6,\n      description: \"Внедрит новый layout\",\n      priority: \"Средний\",\n      date_creation: \"07.03.2023\",\n      date_completion: \"08.03.2023\",\n      folder: 'Все задачи'\n    },\n    {\n      id: 7,\n      description: \"Скролл в новостях\",\n      priority: \"Низкий\",\n      date_creation: \"01.03.2023\",\n      date_completion: \"02.03.2023\",\n      folder: 'Все задачи'\n    },\n    {\n      id: 8,\n      description: \"Форма сброса пароля\",\n      priority: \"Средний\",\n      date_creation: \"25.02.2023\",\n      date_completion: \"27.02.2023\",\n      folder: 'Все задачи'\n    },\n    {\n      id: 9,\n      description: \"Внедрение модуля Chat\",\n      priority: \"Низкий\",\n      date_creation: \"20.02.2023\",\n      date_completion: \"21.02.2023\",\n      folder: 'Все задачи'\n    }\n  ]);\n\n  //Статус открытия модального окна\n  const dialogVisible = ref(false)\n\n  //Массив приоритетов\n  const prioritys = ref([\n    {\n      id: 1,\n      label: 'Низкий',\n      value: 'low'\n    },\n    {\n      id: 2,\n      label: 'Средний',\n      value: 'medium'\n    },\n    {\n      id: 3,\n      label: 'Высокий',\n      value: 'high'\n    }\n  ])\n\n  //Показать модальное окно\n  const showModal = () =\u003e {\n    dialogVisible.value = true\n  }\n\n  //Закрыть модальное окно\n  const closeModal = () =\u003e {\n    dialogVisible.value = false\n  }\n\n  //Удалить задачу\n  const deleteTask = (id) =\u003e {\n    if (confirm(`Вы уверены, что хотите удалить данную задачу?`)) {\n      tasks.value = tasks.value.filter((el) =\u003e el.id !== id);\n    }\n  };\n\n  const tasksInLocalStorage = localStorage.getItem(\"tasks\")\n  if(tasksInLocalStorage) {\n    tasks.value = JSON.parse(tasksInLocalStorage)._value\n  }\n\n  watch(\n    () =\u003e tasks,\n    (state) =\u003e {\n      localStorage.setItem(\"tasks\", JSON.stringify(state))\n    },\n    {deep: true}\n  )\n\n  return {\n    tasks, prioritys, dialogVisible, showModal, closeModal, deleteTask\n  }\n})\n```\n\n\u003cbr\u003e\n\nСоздадим хранилище ```FoldersStore.js``` в папке ```/src/store/```. В нем будут указаны:\n\n1. Реактивный ref-массив ```folders```, содержащий в себе объекты со свойствами ```id``` и ```title```;  \n2. Реактивная переменная ```dialogVisible```, указывающая на статус открытия модального окна;  \n3. Функция ```showModal()``` для открытия модального окна;  \n4. Функция ```closeModal()``` для закрытия модального окна;  \n5. Функция ```watch()```, в которой информация о папках сохраняется в ```localStorage```. При перезагрузке страницы созданные папки не пропадут.\n\n```js\nimport {defineStore} from \"pinia\";\nimport {ref, watch} from \"vue\";\n\nexport const useFoldersStore = defineStore('foldersStore', () =\u003e {\n  const folders = ref([\n    {\n      id: 1,\n      title: 'Все задачи'\n    },\n    {\n      id: 2,\n      title: 'В процессе'\n    },\n    {\n      id: 3,\n      title: 'Выполнено'\n    }\n  ])\n\n  //Статус открытия модального окна\n  const dialogVisible = ref(false)\n\n  //Показать модальное окно\n  const showModal = () =\u003e {\n    dialogVisible.value = true\n  }\n\n  //Закрыть модальное окно\n  const closeModal = () =\u003e {\n    dialogVisible.value = false\n  }\n\n  const foldersInLocalStorage = localStorage.getItem(\"folders\")\n  if(foldersInLocalStorage) {\n    folders.value = JSON.parse(foldersInLocalStorage)._value\n  }\n\n  watch(\n    () =\u003e folders,\n    (state) =\u003e {\n      localStorage.setItem(\"folders\", JSON.stringify(state))\n    },\n    {deep: true}\n  )\n\n  return {\n    folders, dialogVisible, showModal, closeModal\n  }\n})\n```\n\n\u003cbr\u003e\n\nСоздадим компонент ```Tasks.vue``` в папке ```/src/components/```.  \nВ нем будут реализованы:\n\n1. Обращение к авторизованному пользователю;\n2. Иконка создания задачи;\n3. Иконка выхода пользователя из аккаунта.\n\nИ подключены компоненты:\n\n1. Создания задачи ```ModalFormCreateTask.vue``` в папке ```/src/components/```;\n2. Задачи ```Task.vue``` в папке ```/src/components/```\n\nВ скрипте подключаем компоненты ```Task.vue``` и ```ModalFormCreateTask``` и хранилища ```TasksStore.js``` и ```UserStore.js```.\n```js\n\u003cscript setup\u003e\n  import {useTasksStore} from \"../store/TasksStore.js\";\n  import {useUserStore} from \"../store/UserStore.js\";\n\n  import Task from \"./Task.vue\";\n  import ModalFormCreateTask from \"./ModalFormCreateTask.vue\";\n\n  const tasksStore = useTasksStore()\n  const userStore = useUserStore()\n\u003c/script\u003e\n```\n\nВ шаблоне ```template``` обращаемся к хранилищам из скрипта. Из ```UserStore.js``` достаем свойство ```name``` объекта ```users```, при клике на иконку создания задачи срабатывает функция ```showModal``` хранилища ```TasksStore.js```, при клике на иконку выхода пользователя из аккаунта срабатывает функция ```logout``` хранилища ```UserStore.js```.\n```vue\n\u003ctemplate\u003e\n  \u003cdiv class=\"d-flex justify-content-between\"\u003e\n    \u003ch2\u003e{{userStore.users.name}}, это ваш To do list\u003c/h2\u003e\n\n    \u003cdiv class=\"d-flex\"\u003e\n      \u003cdiv class=\"img-plus\" @click=\"tasksStore.showModal\"\u003e\n        \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"30\" height=\"30\" viewBox=\"0 0 24 24\" class=\"svg-plus\"\u003e\n            \u003cpath fill=\"#314b99\" d=\"M17 14h2v3h3v2h-3v3h-2v-3h-3v-2h3v-3M5 3h14c1.11 0 2 .89 2 2v7.8c-.61-.35-1.28-.6-2-.72V5H5v14h7.08c.12.72.37 1.39.72 2H5c-1.11 0-2-.89-2-2V5c0-1.11.89-2 2-2m2 4h10v2H7V7m0 4h10v1.08c-.85.14-1.63.46-2.32.92H7v-2m0 4h5v2H7v-2Z\"/\u003e\n        \u003c/svg\u003e\n      \u003c/div\u003e\n      \u003cdiv class=\"img-logout\" @click=\"userStore.logout\"\u003e\n        \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"30\" height=\"30\" viewBox=\"0 0 24 24\" class=\"svg-logout\"\u003e\n          \u003cpath fill=\"#c11d32\" d=\"M5 21q-.825 0-1.413-.588T3 19V5q0-.825.588-1.413T5 3h7v2H5v14h7v2H5Zm11-4l-1.375-1.45l2.55-2.55H9v-2h8.175l-2.55-2.55L16 7l5 5l-5 5Z\"/\u003e\n        \u003c/svg\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\n  \u003cModalFormCreateTask\u003e\u003c/ModalFormCreateTask\u003e\n\n  \u003cTask class=\"mt-4\"\u003e\u003c/Task\u003e\n\u003c/template\u003e\n```\n\n\u003cbr\u003e\n\nСоздадим компонент ```Task.vue``` в папке ```/src/components/```.  \nВ нем будут реализованы:\n\n\u003col\u003e\n  \u003cli\u003eСписок папок;\u003c/li\u003e\n  \u003cli\u003eПоле поиска задач по описанию;\u003c/li\u003e\n  \u003cli\u003eСортировка по дате завершения, приоритету, от А до Я и от Я до А;\u003c/li\u003e\n  \u003cli\u003eЗадачи. Каждая задача включает в себя:\u003c/li\u003e\n  \u003col type=\"1\"\u003e\n    \u003cli\u003eСтатус выполнения (галочка);\u003c/li\u003e\n    \u003cli\u003eОписание;\u003c/li\u003e\n    \u003cli\u003eПриоритет;\u003c/li\u003e\n    \u003cli\u003eДата создания;\u003c/li\u003e\n    \u003cli\u003eСрок выполнения;\u003c/li\u003e\n    \u003cli\u003eПапка;\u003c/li\u003e\n    \u003cli\u003eИконка редактирования задачи;\u003c/li\u003e\n    \u003cli\u003eИконка удаления задачи.\u003c/li\u003e\n  \u003c/ol\u003e\n\u003c/ol\u003e\n\nВ ```\u003cscript setup\u003e``` подключаем хранилища ```TasksStore.js``` и ```FoldersStore.js```.\n```js\n\u003cscript setup\u003e\n  import {useFoldersStore} from \"../store/FoldersStore.js\";\n  import {useTasksStore} from \"../store/TasksStore.js\";\n\n  const folderStore = useFoldersStore()\n  const taskStore = useTasksStore()\n\u003c/script\u003e\n```\n\n:exclamation: В следующих подпунктах описана поэтапная реализация структуры компонента ```Task.vue```.\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n#### \u003cp id = \"tasks-folders\"\u003eПапки с задачами\u003c/p\u003e\nВ компоненте ```Task.vue``` в скрипте подключаем хранилища ```FoldersStore.js``` и ```TasksStore.js```.  \nВ функции ```data()``` создаем свойство ```specificFolder```, отвечающее за выбор определенной папки.  \nВ ```computed``` создаем функцию ```getTasksInFolder()```, в которой прописываем связь с массивом ```tasks``` хранилища ```TasksStore.js```. Метод массива ```.filter()``` позволяет получить новый массив, отфильтровав элементы с помощью переданной колбэк-функции. Прописываем условия, в которых сказано, что, если пользователь выберет определенное значение, то в браузере выведутся задачи, в которых ```folder``` равен данному значению.\n ```js\n \u003cscript\u003e\n  import {useFoldersStore} from \"../store/FoldersStore.js\";\n  import {useTasksStore} from \"../store/TasksStore.js\";\n\n  export default {\n    name: 'Task',\n    data() {\n      return {\n        specificFolder: 'all-tasks'\n      }\n    },\n    computed: {\n      getTasksInFolder() {\n        let array = useTasksStore().tasks;\n\n        //Определенные задачи в определенных папках\n        return array.filter(task =\u003e {\n          if (this.specificFolder === 'all-tasks') {\n            return task.folder\n          }\n\n          if (this.specificFolder === 'in-process') {\n            return task.folder === 'В процессе'\n          }\n\n          if (this.specificFolder === 'done-tasks') {\n            return task.folder === 'Выполнено'\n          }\n        })\n      },\n    }\n  }\n\u003c/script\u003e\n ```\n\nhttps://user-images.githubusercontent.com/93386515/236671929-225e16d9-19b6-46c6-9920-09ba8fa34bd0.mp4\n \n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n#### \u003cp id = \"search-for-tasks-by-name\"\u003eПоиск задач по описанию\u003c/p\u003e\nВ компоненте ```Task.vue``` в ```computed``` скрипта создаем функцию ```searchDesc()```, в которой прописываем связь с функцией ```getTasksInFolder()```. Метод массива ```.filter()``` позволяет получить новый массив, отфильтровав элементы с помощью переданной колбэк-функции. Введенные данные в поле поиска сравниваются с данными из массива ```tasks```, а именно со свойством ```description```. Метод ```toLowerCase()``` возвращает значение строки, на которой он был вызван, преобразованное в нижний регистр.\n ```js\n \u003cscript\u003e\n  import {useFoldersStore} from \"../store/FoldersStore.js\";\n  import {useTasksStore} from \"../store/TasksStore.js\";\n  \n  export default {\n    name: 'Task',\n    data() {\n      return {\n        specificFolder: 'all-tasks',\n        searchDescription: '', //!\n      }\n    },\n    computed: {\n      getTasksInFolder() {\n        let array = useTasksStore().tasks;\n\n        //Определенные задачи в определенных папках\n        return array.filter(task =\u003e {\n          if (this.specificFolder === 'all-tasks') {\n            return task.folder\n          }\n\n          if (this.specificFolder === 'in-process') {\n            return task.folder === 'В процессе'\n          }\n\n          if (this.specificFolder === 'done-tasks') {\n            return task.folder === 'Выполнено'\n          }\n        })\n      },\n      searchDesc() { //!\n        //Поиск задач по названию\n        return this.getTasksInFolder.filter(task =\u003e {\n          return task.description.toLowerCase().includes(this.searchDescription.toLowerCase())\n        })\n      },\n    }\n  }\n\u003c/script\u003e\n ```\n\nhttps://user-images.githubusercontent.com/93386515/236672096-d8077c14-aa21-4243-a2e4-b4afe07c56c1.mp4\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n#### \u003cp id = \"tasks-sorting\"\u003eСортировка задач\u003c/p\u003e\nВ компоненте ```Task.vue``` в скрипте подключаем ```moment.js```.  \n**[Moment.js](https://momentjs.com/)** — это одна из самых популярных JavaScript-библиотек для разбора и форматирования дат.\n\nВ функции ```data()``` создаем свойство ```sortby```, отвечающее за выбор определенной сортировки.  \nВ ```computed``` скрипта создаем функцию ```searchAndSort()```, в которой прописываем связь с функцией ```searchDesc()```. Метод массива ```.sort()``` на месте сортирует элементы массива и возвращает отсортированный массив.\n\nСортировка задач происходит:\n- По дате завершения;\n- По приоритету;\n- От А до Я;\n- От Я до А.\n\nПри сортировке \"По дате завершения\" обращаемся к библиотеке ```moment.js```. Идет обращение к свойству ```date_completion``` массива ```tasks```, задается формат даты ```DD.MM.YY```. При данной сортировке сначала идут задачи, созданные позже предыдущих.\n\nПри сортировке \"По приоритету\" идет обращение к длине свойства ```priority``` массива ```tasks```, также используется метод ```localeCompare()```.  \n**localeCompare()** - возвращает число, указывающее, должна ли данная строка находиться до, после или в том же самом месте, что и строка, переданная через параметр, при сортировке этих строк.  \nПри данной сортировке сначала идут задачи высокого, затем среднего и только потом низкого приоритета.\n\nПри сортировке \"От А до Я\" и \"От Я до А\" идет обращение к свойству ```description``` массива ```tasks```, также используется метод ```localeCompare()```. При сортировке \"От А до Я\" задачи сначала идут по возрастанию, а при сортировке \"От Я до А\" - по убыванию.\n\n```js\n\u003cscript\u003e\n  import {useFoldersStore} from \"../store/FoldersStore.js\";\n  import {useTasksStore} from \"../store/TasksStore.js\";\n  import moment from \"moment/moment.js\";\n\n  export default {\n    name: 'Task',\n    data() {\n      return {\n        specificFolder: 'all-tasks',\n        searchDescription: '',\n        sortby: 'date', //!\n      }\n    },\n    computed: {\n      getTasksInFolder() {\n        let array = useTasksStore().tasks;\n\n        //Определенные задачи в определенных папках\n        return array.filter(task =\u003e {\n          if (this.specificFolder === 'all-tasks') {\n            return task.folder\n          }\n\n          if (this.specificFolder === 'in-process') {\n            return task.folder === 'В процессе'\n          }\n\n          if (this.specificFolder === 'done-tasks') {\n            return task.folder === 'Выполнено'\n          }\n        })\n      },\n      searchDesc() {\n        //Поиск задач по названию\n        return this.getTasksInFolder.filter(task =\u003e {\n          return task.description.toLowerCase().includes(this.searchDescription.toLowerCase())\n        })\n      },\n      searchAndSort() { //!\n        //Сортировка\n        return this.searchDesc.sort((a, b) =\u003e {\n          //По дате завершения\n          if (this.sortby === 'date') {\n            return moment(b.date_completion, 'DD.MM.YY') - moment(a.date_completion, 'DD.MM.YY')\n          }\n\n          //По приоритету\n          if (this.sortby === 'priority') {\n            return b.priority.length - a.priority.length || a.priority.localeCompare(b.priority);\n          }\n\n          //От А до Я\n          if (this.sortby === 'from_a_to_z') {\n            return a.description.localeCompare(b.description)\n          }\n\n          //От Я до А\n          if (this.sortby === 'from_z_to_a') {\n            return b.description.localeCompare(a.description)\n          }\n        })\n      }\n    }\n  }\n\u003c/script\u003e\n```\n\nhttps://user-images.githubusercontent.com/93386515/236672225-88417e0b-3735-4cd1-bf0c-faedf2ef9e9d.mp4\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n#### \u003cp id = \"final-template\"\u003eИтоговый шаблон задач\u003c/p\u003e\n```vue\n\u003ctemplate\u003e\n  \u003cdiv class=\"table-responsive\"\u003e\n    \u003cdiv class=\"d-flex align-items-center sort-select\"\u003e\n      \u003cdiv class=\"text-sort folder\"\u003eПапки:\u003c/div\u003e\n      \u003cselect v-model=\"specificFolder\" class=\"form-select\"\u003e\n        \u003coption value=\"all-tasks\"\u003eВсе задачи\u003c/option\u003e\n        \u003coption value=\"in-process\"\u003eВ процессе\u003c/option\u003e\n        \u003coption value=\"done-tasks\"\u003eВыполнено\u003c/option\u003e\n      \u003c/select\u003e\n    \u003c/div\u003e\n\n    \u003cdiv class=\"d-flex align-items-center justify-content-between mt-4\"\u003e\n      \u003cdiv class=\"d-flex align-items-center search-input\"\u003e\n        \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"30\" height=\"30\" viewBox=\"0 0 24 24\"\u003e\n          \u003cpath fill=\"#59bba6\" d=\"M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5A6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5S14 7.01 14 9.5S11.99 14 9.5 14z\"/\u003e\n        \u003c/svg\u003e\n        \u003cinput type=\"text\" @input=\"searchDescription = $event.target.value\" class=\"form-control\" placeholder=\"Поиск по описанию\"\u003e\n      \u003c/div\u003e\n\n      \u003cdiv class=\"d-flex align-items-center sort-select\"\u003e\n        \u003cdiv class=\"text-sort\"\u003eСортировать по:\u003c/div\u003e\n        \u003cselect v-model=\"sortby\" class=\"form-select\"\u003e\n          \u003coption value=\"date\"\u003eДата\u003c/option\u003e\n          \u003coption value=\"priority\"\u003eПриоритет\u003c/option\u003e\n          \u003coption value=\"from_a_to_z\"\u003eОт А до Я\u003c/option\u003e\n          \u003coption value=\"from_z_to_a\"\u003eОт Я до А\u003c/option\u003e\n        \u003c/select\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n\n    \u003ctable class=\"table table-bordered mt-4\"\u003e\n      \u003cthead\u003e\n        \u003ctr\u003e\n          \u003cth scope=\"col\"\u003e\u003c/th\u003e\n          \u003cth scope=\"col\" class=\"border-start border-2 border-secondary\"\u003eОписание\u003c/th\u003e\n          \u003cth scope=\"col\" class=\"border-start border-2 border-secondary\"\u003eПриоритет\u003c/th\u003e\n          \u003cth scope=\"col\" class=\"border-start border-2 border-secondary\"\u003eДата создания\u003c/th\u003e\n          \u003cth scope=\"col\" class=\"border-start border-2 border-secondary\"\u003eСрок выполнения\u003c/th\u003e\n          \u003cth scope=\"col\" class=\"border-start border-2 border-secondary\"\u003eПапка\u003c/th\u003e\n          \u003cth scope=\"col\" class=\"border-start border-2 border-secondary\"\u003e\u003c/th\u003e\n        \u003c/tr\u003e\n      \u003c/thead\u003e\n\n      \u003ctbody\u003e\n        \u003ctransition-group name=\"tasks-list\"\u003e\n          \u003ctr\n            v-for=\"task in searchAndSort\" :key=\"task.id\"\n            :class=\"[{active_tr: task.isDone === true}, {overdue_task: moment(task.date_completion, 'DD.MM.YYYY').isBefore(Date.now(), 'DD.MM.YYYY')}]\"\n            class=\"border-top border-2\"\n          \u003e\n            \u003ctd class=\"check-mark\"\u003e\n              \u003cdiv @click=\"task.isDone = !task.isDone\"\u003e\n                \u003csvg v-if=\"!task.isDone\" xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"40\" viewBox=\"0 0 256 256\"\u003e\n                  \u003cpath fill=\"currentColor\" d=\"M128 28a100 100 0 1 0 100 100A100.11 100.11 0 0 0 128 28Zm0 192a92 92 0 1 1 92-92a92.1 92.1 0 0 1-92 92Z\"/\u003e\n                \u003c/svg\u003e\n\n                \u003csvg v-else xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"40\" viewBox=\"0 0 256 256\"\u003e\n                  \u003cpath fill=\"#27b24a\" d=\"M172.24 99.76a6 6 0 0 1 0 8.48l-56 56a6 6 0 0 1-8.48 0l-24-24a6 6 0 0 1 8.48-8.48L112 151.51l51.76-51.75a6 6 0 0 1 8.48 0ZM230 128A102 102 0 1 1 128 26a102.12 102.12 0 0 1 102 102Zm-12 0a90 90 0 1 0-90 90a90.1 90.1 0 0 0 90-90Z\"/\u003e\n                \u003c/svg\u003e\n              \u003c/div\u003e\n            \u003c/td\u003e\n\n            \u003ctd\u003e\n              \u003cspan v-if=\"!edit\"\u003e{{task.description}}\u003c/span\u003e\n              \u003cdiv v-else\u003e\n                \u003ctextarea rows=\"4\" cols=\"30\" v-model.lazy=\"task.description\"\u003e\u003c/textarea\u003e\n              \u003c/div\u003e\n            \u003c/td\u003e\n\n            \u003ctd :class=\"[{text_blue: task.priority === 'Низкий'}, {text_yellow: task.priority === 'Средний'}, {text_red: task.priority === 'Высокий'}]\"\u003e\n              \u003cspan v-if=\"!edit\"\u003e{{task.priority}}\u003c/span\u003e\n              \u003cselect v-else v-model.lazy=\"task.priority\" class=\"form-select\"\u003e\n                \u003coption v-for=\"priority in taskStore.prioritys\" :key=\"priority.id\" :value=\"priority.label\"\u003e\n                  {{priority.label}}\n                \u003c/option\u003e\n              \u003c/select\u003e\n            \u003c/td\u003e\n\n            \u003ctd\u003e{{task.date_creation}}\u003c/td\u003e\n\n            \u003ctd\u003e\n              \u003cspan v-if=\"!edit\"\u003e{{task.date_completion}}\u003c/span\u003e\n              \u003cdiv v-else\u003e\n                \u003cinput v-model.lazy=\"task.date_completion\" class=\"form-control\"/\u003e\n              \u003c/div\u003e\n            \u003c/td\u003e\n\n            \u003ctd\u003e\n              \u003cspan v-if=\"!edit\"\u003e{{task.folder}}\u003c/span\u003e\n              \u003cselect v-else v-model.lazy=\"task.folder\" class=\"form-select\"\u003e\n                \u003coption v-for=\"folder in folderStore.folders\" :key=\"folder.id\" :value=\"folder.title\"\u003e\n                  {{folder.title}}\n                \u003c/option\u003e\n              \u003c/select\u003e\n            \u003c/td\u003e\n\n            \u003ctd class=\"d-flex justify-content-between\"\u003e\n              \u003cdiv v-if=\"!edit\" @click=\"showEdit\" class=\"edit\"\u003e\n                \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\"\u003e\n                  \u003cpath fill=\"#314b99\" d=\"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75zM20.71 5.63l-2.34-2.34a.996.996 0 0 0-1.41 0l-1.83 1.83l3.75 3.75l1.83-1.83a.996.996 0 0 0 0-1.41z\"/\u003e\n                \u003c/svg\u003e\n              \u003c/div\u003e\n              \u003cdiv v-else @click=\"hideEdit\" class=\"edit-check\"\u003e\n                \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"35\" height=\"35\" viewBox=\"0 0 24 24\"\u003e\n                  \u003cpath fill=\"#59bba6\" d=\"m10 13.6l5.9-5.9q.275-.275.7-.275t.7.275q.275.275.275.7t-.275.7l-6.6 6.6q-.3.3-.7.3t-.7-.3l-2.6-2.6q-.275-.275-.275-.7t.275-.7q.275-.275.7-.275t.7.275l1.9 1.9Z\"/\u003e\n                \u003c/svg\u003e\n              \u003c/div\u003e\n              \u003cdiv @click=\"taskStore.deleteTask(task.id)\" class=\"basket\"\u003e\n                \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 15 15\"\u003e\n                  \u003cpath fill=\"#c11d13\" d=\"m12.41 5.58l-1.34 8a.5.5 0 0 1-.49.41H4.42a.5.5 0 0 1-.49-.41l-1.34-8A.5.5 0 0 1 3.08 5h8.83a.5.5 0 0 1 .5.58zM13 3.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1 0-1H5V1.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5V3h2.5a.5.5 0 0 1 .5.5zM9 3V2H6v1h3z\"/\u003e\n                \u003c/svg\u003e\n              \u003c/div\u003e\n            \u003c/td\u003e\n          \u003c/tr\u003e\n        \u003c/transition-group\u003e\n      \u003c/tbody\u003e\n    \u003c/table\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\nhttps://user-images.githubusercontent.com/93386515/236671687-3aca15ed-b857-4aec-86a4-8c357798c230.mp4\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n#### \u003cp id = \"add-task\"\u003eДобавить задачу\u003c/p\u003e\nВ компоненте ```Tasks.vue``` указана иконка создания задачи. При клике на нее идет обращение к функции ```showModal()``` хранилища ```TasksStore.js```, которая отвечает за всплывание модального окна.\n\nИконка в шаблоне ```template``` компонента ```Tasks.vue```.\n```vue\n\u003cdiv class=\"img-plus\" @click=\"tasksStore.showModal\"\u003e\n  \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"30\" height=\"30\" viewBox=\"0 0 24 24\" class=\"svg-plus\"\u003e\n    \u003cpath fill=\"#314b99\" d=\"M17 14h2v3h3v2h-3v3h-2v-3h-3v-2h3v-3M5 3h14c1.11 0 2 .89 2 2v7.8c-.61-.35-1.28-.6-2-.72V5H5v14h7.08c.12.72.37 1.39.72 2H5c-1.11 0-2-.89-2-2V5c0-1.11.89-2 2-2m2 4h10v2H7V7m0 4h10v1.08c-.85.14-1.63.46-2.32.92H7v-2m0 4h5v2H7v-2Z\"/\u003e\n  \u003c/svg\u003e\n\u003c/div\u003e\n```\n\nСоздадим компонент ```ModalFormCreateTask.vue```.  \nВ ```\u003cscript setup\u003e``` компонента подключаем хранилище ```TasksStore.js```.\n```js\n\u003cscript setup\u003e\n  import {useTasksStore} from \"../store/TasksStore.js\";\n\n  const taskStore = useTasksStore();\n\u003c/script\u003e\n```\n\nВ функции ```data()``` создаем свойства ```id```, ```description```, ```priority```, ```date_creation```, ```date_completion```, ```isDone``` и ```folder```.  \nВ ```methods``` прописываем функцию ```createTask()```, в которой создаем объект ```newTask```, ссылающийся на свойства из ```data()```. Далее, проверяем поля ```description```, ```priority``` и ```date_completion``` на пустоту. Если они не пустые, то при помощи метода ```.push()``` добавляем объект ```newTask``` в массив ```tasks``` и закрываем модальное окно.\n\n```js\n\u003cscript\u003e\n  import {useTasksStore} from \"../store/TasksStore.js\";\n\n  import useVuelidate from \"@vuelidate/core\";\n  import {required} from \"@vuelidate/validators\";\n\n  import {vMaska} from \"maska\";\n\n  const date = new Date();\n  const day = date.getDate();\n  const month = date.getMonth() + 1;\n  const year = date.getFullYear();\n  const format = ('0' + day).slice(-2) + '.' + ('0' + month).slice(-2) + '.' + year;\n\n  export default {\n    name: \"ModalFormCreateTask\",\n    data() {\n      return {\n        v$: useVuelidate(),\n        id: Date.now(),\n        description: '',\n        priority: '',\n        date_creation: `${format}`,\n        date_completion: '',\n        isDone: false,\n        folder: 'Все задачи'\n      }\n    },\n    validations() {\n      return {\n        description: {required},\n        priority: {required},\n        date_completion: {required}\n      }\n    },\n    methods: {\n      createTask(event) {\n        const newTask = {\n          id: this.id,\n          description: this.description,\n          priority: this.priority,\n          date_creation: this.date_creation,\n          date_completion: this.date_completion,\n          isDone: this.isDone,\n          folder: this.folder\n        }\n\n        if (this.description \u0026\u0026 this.priority \u0026\u0026 this.date_completion) {\n          useTasksStore().tasks.push(newTask)\n\n          useTasksStore().dialogVisible = false;\n        }\n      }\n    }\n  }\n\u003c/script\u003e\n```\n\nШаблон ```template``` компонента ```ModalFormCreateTask.vue```.\n```vue\n\u003ctemplate\u003e\n  \u003cdiv v-if=\"taskStore.dialogVisible === true\" class=\"modal d-block py-5\" id=\"modalFormCreateTask\"\u003e\n    \u003cdiv @click.stop class=\"modal-dialog\"\u003e\n      \u003cdiv class=\"modal-content rounded-4 shadow\"\u003e\n        \u003cdiv class=\"modal-header pb-4 border-bottom-0\"\u003e\n          \u003ch3\u003eСоздать новую задачу\u003c/h3\u003e\n          \u003cbutton type=\"button\" @click=\"taskStore.closeModal\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Закрыть\"\u003e\u003c/button\u003e\n        \u003c/div\u003e\n\n        \u003cdiv class=\"modal-body pt-0\"\u003e\n          \u003cform @submit.prevent\u003e\n            \u003cdiv class=\"form-floating mb-3\"\u003e\n              \u003cinput type=\"text\" @input=\"description = $event.target.value\" :class=\"v$.description.required.$invalid ? 'is-invalid' : ''\" class=\"form-control rounded-3\" id=\"description\"\u003e\n              \u003clabel for=\"description\" :class=\"v$.description.required.$invalid ? 'is-invalid-label' : ''\" class=\"col-form-label fst-italic\"\u003e\n                Описание\n              \u003c/label\u003e\n            \u003c/div\u003e\n            \u003cdiv class=\"form-floating mb-3\"\u003e\n              \u003cselect @input=\"priority = $event.target.value\" :class=\"v$.priority.required.$invalid ? 'is-invalid' : ''\" id=\"priority\" class=\"form-select\"\u003e\n                \u003coption selected\u003eВыберите приоритет\u003c/option\u003e\n                \u003coption v-for=\"priority in taskStore.prioritys\" :key=\"priority.id\" :value=\"priority.label\"\u003e\n                  {{priority.label}}\n                \u003c/option\u003e\n              \u003c/select\u003e\n              \u003clabel for=\"priority\" :class=\"v$.priority.required.$invalid ? 'is-invalid-label' : ''\" class=\"col-form-label fst-italic\"\u003e\n                Приоритет\n              \u003c/label\u003e\n            \u003c/div\u003e\n            \u003cdiv class=\"form-floating mb-3\"\u003e\n              \u003cinput @input=\"date_completion = $event.target.value\" v-maska data-maska=\"##.##.####\" :class=\"v$.date_completion.required.$invalid ? 'is-invalid' : ''\" class=\"form-control rounded-3\" id=\"date-completion-create\"\u003e\n              \u003clabel for=\"date-completion-create\" :class=\"v$.date_completion.required.$invalid ? 'is-invalid-label' : ''\" class=\"col-form-label fst-italic\"\u003e\n                Срок выполнения (22.05.2023)\n              \u003c/label\u003e\n            \u003c/div\u003e\n\n            \u003cbutton type=\"submit\" @click=\"createTask\" class=\"btn create-new mt-4\"\u003eСоздать\u003c/button\u003e\n          \u003c/form\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\nhttps://user-images.githubusercontent.com/93386515/236677694-49a3e82a-0d57-4270-8cb6-e9bc00349a2d.mp4\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n#### \u003cp id = \"update-task\"\u003eРедактировать задачу\u003c/p\u003e\nВ скрипте компонента ```Task.vue``` в функции ```data()``` прописываем свойство ```edit```, отвечающее за доступ к редактированию задачи.  \nВ ```methods``` создаем функции ```showEdit()```, которая отвечает за открытие полей редактирования, и ```hideEdit()```, которая отвечает за закрытие полей редактирования.\n```js\n\u003cscript\u003e\n  import {useFoldersStore} from \"../store/FoldersStore.js\";\n  import {useTasksStore} from \"../store/TasksStore.js\";\n  import moment from \"moment/moment.js\";\n\n  export default {\n    name: 'Task',\n    data() {\n      return {\n        specificFolder: 'all-tasks',\n        searchDescription: '',\n        sortby: 'date',\n        edit: false //!\n      }\n    },\n    methods: {\n      showEdit() { //!\n        this.edit = true\n      },\n      hideEdit() { //!\n        this.edit = false\n      }\n    },\n    computed: {\n      getTasksInFolder() {\n        let array = useTasksStore().tasks;\n\n        //Определенные задачи в определенных папках\n        return array.filter(task =\u003e {\n          if (this.specificFolder === 'all-tasks') {\n            return task.folder\n          }\n\n          if (this.specificFolder === 'in-process') {\n            return task.folder === 'В процессе'\n          }\n\n          if (this.specificFolder === 'done-tasks') {\n            return task.folder === 'Выполнено'\n          }\n        })\n      },\n      searchDesc() {\n        //Поиск задач по названию\n        return this.getTasksInFolder.filter(task =\u003e {\n          return task.description.toLowerCase().includes(this.searchDescription.toLowerCase())\n        })\n      },\n      searchAndSort() { //!\n        //Сортировка\n        return this.searchDesc.sort((a, b) =\u003e {\n          //По дате завершения\n          if (this.sortby === 'date') {\n            return moment(b.date_completion, 'DD.MM.YY') - moment(a.date_completion, 'DD.MM.YY')\n          }\n\n          //По приоритету\n          if (this.sortby === 'priority') {\n            return b.priority.length - a.priority.length || a.priority.localeCompare(b.priority);\n          }\n\n          //От А до Я\n          if (this.sortby === 'from_a_to_z') {\n            return a.description.localeCompare(b.description)\n          }\n\n          //От Я до А\n          if (this.sortby === 'from_z_to_a') {\n            return b.description.localeCompare(a.description)\n          }\n        })\n      }\n    }\n  }\n\u003c/script\u003e\n```\n\nИконки шаблона ```template``` компонента ```Task.vue```, отвечающие за открытие и закрытие полей редактирования.\n```vue\n\u003cdiv v-if=\"!edit\" @click=\"showEdit\" class=\"edit\"\u003e\n  \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\"\u003e\n    \u003cpath fill=\"#314b99\" d=\"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75zM20.71 5.63l-2.34-2.34a.996.996 0 0 0-1.41 0l-1.83 1.83l3.75 3.75l1.83-1.83a.996.996 0 0 0 0-1.41z\"/\u003e\n  \u003c/svg\u003e\n\u003c/div\u003e\n\u003cdiv v-else @click=\"hideEdit\" class=\"edit-check\"\u003e\n  \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"35\" height=\"35\" viewBox=\"0 0 24 24\"\u003e\n    \u003cpath fill=\"#59bba6\" d=\"m10 13.6l5.9-5.9q.275-.275.7-.275t.7.275q.275.275.275.7t-.275.7l-6.6 6.6q-.3.3-.7.3t-.7-.3l-2.6-2.6q-.275-.275-.275-.7t.275-.7q.275-.275.7-.275t.7.275l1.9 1.9Z\"/\u003e\n  \u003c/svg\u003e\n\u003c/div\u003e\n```\n\nРедактирование на примере поля ```description``` в шаблоне ```template``` компонента ```Task.vue```.\n```vue\n\u003ctd\u003e\n  \u003cspan v-if=\"!edit\"\u003e{{task.description}}\u003c/span\u003e\n  \u003cdiv v-else\u003e\n    \u003ctextarea rows=\"4\" cols=\"30\" v-model.lazy=\"task.description\"\u003e\u003c/textarea\u003e\n  \u003c/div\u003e\n\u003c/td\u003e\n```\n\nhttps://user-images.githubusercontent.com/93386515/236679749-391a17de-aaa4-43cb-8a52-06c4d65d4e4e.mp4\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n\n#### \u003cp id = \"delete-task\"\u003eУдалить задачу\u003c/p\u003e\nПри клике на иконку шаблона ```template``` компонента ```Task.vue```, отвечающую за удаление задачи по ```id```, срабатывает функция ```deleteTask()``` хранилища ```TasksStore.js```. В этот момент всплывает окно подтверждения удаления задачи. Если нажать \"ОК\" задача удалится.\n\nИконка шаблона ```template``` компонента ```Task.vue```.\n```vue\n\u003cdiv @click=\"taskStore.deleteTask(task.id)\" class=\"basket\"\u003e\n  \u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 15 15\"\u003e\n    \u003cpath fill=\"#c11d13\" d=\"m12.41 5.58l-1.34 8a.5.5 0 0 1-.49.41H4.42a.5.5 0 0 1-.49-.41l-1.34-8A.5.5 0 0 1 3.08 5h8.83a.5.5 0 0 1 .5.58zM13 3.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1 0-1H5V1.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5V3h2.5a.5.5 0 0 1 .5.5zM9 3V2H6v1h3z\"/\u003e\n  \u003c/svg\u003e\n\u003c/div\u003e\n```\n\nФункция ```deleteTask()``` хранилища ```TasksStore.js```.\n```js\n//Удалить задачу\nconst deleteTask = (id) =\u003e {\n  if (confirm(`Вы уверены, что хотите удалить данную задачу?`)) {\n    tasks.value = tasks.value.filter((el) =\u003e el.id !== id);\n  }\n};\n```\n\nhttps://user-images.githubusercontent.com/93386515/236680774-a517c287-e025-41b5-861f-c72a9bc70c22.mp4\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n## \u003cp id = \"project-deployment\"\u003eРазвертывание проекта на GitHub\u003c/p\u003e\n**1. Установите ```base``` на ```/vite.config.js```.**\n```js\nimport vue from '@vitejs/plugin-vue'\n\nexport default defineConfig({\n  plugins: [vue()],\n  base:\n      process.env.NODE_ENV === \"production\"\n          ? \"/to-do-list/\"\n          : \"/\",\n})\n```\n\n**2. Удалить ```dist``` из ```/.gitignore```.**\n\n**3. Публиковать проект.**\n```js\nnpm run build\n```\n\n**4. Убедиться, что зафиксировано и нажато origin master/main.**\n\n**5. Использовать поддерево.**\n```js\ngit subtree push --prefix dist origin gh-pages\n```\n\n**6. Перейти в репозиторий проекта и открыть ```Settings \u003e Pages \u003e Build and deployment```.**  \nВ ```Branch``` выбрать ветку ```gh-pages```. Нажать \"Сохранить\".\n\n**7. Сделано.**  \nОткрыть проект в браузере.\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n\n## \u003cp id = \"project-problems\"\u003eНеполадки с проектом на GitHub\u003c/p\u003e\nПри перезагрузке страницы задач, возникает ошибка 404. На локальном сервере данной ошибки нет.\n\nhttps://user-images.githubusercontent.com/93386515/236690112-6cd2f634-4b80-4768-ae24-5d5abae3a022.mp4\n\n\u003cbr\u003e\n:bookmark_tabs: \u003ca href = \"#table-of-contents\"\u003eОглавление\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fketrindorofeeva%2Fto-do-list","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fketrindorofeeva%2Fto-do-list","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fketrindorofeeva%2Fto-do-list/lists"}