├── .env ├── .eslintrc.js ├── .gitignore ├── .storybook ├── main.js └── preview.js ├── README.md ├── README_CN.md ├── README_DE.md ├── README_ES.md ├── README_FR.md ├── README_IN.md ├── README_JP.md ├── README_RU.md ├── babel.config.json ├── jest.config.js ├── jsconfig.json ├── package.json ├── public ├── CNAME ├── description.gif ├── favicon.ico ├── github.svg ├── hn.png ├── index.html ├── logo.svg ├── logo192.png ├── logo512.png ├── manifest.json ├── readme_logo.png ├── readme_logo_cn.png ├── readme_logo_de.png ├── readme_logo_es.png ├── readme_logo_fr.png ├── readme_logo_in.png ├── readme_logo_jp.png ├── readme_logo_ru.png ├── robots.txt ├── showcase.html ├── showcase_cn.html ├── showcase_de.html ├── showcase_es.html ├── showcase_fr.html ├── showcase_in.html ├── showcase_jp.html └── showcase_ru.html ├── src ├── Introduction.stories.mdx ├── TinyManager │ ├── assets │ │ ├── locales │ │ │ ├── cn │ │ │ │ └── translations.json │ │ │ ├── de │ │ │ │ └── translations.json │ │ │ ├── en │ │ │ │ └── translations.json │ │ │ ├── es │ │ │ │ └── translations.json │ │ │ ├── fr │ │ │ │ └── translations.json │ │ │ ├── in │ │ │ │ └── translations.json │ │ │ ├── jp │ │ │ │ └── translations.json │ │ │ └── ru │ │ │ │ └── translations.json │ │ └── quotes │ │ │ └── index.js │ ├── components │ │ ├── CircularProgressWithLabel │ │ │ ├── CircularProgressWithLabel.js │ │ │ ├── CircularProgressWithLabel.stories.js │ │ │ └── index.js │ │ ├── ConfirmationDialog │ │ │ ├── ConfirmationDialog.js │ │ │ ├── ConfirmationDialog.stories.js │ │ │ └── index.js │ │ ├── IconButton │ │ │ ├── IconButton.js │ │ │ ├── IconButton.stories.js │ │ │ └── index.js │ │ ├── Loader │ │ │ ├── Loader.js │ │ │ ├── Loader.stories.js │ │ │ └── index.js │ │ ├── OutlinedSelect │ │ │ ├── OutlinedSelect.js │ │ │ └── index.js │ │ ├── Paper │ │ │ ├── Paper.js │ │ │ ├── Paper.stories.js │ │ │ └── index.js │ │ ├── ProjectCard │ │ │ ├── ProjectCard.js │ │ │ ├── ProjectCard.stories.js │ │ │ └── index.js │ │ ├── ProjectForm │ │ │ ├── ProjectForm.js │ │ │ ├── ProjectForm.stories.js │ │ │ └── index.js │ │ ├── TaskCard │ │ │ ├── TaskCard.js │ │ │ ├── TaskCard.stories.js │ │ │ └── index.js │ │ ├── TaskForm │ │ │ ├── TaskForm.js │ │ │ ├── TaskForm.stories.js │ │ │ └── index.js │ │ ├── Todo │ │ │ ├── Todo.js │ │ │ ├── Todo.stories.js │ │ │ └── index.js │ │ ├── TodoForm │ │ │ ├── TodoForm.js │ │ │ ├── TodoForm.stories.js │ │ │ └── index.js │ │ ├── TodoList │ │ │ ├── TodoList.js │ │ │ ├── TodoList.stories.js │ │ │ └── index.js │ │ ├── Topbar │ │ │ └── index.js │ │ └── utils.js │ ├── containers │ │ ├── Content │ │ │ └── index.js │ │ ├── Home │ │ │ └── index.js │ │ ├── Notes │ │ │ └── index.js │ │ ├── Projects │ │ │ ├── ProjectForm.js │ │ │ ├── ProjectList.js │ │ │ ├── ProjectView.js │ │ │ ├── TaskForm.js │ │ │ └── index.js │ │ ├── Todos │ │ │ ├── TodoFormDialog.js │ │ │ └── index.js │ │ └── Topbar │ │ │ └── index.js │ ├── decorators │ │ └── index.js │ ├── hooks │ │ └── useDialog.js │ ├── index.js │ ├── providers │ │ ├── SnackbarProvider.js │ │ ├── ThemeProvider.js │ │ └── TranslationProvider.js │ ├── services │ │ ├── DbService.js │ │ ├── DbService.spec.js │ │ ├── LocalStorageService.js │ │ ├── LocalStorageService.spec.js │ │ ├── QuoteService.js │ │ ├── QuoteService.spec.js │ │ ├── TinyManagerAPI.js │ │ └── Utils.js │ └── types │ │ └── index.js ├── __snapshots__ │ └── storybook.spec.js.snap ├── index.css ├── index.js ├── serviceWorker.js ├── setupTests.js └── storybook.spec.js └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | PORT=3050 -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | jest: true, 6 | es6: true, 7 | }, 8 | extends: [ 9 | "eslint:recommended", 10 | "plugin:react/recommended", 11 | "plugin:react-hooks/recommended", 12 | ], 13 | plugins: ["react"], 14 | parser: "@babel/eslint-parser", 15 | parserOptions: { 16 | ecmaFeatures: { 17 | jsx: true, 18 | }, 19 | }, 20 | settings: { 21 | react: { 22 | version: "detect", 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "stories": [ 3 | "../src/**/*.stories.mdx", 4 | "../src/**/*.stories.@(js|jsx|ts|tsx)" 5 | ], 6 | "addons": [ 7 | "@storybook/addon-links", 8 | "@storybook/addon-essentials", 9 | "@storybook/preset-create-react-app" 10 | ] 11 | } -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ThemeProvider from "../src/TinyManager/providers/ThemeProvider"; 3 | 4 | export const parameters = { 5 | actions: { argTypesRegex: "^on[A-Z].*" }, 6 | }; 7 | 8 | export const globalTypes = { 9 | darkMode: { 10 | name: "Dark mode", 11 | description: "Dark mode view for component", 12 | defaultValue: false, 13 | toolbar: { 14 | items: [ 15 | { icon: "circle", value: true, title: "Dark" }, 16 | { icon: "circlehollow", value: false, title: "Light" }, 17 | ], 18 | }, 19 | }, 20 | }; 21 | 22 | const withThemeProvider = (Story, context) => { 23 | const darkMode = context.globals.darkMode; 24 | return ( 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export const decorators = [withThemeProvider]; 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Tiny Manager logo

3 |

4 | 5 | # 6 | 7 |

8 | A simple offline project manager for your pet projects. 9 |

10 | 11 |

12 | Description GIF

13 |

14 | 15 | [![Current Version](https://img.shields.io/badge/version-1.0.0-green.svg)](https://nishantpainter.github.io/tiny-manager) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg?style=flat-square)](http://makeapullrequest.com) [![License](https://img.shields.io/github/license/day8/re-frame.svg)](https://github.com/nishantpainter/tiny-manager/blob/main/license.txt) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-blue.svg)](https://github.com/nishantpainter/tiny-manager/commits/master) [![GitHub issues](https://img.shields.io/github/issues/nishantpainter/tiny-manager)](https://github.com/nishantpainter/tiny-manager/issues) 16 | 17 | [English](https://github.com/nishantpainter/tiny-manager/blob/master/README.md) - [Français](https://github.com/nishantpainter/tiny-manager/blob/master/README_FR.md) - [Español](https://github.com/nishantpainter/tiny-manager/blob/master/README_ES.md) - [Pусский](https://github.com/nishantpainter/tiny-manager/blob/master/README_RU.md) - [Deutsch](https://github.com/nishantpainter/tiny-manager/blob/master/README_DE.md) - [हिंदी](https://github.com/nishantpainter/tiny-manager/blob/master/README_IN.md) - [中文](https://github.com/nishantpainter/tiny-manager/blob/master/README_CN.md) - [日本語](https://github.com/nishantpainter/tiny-manager/blob/master/README_JP.md) 18 | 19 | # Table of Contents 20 | 21 | - **[Introduction](#introduction)** 22 | - **[Features](#features)** 23 | - **[Dark Mode](#dark-mode)** 24 | - **[Localization](#localization)** 25 | - **[Offline Usage](#offline-usage)** 26 | - **[Development](#development)** 27 | - **[Privacy](#privacy)** 28 | 29 | ## Introduction 30 | 31 | Tiny Manager is an offline first simple application that assists you in managing your pet projects. Along with project management it allows mundane management using todos and a simple notepad application, all at one place. 32 | 33 | ## Features 34 | 35 | A few of the things you can do with Tiny Manager: 36 | 37 | * Add, Edit, Save Notes 38 | * Add, Edit, Delete Todos 39 | * Pet Project management 40 | * Project completion status 41 | * Filtering and sorting 42 | * Issues priority setting 43 | * Dark Mode 44 | * Localization Support 45 | * Offline capable 46 | * Offline local storage 47 | 48 | ## Dark Mode 49 | 50 | Dark mode helps you quickly turn the screen to dark ( while using during night). Toolbar offers you icon button that serve as an on/off switch for dark mode. 51 | 52 | ## Localization 53 | 54 | Tiny Manager offers local support for language including : 55 | 56 | - English 57 | - Français 58 | - Español 59 | - Pусский 60 | - Deutsch 61 | - हिंदी 62 | - 中文 63 | - 日本語 64 | 65 | ## Offline Usage 66 | 67 | The application is registered with service workers and behaves as a progressive web application ([PWA](https://en.wikipedia.org/wiki/Progressive_web_application)). For offline usage you can select the **Add To Home** option, while accessing application in browser, to install the application locally to your mobile devices. 68 | 69 | ## Development 70 | 71 | The application is scaffolded using create-react-app ([CRA](https://create-react-app.dev/docs/getting-started/)). You can clone the [Tiny Manager](https://github.com/nishantpainter/tiny-manager) repository for custom development. 72 | 73 | To run the application locally on your machine you can follow the steps below : 74 | 75 | - Using npm 76 | 77 | ``` 78 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 79 | 2: cd tiny-manager 80 | 3: npm install 81 | 4: npm run build 82 | 5: sudo npm install -g serve 83 | 6: serve -s build 84 | ``` 85 | 86 | - Using yarn 87 | 88 | ``` 89 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 90 | 2: cd tiny-manager 91 | 3: yarn install 92 | 4: yarn build 93 | 5: sudo npm install -g serve 94 | 6: serve -s build 95 | ``` 96 | 97 | ## Privacy 98 | 99 | The application makes use of local storage for persisting your work data and does not store any Tiny Manager data on any sort of server. The application uses Google analytics to get an overview of the application usage. 100 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 |

2 | Tiny Manager logo

3 |

4 | 5 | # 6 | 7 |

8 | 一个简单的离线项目管理器,适合您的宠物项目。 9 |

10 | 11 |

12 | Description GIF

13 |

14 | 15 | [![Current Version](https://img.shields.io/badge/version-1.0.0-green.svg)](https://nishantpainter.github.io/tiny-manager) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg?style=flat-square)](http://makeapullrequest.com) [![License](https://img.shields.io/github/license/day8/re-frame.svg)](https://github.com/nishantpainter/tiny-manager/blob/main/license.txt) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-blue.svg)](https://github.com/nishantpainter/tiny-manager/commits/master) [![GitHub issues](https://img.shields.io/github/issues/nishantpainter/tiny-manager)](https://github.com/nishantpainter/tiny-manager/issues) 16 | 17 | [English](https://github.com/nishantpainter/tiny-manager/blob/master/README.md) - [Français](https://github.com/nishantpainter/tiny-manager/blob/master/README_FR.md) - [Español](https://github.com/nishantpainter/tiny-manager/blob/master/README_ES.md) - [Pусский](https://github.com/nishantpainter/tiny-manager/blob/master/README_RU.md) - [Deutsch](https://github.com/nishantpainter/tiny-manager/blob/master/README_DE.md) - [हिंदी](https://github.com/nishantpainter/tiny-manager/blob/master/README_IN.md) - [中文](https://github.com/nishantpainter/tiny-manager/blob/master/README_CN.md) - [日本語](https://github.com/nishantpainter/tiny-manager/blob/master/README_JP.md) 18 | 19 | # 目录 20 | 21 | - **[介绍](#介绍)** 22 | - **[特征](#特征)** 23 | - **[深色模式](#深色模式)** 24 | - **[本土化](#本土化)** 25 | - **[离线使用](#离线使用)** 26 | - **[发展](#发展)** 27 | - **[隐私](#隐私)** 28 | 29 | ## 介绍 30 | 31 | Tiny Manager 是一款离线的第一个简单应用程序,可帮助您管理您的宠物项目。除了项目管理之外,它还允许使用待办事项和简单的记事本应用程序在一个地方进行日常管理。 32 | 33 | ## 特征 34 | 35 | 您可以使用 Tiny Manager 执行以下操作: 36 | 37 | * 添加、编辑、保存注释 38 | * 添加、编辑、删除待办事项 39 | * 宠物项目管理 40 | * 项目完成情况 41 | * 过滤和排序 42 | * 问题优先级设置 43 | * 深色模式 44 | * 本地化支持 45 | * 离线能力 46 | * 离线本地存储 47 | 48 | ## 深色模式 49 | 50 | 深色模式可帮助您快速将屏幕调暗(夜间使用时)。工具栏为您提供图标按钮,用作暗模式的开/关开关。 51 | 52 | ## 本土化 53 | 54 | Tiny Manager 提供本地语言支持,包括: 55 | 56 | - English 57 | - Français 58 | - Español 59 | - Pусский 60 | - Deutsch 61 | - हिंदी 62 | - 中文 63 | - 日本語 64 | 65 | ## 离线使用 66 | 67 | 该应用程序已向 Service Worker 注册,并表现为渐进式 Web 应用程序 ([PWA](https://en.wikipedia.org/wiki/Progressive_web_application)). 对于离线使用,您可以在浏览器中访问应用程序时选择**添加到主页**选项,以将应用程序本地安装到您的移动设备。 68 | 69 | ## 发展 70 | 71 | 该应用程序是使用 create-react-app 搭建的 ([CRA](https://create-react-app.dev/docs/getting-started/)). 您可以克隆 [Tiny Manager](https://github.com/nishantpainter/tiny-manager) 用于定制开发的存储库. 72 | 73 | 要在您的计算机上本地运行该应用程序,您可以按照以下步骤操作: 74 | 75 | - 使用 npm 76 | 77 | ``` 78 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 79 | 2: cd tiny-manager 80 | 3: npm install 81 | 4: npm run build 82 | 5: sudo npm install -g serve 83 | 6: serve -s build 84 | ``` 85 | 86 | - 使用 yarn 87 | 88 | ``` 89 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 90 | 2: cd tiny-manager 91 | 3: yarn install 92 | 4: yarn build 93 | 5: sudo npm install -g serve 94 | 6: serve -s build 95 | ``` 96 | 97 | ## 隐私 98 | 99 | 该应用程序利用本地存储来保存您的工作数据,并且不会在任何类型的服务器上存储任何 Tiny Manager 数据。该应用程序使用 Google 分析来获取应用程序使用情况的概述。 100 | -------------------------------------------------------------------------------- /README_DE.md: -------------------------------------------------------------------------------- 1 |

2 | Tiny Manager logo

3 |

4 | 5 | # 6 | 7 |

8 | Ein einfacher Offline-Projektmanager für Ihre Lieblingsprojekte. 9 |

10 | 11 |

12 | Description GIF

13 |

14 | 15 | [![Current Version](https://img.shields.io/badge/version-1.0.0-green.svg)](https://nishantpainter.github.io/tiny-manager) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg?style=flat-square)](http://makeapullrequest.com) [![License](https://img.shields.io/github/license/day8/re-frame.svg)](https://github.com/nishantpainter/tiny-manager/blob/main/license.txt) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-blue.svg)](https://github.com/nishantpainter/tiny-manager/commits/master) [![GitHub issues](https://img.shields.io/github/issues/nishantpainter/tiny-manager)](https://github.com/nishantpainter/tiny-manager/issues) 16 | 17 | [English](https://github.com/nishantpainter/tiny-manager/blob/master/README.md) - [Français](https://github.com/nishantpainter/tiny-manager/blob/master/README_FR.md) - [Español](https://github.com/nishantpainter/tiny-manager/blob/master/README_ES.md) - [Pусский](https://github.com/nishantpainter/tiny-manager/blob/master/README_RU.md) - [Deutsch](https://github.com/nishantpainter/tiny-manager/blob/master/README_DE.md) - [हिंदी](https://github.com/nishantpainter/tiny-manager/blob/master/README_IN.md) - [中文](https://github.com/nishantpainter/tiny-manager/blob/master/README_CN.md) - [日本語](https://github.com/nishantpainter/tiny-manager/blob/master/README_JP.md) 18 | 19 | # Inhaltsverzeichnis 20 | 21 | - **[Einführung](#einführung)** 22 | - **[Merkmale](#merkmale)** 23 | - **[Dunkler Modus](#dunkler-modus)** 24 | - **[Lokalisierung](#lokalisierung)** 25 | - **[Offline-Nutzung](#offline-nutzung)** 26 | - **[Entwicklung](#entwicklung)** 27 | - **[Privatsphäre](#privatsphäre)** 28 | 29 | ## Einführung 30 | 31 | Tiny Manager ist eine einfache Offline-Anwendung, die Sie bei der Verwaltung Ihrer Lieblingsprojekte unterstützt. Zusammen mit dem Projektmanagement ermöglicht es die alltägliche Verwaltung mithilfe von Todos und einer einfachen Notizblockanwendung, alles an einem Ort. 32 | 33 | ## Merkmale 34 | 35 | Einige der Dinge, die Sie mit Tiny Manager tun können: 36 | 37 | * Notizen hinzufügen, bearbeiten, speichern 38 | * Aufgaben hinzufügen, bearbeiten, löschen 39 | * Projektmanagement für Haustiere 40 | * Projektabschlussstatus 41 | * Filtern und Sortieren 42 | * Prioritätseinstellung für Probleme 43 | * Dunkler Modus 44 | * Lokalisierungsunterstützung 45 | * Offlinefähig 46 | * Lokaler Offline-Speicher 47 | 48 | ## Dunkler Modus 49 | 50 | Der Dunkelmodus hilft Ihnen, den Bildschirm schnell dunkel zu machen (während der Verwendung nachts). Die Symbolleiste bietet Ihnen eine Symbolschaltfläche, die als Ein-/Ausschalter für den Dunkelmodus dient. 51 | 52 | ## Lokalisierung 53 | 54 | Tiny Manager bietet lokale Unterstützung für Sprachen, darunter: 55 | 56 | - English 57 | - Français 58 | - Español 59 | - Pусский 60 | - Deutsch 61 | - हिंदी 62 | - 中文 63 | - 日本語 64 | 65 | ## Offline-Nutzung 66 | 67 | Die Anwendung wird bei Servicemitarbeitern registriert und verhält sich wie eine progressive Webanwendung ([PWA](https://en.wikipedia.org/wiki/Progressive_web_application)). Für die Offline-Nutzung können Sie beim Zugriff auf die Anwendung im Browser die Option **Zur Startseite hinzufügen** auswählen, um die Anwendung lokal auf Ihren Mobilgeräten zu installieren. 68 | 69 | ## Entwicklung 70 | 71 | Die Anwendung wird mithilfe von create-react-app als Gerüst erstellt ([CRA](https://create-react-app.dev/docs/getting-started/)). Sie können das klonen [Tiny Manager](https://github.com/nishantpainter/tiny-manager) repository für kundenspezifische Entwicklung. 72 | 73 | Um die Anwendung lokal auf Ihrem Computer auszuführen, können Sie die folgenden Schritte ausführen: 74 | 75 | - Verwendung von npm 76 | 77 | ``` 78 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 79 | 2: cd tiny-manager 80 | 3: npm install 81 | 4: npm run build 82 | 5: sudo npm install -g serve 83 | 6: serve -s build 84 | ``` 85 | 86 | - Verwendung von yarn 87 | 88 | ``` 89 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 90 | 2: cd tiny-manager 91 | 3: yarn install 92 | 4: yarn build 93 | 5: sudo npm install -g serve 94 | 6: serve -s build 95 | ``` 96 | 97 | ## Privatsphäre 98 | 99 | Die Anwendung nutzt den lokalen Speicher zur Beibehaltung Ihrer Arbeitsdaten und speichert keine Tiny Manager-Daten auf irgendeinem Server. Die Anwendung nutzt Google Analytics, um einen Überblick über die Anwendungsnutzung zu erhalten. 100 | -------------------------------------------------------------------------------- /README_ES.md: -------------------------------------------------------------------------------- 1 |

2 | Tiny Manager logo

3 |

4 | 5 | # 6 | 7 |

8 | Un simple administrador de proyectos fuera de línea para sus proyectos favoritos. 9 |

10 | 11 |

12 | Description GIF

13 |

14 | 15 | [![Current Version](https://img.shields.io/badge/version-1.0.0-green.svg)](https://nishantpainter.github.io/tiny-manager) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg?style=flat-square)](http://makeapullrequest.com) [![License](https://img.shields.io/github/license/day8/re-frame.svg)](https://github.com/nishantpainter/tiny-manager/blob/main/license.txt) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-blue.svg)](https://github.com/nishantpainter/tiny-manager/commits/master) [![GitHub issues](https://img.shields.io/github/issues/nishantpainter/tiny-manager)](https://github.com/nishantpainter/tiny-manager/issues) 16 | 17 | [English](https://github.com/nishantpainter/tiny-manager/blob/master/README.md) - [Français](https://github.com/nishantpainter/tiny-manager/blob/master/README_FR.md) - [Español](https://github.com/nishantpainter/tiny-manager/blob/master/README_ES.md) - [Pусский](https://github.com/nishantpainter/tiny-manager/blob/master/README_RU.md) - [Deutsch](https://github.com/nishantpainter/tiny-manager/blob/master/README_DE.md) - [हिंदी](https://github.com/nishantpainter/tiny-manager/blob/master/README_IN.md) - [中文](https://github.com/nishantpainter/tiny-manager/blob/master/README_CN.md) - [日本語](https://github.com/nishantpainter/tiny-manager/blob/master/README_JP.md) 18 | 19 | # Tabla de contenido 20 | 21 | - **[Introducción](#introducción)** 22 | - **[Características](#características)** 23 | - **[Modo oscuro](#modo-oscuro)** 24 | - **[Localización](#localización)** 25 | - **[Uso fuera de línea](#uso-fuera-de-línea)** 26 | - **[Desarrollo](#desarrollo)** 27 | - **[Privacidad](#privacidad)** 28 | 29 | ## Introducción 30 | 31 | Tiny Manager es una primera aplicación simple fuera de línea que lo ayuda a administrar sus proyectos favoritos. Junto con la gestión de proyectos, permite la gestión mundana utilizando todos y una aplicación de bloc de notas simple, todo en un solo lugar. 32 | 33 | ## Características 34 | 35 | Algunas de las cosas que puede hacer con Tiny Manager: 36 | 37 | * Agregar, editar, guardar notas 38 | * Agregar, editar, eliminar todos 39 | * Gestión de proyectos de mascotas 40 | * Estado de finalización del proyecto 41 | * Filtrado y clasificación 42 | * Establecimiento de prioridades de problemas 43 | * Modo oscuro 44 | * Soporte de localización 45 | * Capacidad sin conexión 46 | * Almacenamiento local sin conexión 47 | 48 | ## Modo oscuro 49 | 50 | El modo oscuro lo ayuda a oscurecer rápidamente la pantalla (mientras se usa durante la noche). La barra de herramientas le ofrece un botón de icono que sirve como interruptor de encendido/apagado para el modo oscuro. 51 | 52 | ## Localización 53 | 54 | Tiny Manager ofrece soporte local para idiomas que incluyen: 55 | 56 | - English 57 | - Français 58 | - Español 59 | - Pусский 60 | - Deutsch 61 | - हिंदी 62 | - 中文 63 | - 日本語 64 | 65 | ## Uso fuera de línea 66 | 67 | La aplicación está registrada con los trabajadores del servicio y se comporta como una aplicación web progresiva ([PWA](https://en.wikipedia.org/wiki/Progressive_web_application)). Para el uso sin conexión, puede seleccionar la opción **Agregar a Inicio**, mientras accede a la aplicación en el navegador, para instalar la aplicación localmente en sus dispositivos móviles. 68 | 69 | ## Desarrollo 70 | 71 | La aplicación está estructurada usando create-react-app ([CRA](https://create-react-app.dev/docs/getting-started/)). Puedes clonar el [Tiny Manager](https://github.com/nishantpainter/tiny-manager) repositorio para desarrollo personalizado. 72 | 73 | Para ejecutar la aplicación localmente en su máquina, puede seguir los pasos a continuación: 74 | 75 | - Usando npm 76 | 77 | ``` 78 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 79 | 2: cd tiny-manager 80 | 3: npm install 81 | 4: npm run build 82 | 5: sudo npm install -g serve 83 | 6: serve -s build 84 | ``` 85 | 86 | - Usando yarn 87 | 88 | ``` 89 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 90 | 2: cd tiny-manager 91 | 3: yarn install 92 | 4: yarn build 93 | 5: sudo npm install -g serve 94 | 6: serve -s build 95 | ``` 96 | 97 | ## Privacidad 98 | 99 | La aplicación utiliza el almacenamiento local para conservar los datos de su trabajo y no almacena ningún dato de Tiny Manager en ningún tipo de servidor. La aplicación utiliza Google Analytics para obtener una visión general del uso de la aplicación. -------------------------------------------------------------------------------- /README_FR.md: -------------------------------------------------------------------------------- 1 |

2 | Tiny Manager logo

3 |

4 | 5 | # 6 | 7 |

8 | Un gestionnaire de projet hors ligne simple pour vos projets favoris. 9 |

10 | 11 |

12 | Description GIF

13 |

14 | 15 | [![Current Version](https://img.shields.io/badge/version-1.0.0-green.svg)](https://nishantpainter.github.io/tiny-manager) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg?style=flat-square)](http://makeapullrequest.com) [![License](https://img.shields.io/github/license/day8/re-frame.svg)](https://github.com/nishantpainter/tiny-manager/blob/main/license.txt) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-blue.svg)](https://github.com/nishantpainter/tiny-manager/commits/master) [![GitHub issues](https://img.shields.io/github/issues/nishantpainter/tiny-manager)](https://github.com/nishantpainter/tiny-manager/issues) 16 | 17 | [English](https://github.com/nishantpainter/tiny-manager/blob/master/README.md) - [Français](https://github.com/nishantpainter/tiny-manager/blob/master/README_FR.md) - [Español](https://github.com/nishantpainter/tiny-manager/blob/master/README_ES.md) - [Pусский](https://github.com/nishantpainter/tiny-manager/blob/master/README_RU.md) - [Deutsch](https://github.com/nishantpainter/tiny-manager/blob/master/README_DE.md) - [हिंदी](https://github.com/nishantpainter/tiny-manager/blob/master/README_IN.md) - [中文](https://github.com/nishantpainter/tiny-manager/blob/master/README_CN.md) - [日本語](https://github.com/nishantpainter/tiny-manager/blob/master/README_JP.md) 18 | 19 | # Table des matières 20 | 21 | - **[Introduction](#introduction)** 22 | - **[Caractéristiques](#caractéristiques)** 23 | - **[Mode sombre](#mode-sombre)** 24 | - **[Localisation](#localisation)** 25 | - **[Utilisation hors ligne](#utilisation-hors-ligne)** 26 | - **[Développement](#développement)** 27 | - **[Confidentialité](#confidentialité)** 28 | 29 | ## Introduction 30 | 31 | Tiny Manager est une première application simple hors ligne qui vous aide à gérer vos projets favoris. Parallèlement à la gestion de projet, il permet une gestion banale à l'aide de tâches et d'une simple application de bloc-notes, le tout au même endroit. 32 | 33 | ## Caractéristiques 34 | 35 | A few of the things you can do with Tiny Manager: 36 | 37 | * Ajouter, modifier, enregistrer des notes 38 | * Ajouter, modifier, supprimer des tâches 39 | * Gestion de projets pour animaux de compagnie 40 | * Statut d'achèvement du projet 41 | * Filtrage et tri 42 | * Établit la priorité des problèmes 43 | * Mode sombre 44 | * Prise en charge de la localisation 45 | * Capable hors ligne 46 | * Stockage local hors ligne 47 | 48 | ## Mode sombre 49 | 50 | Le mode sombre vous aide à rendre rapidement l'écran sombre (lors de l'utilisation pendant la nuit). La barre d'outils vous offre un bouton d'icône qui sert d'interrupteur marche/arrêt pour le mode sombre. 51 | 52 | ## Localisation 53 | 54 | Tiny Manager offre un support local pour la langue, notamment : 55 | 56 | - English 57 | - Français 58 | - Español 59 | - Pусский 60 | - Deutsch 61 | - हिंदी 62 | - 中文 63 | - 日本語 64 | 65 | ## Utilisation hors ligne 66 | 67 | L'application est enregistrée auprès des service workers et se comporte comme une application web progressive ([PWA](https://en.wikipedia.org/wiki/Progressive_web_application)). Pour une utilisation hors ligne, vous pouvez sélectionner l'option ** Ajouter à la maison **, tout en accédant à l'application dans le navigateur, pour installer l'application localement sur vos appareils mobiles. 68 | 69 | ## Développement 70 | 71 | L'application est échafaudée à l'aide de create-react-app ([CRA](https://create-react-app.dev/docs/getting-started/)). Vous pouvez cloner le [Tiny Manager](https://github.com/nishantpainter/tiny-manager) référentiel pour le développement personnalisé. 72 | 73 | Pour exécuter l'application localement sur votre machine vous pouvez suivre les étapes ci-dessous : 74 | 75 | - Utiliser npm 76 | 77 | ``` 78 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 79 | 2: cd tiny-manager 80 | 3: npm install 81 | 4: npm run build 82 | 5: sudo npm install -g serve 83 | 6: serve -s build 84 | ``` 85 | 86 | - Utiliser yarn 87 | 88 | ``` 89 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 90 | 2: cd tiny-manager 91 | 3: yarn install 92 | 4: yarn build 93 | 5: sudo npm install -g serve 94 | 6: serve -s build 95 | ``` 96 | 97 | ## Confidentialité 98 | 99 | L'application utilise le stockage local pour conserver vos données de travail et ne stocke aucune donnée Tiny Manager sur aucun type de serveur. L'application utilise Google Analytics pour obtenir un aperçu de l'utilisation de l'application. -------------------------------------------------------------------------------- /README_IN.md: -------------------------------------------------------------------------------- 1 |

2 | Tiny Manager logo

3 |

4 | 5 | # 6 | 7 |

8 | आपके पसंदीदा प्रोजेक्ट के लिए एक सरल ऑफ़लाइन प्रोजेक्ट प्रबंधक। 9 |

10 | 11 |

12 | Description GIF

13 |

14 | 15 | [![Current Version](https://img.shields.io/badge/version-1.0.0-green.svg)](https://nishantpainter.github.io/tiny-manager) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg?style=flat-square)](http://makeapullrequest.com) [![License](https://img.shields.io/github/license/day8/re-frame.svg)](https://github.com/nishantpainter/tiny-manager/blob/main/license.txt) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-blue.svg)](https://github.com/nishantpainter/tiny-manager/commits/master) [![GitHub issues](https://img.shields.io/github/issues/nishantpainter/tiny-manager)](https://github.com/nishantpainter/tiny-manager/issues) 16 | 17 | [English](https://github.com/nishantpainter/tiny-manager/blob/master/README.md) - [Français](https://github.com/nishantpainter/tiny-manager/blob/master/README_FR.md) - [Español](https://github.com/nishantpainter/tiny-manager/blob/master/README_ES.md) - [Pусский](https://github.com/nishantpainter/tiny-manager/blob/master/README_RU.md) - [Deutsch](https://github.com/nishantpainter/tiny-manager/blob/master/README_DE.md) - [हिंदी](https://github.com/nishantpainter/tiny-manager/blob/master/README_IN.md) - [中文](https://github.com/nishantpainter/tiny-manager/blob/master/README_CN.md) - [日本語](https://github.com/nishantpainter/tiny-manager/blob/master/README_JP.md) 18 | 19 | # विषयसूची 20 | 21 | - **[परिचय](#परिचय)** 22 | - **[विशेषताएँ](#विशेषताएँ)** 23 | - **[डार्क मोड](#डार्क-मोड)** 24 | - **[स्थानीयकरण](#स्थानीयकरण)** 25 | - **[ऑफ़लाइन उपयोग](#ऑफ़लाइन-उपयोग)** 26 | - **[विकास](#विकास)** 27 | - **[गोपनीयता](#गोपनीयता)** 28 | 29 | ## परिचय 30 | 31 | Tiny Manager एक ऑफ़लाइन पहला सरल एप्लिकेशन है जो आपकी पसंदीदा परियोजनाओं को प्रबंधित करने में आपकी सहायता करता है। परियोजना प्रबंधन के साथ-साथ यह एक ही स्थान पर कार्य और एक साधारण नोटपैड एप्लिकेशन का उपयोग करके सांसारिक प्रबंधन की अनुमति देता है। 32 | 33 | ## विशेषताएँ 34 | 35 | कुछ चीजें जो आप Tiny Manager के साथ कर सकते हैं: 36 | 37 | * नोट्स जोड़ें, संपादित करें, सहेजें 38 | * टोडो जोड़ें, संपादित करें, हटाएं 39 | * पालतू परियोजना प्रबंधन 40 | * परियोजना पूर्ण होने की स्थिति 41 | * फ़िल्टरिंग और सॉर्टिंग 42 | * प्राथमिकता सेटिंग जारी करता है 43 | * डार्क मोड 44 | * स्थानीयकरण समर्थन 45 | * ऑफ़लाइन सक्षम 46 | * ऑफ़लाइन स्थानीय भंडारण 47 | 48 | ## डार्क मोड 49 | 50 | डार्क मोड आपको स्क्रीन को जल्दी से डार्क करने में मदद करता है (रात के दौरान उपयोग करते समय)। टूलबार आपको आइकन बटन प्रदान करता है जो डार्क मोड के लिए ऑन/ऑफ स्विच के रूप में काम करता है। 51 | ## स्थानीयकरण 52 | 53 | Tiny Manager भाषा के लिए स्थानीय समर्थन प्रदान करता है जिसमें शामिल हैं: 54 | 55 | - English 56 | - Français 57 | - Español 58 | - Pусский 59 | - Deutsch 60 | - हिंदी 61 | - 中文 62 | - 日本語 63 | 64 | ## ऑफ़लाइन-उपयोग 65 | 66 | एप्लिकेशन सेवा कर्मियों के साथ पंजीकृत है और एक प्रगतिशील वेब एप्लिकेशन के रूप में व्यवहार करता है ([PWA](https://en.wikipedia.org/wiki/Progressive_web_application)). ऑफ़लाइन उपयोग के लिए आप अपने मोबाइल उपकरणों पर स्थानीय रूप से एप्लिकेशन इंस्टॉल करने के लिए ब्राउज़र में एप्लिकेशन एक्सेस करते समय **होम में जोड़ें** विकल्प का चयन कर सकते हैं। 67 | 68 | ## विकास 69 | 70 | एप्लिकेशन को create-react-app का उपयोग करके तैयार किया गया है ([CRA](https://create-react-app.dev/docs/getting-started/)). आप क्लोन कर सकते हैं [Tiny Manager](https://github.com/nishantpainter/tiny-manager) कस्टम विकास के लिए भंडार। 71 | 72 | अपनी मशीन पर एप्लिकेशन को स्थानीय रूप से चलाने के लिए आप नीचे दिए गए चरणों का पालन कर सकते हैं: 73 | 74 | - npm का उपयोग करना 75 | 76 | ``` 77 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 78 | 2: cd tiny-manager 79 | 3: npm install 80 | 4: npm run build 81 | 5: sudo npm install -g serve 82 | 6: serve -s build 83 | ``` 84 | 85 | - yarn का उपयोग करना 86 | 87 | ``` 88 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 89 | 2: cd tiny-manager 90 | 3: yarn install 91 | 4: yarn build 92 | 5: sudo npm install -g serve 93 | 6: serve -s build 94 | ``` 95 | 96 | ## गोपनीयता 97 | 98 | एप्लिकेशन आपके कार्य डेटा को बनाए रखने के लिए स्थानीय भंडारण का उपयोग करता है और किसी भी प्रकार के सर्वर पर किसी भी Tiny Manager डेटा को संग्रहीत नहीं करता है। एप्लिकेशन उपयोग का अवलोकन प्राप्त करने के लिए एप्लिकेशन Google एनालिटिक्स का उपयोग करता है। 99 | -------------------------------------------------------------------------------- /README_JP.md: -------------------------------------------------------------------------------- 1 |

2 | Tiny Manager logo

3 |

4 | 5 | # 6 | 7 |

8 | あなたの大切なプロジェクトのためのシンプルなオフラインプロジェクトマネージャー。 9 |

10 | 11 |

12 | Description GIF

13 |

14 | 15 | [![Current Version](https://img.shields.io/badge/version-1.0.0-green.svg)](https://nishantpainter.github.io/tiny-manager) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg?style=flat-square)](http://makeapullrequest.com) [![License](https://img.shields.io/github/license/day8/re-frame.svg)](https://github.com/nishantpainter/tiny-manager/blob/main/license.txt) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-blue.svg)](https://github.com/nishantpainter/tiny-manager/commits/master) [![GitHub issues](https://img.shields.io/github/issues/nishantpainter/tiny-manager)](https://github.com/nishantpainter/tiny-manager/issues) 16 | 17 | [English](https://github.com/nishantpainter/tiny-manager/blob/master/README.md) - [Français](https://github.com/nishantpainter/tiny-manager/blob/master/README_FR.md) - [Español](https://github.com/nishantpainter/tiny-manager/blob/master/README_ES.md) - [Pусский](https://github.com/nishantpainter/tiny-manager/blob/master/README_RU.md) - [Deutsch](https://github.com/nishantpainter/tiny-manager/blob/master/README_DE.md) - [हिंदी](https://github.com/nishantpainter/tiny-manager/blob/master/README_IN.md) - [中文](https://github.com/nishantpainter/tiny-manager/blob/master/README_CN.md) - [日本語](https://github.com/nishantpainter/tiny-manager/blob/master/README_JP.md) 18 | 19 | # 目次 20 | 21 | - **[序章](#序章)** 22 | - **[特徴](#特徴)** 23 | - **[ダークモード](#ダークモード)** 24 | - **[ローカリゼーション](#ローカリゼーション)** 25 | - **[オフラインでの使用](#オフラインでの使用)** 26 | - **[発達](#発達)** 27 | - **[プライバシー](#プライバシー)** 28 | 29 | ## 序章 30 | 31 | Tiny Manager は、ペット プロジェクトの管理を支援する、オフライン初のシンプルなアプリケーションです。プロジェクト管理に加えて、Todo とシンプルなメモ帳アプリケーションを使用した日常的な管理をすべて 1 か所で行うことができます。 32 | 33 | ## 特徴 34 | 35 | Tiny Manager でできることのいくつかは次のとおりです。 36 | 37 | * メモの追加、編集、保存 38 | * Todoの追加、編集、削除 39 | * ペットプロジェクト管理 40 | ※プロジェクトの完了状況 41 | * フィルタリングと並べ替え 42 | * 優先順位の設定を発行します 43 | * ダークモード 44 | * ローカリゼーションのサポート 45 | * オフライン対応 46 | * オフラインのローカルストレージ 47 | 48 | ## ダークモード 49 | 50 | ダーク モードを使用すると、(夜間の使用中に) 画面をすばやく暗くすることができます。ツールバーには、ダーク モードのオン/オフ スイッチとして機能するアイコン ボタンが表示されます。 51 | 52 | ## ローカリゼーション 53 | 54 | Tiny Manager は、次のような言語のローカル サポートを提供します。 55 | 56 | - English 57 | - Français 58 | - Español 59 | - Pусский 60 | - Deutsch 61 | - हिंदी 62 | - 中文 63 | - 日本語 64 | 65 | ## オフラインでの使用 66 | 67 | アプリケーションは Service Worker に登録され、プログレッシブ Web アプリケーションとして動作します。 ([PWA](https://en.wikipedia.org/wiki/Progressive_web_application)). オフラインで使用する場合は、ブラウザでアプリケーションにアクセスしているときに **ホームに追加** オプションを選択して、アプリケーションをモバイル デバイスにローカルにインストールできます。 68 | 69 | ## 発達 70 | 71 | アプリケーションは create-react-app を使用してスキャフォールディングされます ([CRA](https://create-react-app.dev/docs/getting-started/)). クローンを作成できます [Tiny Manager](https://github.com/nishantpainter/tiny-manager) カスタム開発用のリポジトリ. 72 | 73 | アプリケーションをマシン上でローカルに実行するには、次の手順に従います。 74 | 75 | - 使用する npm 76 | 77 | ``` 78 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 79 | 2: cd tiny-manager 80 | 3: npm install 81 | 4: npm run build 82 | 5: sudo npm install -g serve 83 | 6: serve -s build 84 | ``` 85 | 86 | - 使用する yarn 87 | 88 | ``` 89 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 90 | 2: cd tiny-manager 91 | 3: yarn install 92 | 4: yarn build 93 | 5: sudo npm install -g serve 94 | 6: serve -s build 95 | ``` 96 | 97 | ## プライバシー 98 | 99 | このアプリケーションは、作業データを永続化するためにローカル ストレージを利用し、いかなる種類のサーバーにも Tiny Manager データを保存しません。アプリケーションは Google アナリティクスを使用して、アプリケーションの使用状況の概要を取得します。 100 | -------------------------------------------------------------------------------- /README_RU.md: -------------------------------------------------------------------------------- 1 |

2 | Tiny Manager logo

3 |

4 | 5 | # 6 | 7 |

8 | Простой автономный менеджер проектов для ваших любимых проектов. 9 |

10 | 11 |

12 | Description GIF

13 |

14 | 15 | [![Current Version](https://img.shields.io/badge/version-1.0.0-green.svg)](https://nishantpainter.github.io/tiny-manager) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-orange.svg?style=flat-square)](http://makeapullrequest.com) [![License](https://img.shields.io/github/license/day8/re-frame.svg)](https://github.com/nishantpainter/tiny-manager/blob/main/license.txt) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-blue.svg)](https://github.com/nishantpainter/tiny-manager/commits/master) [![GitHub issues](https://img.shields.io/github/issues/nishantpainter/tiny-manager)](https://github.com/nishantpainter/tiny-manager/issues) 16 | 17 | [English](https://github.com/nishantpainter/tiny-manager/blob/master/README.md) - [Français](https://github.com/nishantpainter/tiny-manager/blob/master/README_FR.md) - [Español](https://github.com/nishantpainter/tiny-manager/blob/master/README_ES.md) - [Pусский](https://github.com/nishantpainter/tiny-manager/blob/master/README_RU.md) - [Deutsch](https://github.com/nishantpainter/tiny-manager/blob/master/README_DE.md) - [हिंदी](https://github.com/nishantpainter/tiny-manager/blob/master/README_IN.md) - [中文](https://github.com/nishantpainter/tiny-manager/blob/master/README_CN.md) - [日本語](https://github.com/nishantpainter/tiny-manager/blob/master/README_JP.md) 18 | 19 | # Оглавление 20 | 21 | - **[Введение](#введение)** 22 | - **[Функции](#функции)** 23 | - **[Темный режим](#темный-режим)** 24 | - **[Локализация](#локализация)** 25 | - **[Использование в автономном режиме](#использование-в-автономном-режиме)** 26 | - **[Разработка](#разработка)** 27 | - **[Конфиденциальность](#конфиденциальность)** 28 | 29 | ## Введение 30 | 31 | Tiny Manager — это первое простое автономное приложение, которое помогает вам управлять вашими любимыми проектами. Наряду с управлением проектами он позволяет управлять рутинными задачами с помощью списка задач и простого приложения «Блокнот» — и все это в одном месте. 32 | 33 | ## Функции 34 | 35 | Несколько вещей, которые вы можете сделать с Tiny Manager: 36 | 37 | * Добавлять, редактировать, сохранять заметки 38 | * Добавить, изменить, удалить Todos 39 | * Управление домашними животными 40 | * Статус завершения проекта 41 | * Фильтрация и сортировка 42 | * Проблемы с установкой приоритета 43 | * Темный режим 44 | * Поддержка локализации 45 | * Автономный режим 46 | * Оффлайн локальное хранилище 47 | 48 | ## Темный режим 49 | 50 | Темный режим помогает быстро сделать экран темным (при использовании ночью). Панель инструментов предлагает вам кнопку со значком, которая служит переключателем для темного режима. 51 | 52 | ## Локализация 53 | 54 | Tiny Manager предлагает локальную поддержку языка, включая: 55 | 56 | - English 57 | - Français 58 | - Español 59 | - Pусский 60 | - Deutsch 61 | - हिंदी 62 | - 中文 63 | - 日本語 64 | 65 | ## Использование в автономном режиме 66 | 67 | Приложение зарегистрировано сервис-воркерами и ведет себя как прогрессивное веб-приложение. ([PWA](https://en.wikipedia.org/wiki/Progressive_web_application)). Для автономного использования вы можете выбрать опцию **Добавить на главную** при доступе к приложению в браузере, чтобы установить приложение локально на свои мобильные устройства. 68 | 69 | ## Разработка 70 | 71 | Приложение создается с помощью create-реагировать-приложение ([CRA](https://create-react-app.dev/docs/getting-started/)). Вы можете клонировать [Tiny Manager](https://github.com/nishantpainter/tiny-manager) репозиторий для пользовательской разработки. 72 | 73 | Чтобы запустить приложение локально на вашем компьютере, вы можете выполнить следующие шаги: 74 | 75 | - С использованием npm 76 | 77 | ``` 78 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 79 | 2: cd tiny-manager 80 | 3: npm install 81 | 4: npm run build 82 | 5: sudo npm install -g serve 83 | 6: serve -s build 84 | ``` 85 | 86 | - С использованием yarn 87 | 88 | ``` 89 | 1: git clone https://github.com/nishantpainter/tiny-manager.git 90 | 2: cd tiny-manager 91 | 3: yarn install 92 | 4: yarn build 93 | 5: sudo npm install -g serve 94 | 6: serve -s build 95 | ``` 96 | 97 | ## конфиденциальность 98 | 99 | Приложение использует локальное хранилище для сохранения ваших рабочих данных и не хранит данные Tiny Manager на каком-либо сервере. Приложение использует аналитику Google, чтобы получить обзор использования приложения. -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/env", "@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rootDir: "src", 3 | moduleFileExtensions: ["js", "json"], 4 | moduleDirectories: ["node_modules", ""], 5 | }; 6 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src" 4 | }, 5 | "include": ["src"] 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiny-manager-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.0", 7 | "@material-ui/icons": "^4.9.1", 8 | "@testing-library/jest-dom": "^4.2.4", 9 | "@testing-library/react": "^9.3.2", 10 | "@testing-library/user-event": "^7.1.2", 11 | "dexie": "^3.0.2", 12 | "formik": "^2.1.5", 13 | "i18next": "^20.2.2", 14 | "jspdf": "^2.3.1", 15 | "lodash.debounce": "^4.0.8", 16 | "moment": "^2.29.1", 17 | "notistack": "^1.0.7", 18 | "react": "^16.13.1", 19 | "react-dom": "^16.13.1", 20 | "react-i18next": "^11.8.14", 21 | "react-router-dom": "^5.2.0", 22 | "react-scripts": "3.4.3" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "jest --watchAll", 28 | "eject": "react-scripts eject", 29 | "storybook": "start-storybook -p 6006 -s public", 30 | "build-storybook": "build-storybook -s public", 31 | "lint": "eslint src/TinyManager/**", 32 | "predeploy": "npm run build", 33 | "deploy": "gh-pages -d build" 34 | }, 35 | "eslintConfig": { 36 | "extends": "react-app" 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | }, 50 | "homepage": ".", 51 | "devDependencies": { 52 | "@babel/core": "^7.11.5", 53 | "@babel/eslint-parser": "^7.11.5", 54 | "@babel/preset-env": "^7.11.5", 55 | "@storybook/addon-actions": "^6.0.20", 56 | "@storybook/addon-essentials": "^6.0.20", 57 | "@storybook/addon-links": "^6.0.20", 58 | "@storybook/addon-storyshots": "^6.0.21", 59 | "@storybook/node-logger": "^6.0.20", 60 | "@storybook/preset-create-react-app": "^3.1.4", 61 | "@storybook/react": "^6.0.20", 62 | "babel-jest": "24.9.0", 63 | "babel-loader": "^8.1.0", 64 | "eslint": "^6.6.0", 65 | "eslint-plugin-react": "^7.20.6", 66 | "eslint-plugin-react-hooks": "^4.1.2", 67 | "faker": "^4.1.0", 68 | "gh-pages": "^3.1.0", 69 | "prettier": "^2.1.1", 70 | "react-is": "^16.13.1", 71 | "react-test-renderer": "^16.13.1", 72 | "uuid": "^8.3.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | tinymanager.js.org -------------------------------------------------------------------------------- /public/description.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/description.gif -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/favicon.ico -------------------------------------------------------------------------------- /public/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/hn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/hn.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 31 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 49 | 53 | 57 | 58 | 59 | 60 | Tiny Manager 61 | 65 | 66 | 67 | 71 | 80 | 81 | 82 | 83 |
84 | 85 | 86 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Created with Fabric.js 5.3.0 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Tiny Manager", 3 | "name": "Tiny Manager", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "32x32", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#3f51b5", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/readme_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/readme_logo.png -------------------------------------------------------------------------------- /public/readme_logo_cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/readme_logo_cn.png -------------------------------------------------------------------------------- /public/readme_logo_de.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/readme_logo_de.png -------------------------------------------------------------------------------- /public/readme_logo_es.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/readme_logo_es.png -------------------------------------------------------------------------------- /public/readme_logo_fr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/readme_logo_fr.png -------------------------------------------------------------------------------- /public/readme_logo_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/readme_logo_in.png -------------------------------------------------------------------------------- /public/readme_logo_jp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/readme_logo_jp.png -------------------------------------------------------------------------------- /public/readme_logo_ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishantpainter/tiny-manager/a46a028bce72ef3998e7f5039703495a20793c2f/public/readme_logo_ru.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/showcase_cn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 小经理 7 | 8 | 9 | 10 | 11 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 46 | 50 | 51 | 52 | 56 | 65 | 66 | 70 | 231 | 232 | 233 | 250 |
251 | 252 |

253 | English -  255 | Français -  257 | Español -  259 | Pусский -  261 | Deutsch -  263 | हिंदी -  265 | 中文 -  267 | 日本語 268 |

269 | 270 | 271 |

272 | 一个简单的离线项目管理器,适合您的宠物项目。 273 |

274 | 275 |

276 | 277 | 小经理 283 | 是一个离线的第一个简单的应用程序,可以帮助您管理您的 284 | 宠物项目。除了项目管理之外,它还可以进行日常管理 285 | 使用待办事项和简单的记事本应用程序,全部集中在一处。 286 |

287 |

捆绑了基本功能,可以快速轻松地管理 你的项目。

288 | 立即开始使用 294 |

295 |
296 | 306 | 315 | 320 |
321 | Star 328 | 329 |
330 | 333 |
334 |

335 |
336 |

特征

337 |

您可以使用 小经理 执行以下操作:

338 |
    339 |
  • 添加、编辑、保存笔记
  • 340 |
  • 添加、编辑、删除待办事项
  • 341 |
  • 宠物项目管理
  • 342 |
  • 项目完成情况
  • 343 |
  • 过滤和排序
  • 344 |
  • 问题优先级设置
  • 345 |
  • 深色模式
  • 346 |
  • 本地化支持
  • 347 |
  • 离线功能
  • 348 |
  • 离线本地存储
  • 349 |
350 |
351 |
352 |

隐私和使用

353 |

354 | 该应用程序利用本地存储来保存您的工作 355 | 数据,并且不会在任何类型的服务器上存储任何 小经理 数据。 356 | 该应用程序使用 Google 分析来获取 应用程序的使用。 357 |

358 |
359 |
360 |

反馈或建议

361 |

请提交您对任何改进、错误的反馈或建议 并修复。

362 | 现在提交 369 |
370 |
371 | 380 | 381 | 382 | -------------------------------------------------------------------------------- /public/showcase_jp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 小さなマネージャー 7 | 8 | 9 | 13 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 36 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 54 | 58 | 59 | 60 | 64 | 73 | 74 | 78 | 239 | 240 | 241 | 258 |
259 | 260 |

261 | English -  263 | Français -  265 | Español -  267 | Pусский -  269 | Deutsch -  271 | हिंदी -  273 | 中文 -  275 | 日本語 276 |

277 | 278 | 279 |

280 | あなたの大切なプロジェクトのためのシンプルなオフラインプロジェクトマネージャー。 282 | 283 |

284 | 285 |

286 | 287 | 小さなマネージャー 293 | は、オフラインでの最初のシンプルなアプリケーションであり、 294 | ペットプロジェクト。プロジェクト管理と併せて日常的な管理も可能にします 295 | Todo とシンプルなメモ帳アプリケーションをすべて 1 か所で使用できます。 296 |

297 |

298 | 重要な機能がバンドルされているため、迅速かつ簡単に管理できます。 299 | あなたのプロジェクト。 300 |

301 | 今すぐ使い始める 307 |

308 |
309 | 319 | 328 | 333 |
334 | Star 341 | 342 |
343 | 346 |
347 |

348 |
349 |

特徴

350 |

小さなマネージャーでできることのいくつか:

351 |
    352 |
  • メモの追加、編集、保存
  • 353 |
  • Todoの追加、編集、削除
  • 354 |
  • ペットプロジェクト管理
  • 355 |
  • プロジェクトの完了状況
  • 356 |
  • フィルタリングと並べ替え
  • 357 |
  • 課題の優先度設定
  • 358 |
  • ダークモード
  • 359 |
  • ローカリゼーションのサポート
  • 360 |
  • オフライン対応
  • 361 |
  • オフラインのローカルストレージ
  • 362 |
363 |
364 |
365 |

プライバシーと使用法

366 |

367 | アプリケーションは作業内容を保存するためにローカル 368 | ストレージを利用します。 369 | データを保存し、いかなる種類の小さなマネージャー 370 | データも保存しません。 サーバ。このアプリケーションは Google 371 | アナリティクスを使用して、 アプリケーションの使用状況。 372 |

373 |
374 |
375 |

フィードバックまたは提案

376 |

377 | 改善やバグについてのフィードバックや提案をお送りください。 378 | そして修正します。 379 |

380 | 今提出 387 |
388 |
389 | 397 | 398 | 399 | -------------------------------------------------------------------------------- /src/Introduction.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from "@storybook/addon-docs/blocks"; 2 | 3 | 4 | 5 | # Tiny Manager 6 | 7 | A management tool for your personal projects. 8 | [**Github Repo**](https://github.com/nishantpainter/tiny-manager) 9 | -------------------------------------------------------------------------------- /src/TinyManager/assets/locales/cn/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add New Project": "添加新项目", 3 | "Add New Task": "添加新任务", 4 | "Add Todo": "添加待办事项", 5 | "Add": "添加", 6 | "All": "全部", 7 | "Cancel": "取消", 8 | "Clear Note": "清除笔记", 9 | "Completed": "完全的", 10 | "Confirm": "确认", 11 | "Created": "已创建", 12 | "Default Notes": "默认备注", 13 | "Delete All Projects": "删除所有项目", 14 | "Delete All Tasks": "删除所有任务", 15 | "Delete All": "删除所有", 16 | "Delete Project": "删除项目", 17 | "Delete project and related tasks ?": "删除项目和相关任务?", 18 | "Description": "描述", 19 | "Do you want to remove all the projects ?": "您要删除所有项目吗?", 20 | "Do you want to remove all the tasks ?": "您要删除所有任务吗?", 21 | "Download Note": "下载说明", 22 | "Edit Project": "编辑项目", 23 | "Edit Task": "编辑任务", 24 | "Edit Todo": "编辑待办事项", 25 | "Filter By": "过滤", 26 | "High": "高的", 27 | "Home": "家", 28 | "Low": "低的", 29 | "Medium": "中等的", 30 | "Name is required.": "姓名是必填项。", 31 | "Name": "姓名", 32 | "New Project": "新项目", 33 | "New Task": "新任务", 34 | "No available projects.": "没有可用的项目。", 35 | "No available tasks.": "没有可用的任务。", 36 | "Note": "笔记", 37 | "Notes will be stored locally on the browser and will be persisted.": "笔记将本地存储在浏览器上,并将被持久化。", 38 | "Notes": "笔记", 39 | "Nothing to be completed. Enjoy your day.": "没有什么可以完成的。 祝您愉快。", 40 | "PDF": "PDF", 41 | "Pending": "待办的", 42 | "Percentage": "百分比", 43 | "Priority": "优先", 44 | "Progress": "进步", 45 | "Projects": "项目", 46 | "Save": "节省", 47 | "Sort By": "排序方式", 48 | "TXT": "TXT", 49 | "Tiny Manager": "小经理", 50 | "Title is required.": "标题是必需的。", 51 | "Title": "标题", 52 | "Todos": "待办事项", 53 | "Updated": "更新", 54 | "You can enter your notes here...": "你可以在这里输入你的笔记..." 55 | } 56 | -------------------------------------------------------------------------------- /src/TinyManager/assets/locales/de/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add New Project": "Neues Projekt hinzufügen", 3 | "Add New Task": "Neue Aufgabe hinzufügen", 4 | "Add Todo": "Aufgaben hinzufügen", 5 | "Add": "Hinzufügen", 6 | "All": "Alle", 7 | "Cancel": "Absagen", 8 | "Clear Note": "Hinweis löschen", 9 | "Completed": "Abgeschlossen", 10 | "Confirm": "Bestätigen", 11 | "Created": "Erstellt", 12 | "Default Notes": "Standardnotizen", 13 | "Delete All Projects": "Alle Projekte löschen", 14 | "Delete All Tasks": "Alle Aufgaben löschen", 15 | "Delete All": "Alles löschen", 16 | "Delete Project": "Projekt löschen", 17 | "Delete project and related tasks ?": "Projekt und zugehörige Aufgaben löschen ?", 18 | "Description": "Beschreibung", 19 | "Do you want to remove all the projects ?": "Möchten Sie alle Projekte entfernen?", 20 | "Do you want to remove all the tasks ?": "Möchten Sie alle Aufgaben entfernen?", 21 | "Download Note": "Hinweis herunterladen", 22 | "Edit Project": "Projekt bearbeiten", 23 | "Edit Task": "Aufgabe bearbeiten", 24 | "Edit Todo": "Aufgaben bearbeiten", 25 | "Filter By": "Filtern nach", 26 | "High": "Hoch", 27 | "Home": "Heim", 28 | "Low": "Niedrig", 29 | "Medium": "Mittel", 30 | "Name is required.": "Name ist erforderlich.", 31 | "Name": "Name", 32 | "New Project": "Neues Projekt", 33 | "New Task": "Neue Aufgabe", 34 | "No available projects.": "Keine verfügbaren Projekte.", 35 | "No available tasks.": "Keine verfügbaren Aufgaben.", 36 | "Note": "Notiz", 37 | "Notes will be stored locally on the browser and will be persisted.": "Notizen werden lokal im Browser gespeichert und bleiben erhalten.", 38 | "Notes": "Anmerkungen", 39 | "Nothing to be completed. Enjoy your day.": "Nichts zu vervollständigen. Genieße deinen Tag.", 40 | "PDF": "PDF", 41 | "Pending": "Ausstehend", 42 | "Percentage": "Prozentsatz", 43 | "Priority": "Priorität", 44 | "Progress": "Fortschritt", 45 | "Projects": "Projekte", 46 | "Save": "Speichern", 47 | "Sort By": "Sortieren nach", 48 | "TXT": "TXT", 49 | "Tiny Manager": "Winziger Manager", 50 | "Title is required.": "Titel ist erforderlich.", 51 | "Title": "Titel", 52 | "Todos": "Tods", 53 | "Updated": "Aktualisiert", 54 | "You can enter your notes here...": "Hier können Sie Ihre Notizen eingeben..." 55 | } 56 | -------------------------------------------------------------------------------- /src/TinyManager/assets/locales/en/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add New Project": "Add New Project", 3 | "Add New Task": "Add New Task", 4 | "Add Todo": "Add Todo", 5 | "Add": "Add", 6 | "All": "All", 7 | "Cancel": "Cancel", 8 | "Clear Note": "Clear Note", 9 | "Completed": "Completed", 10 | "Confirm": "Confirm", 11 | "Created": "Created", 12 | "Default Notes": "Default Notes", 13 | "Delete All Projects": "Delete All Projects", 14 | "Delete All Tasks": "Delete All Tasks", 15 | "Delete All": "Delete All", 16 | "Delete Project": "Delete Project", 17 | "Delete project and related tasks ?": "Delete project and related tasks ?", 18 | "Description": "Description", 19 | "Do you want to remove all the projects ?": "Do you want to remove all the projects ?", 20 | "Do you want to remove all the tasks ?": "Do you want to remove all the tasks ?", 21 | "Download Note": "Download Note", 22 | "Edit Project": "Edit Project", 23 | "Edit Task": "Edit Task", 24 | "Edit Todo": "Edit Todo", 25 | "Filter By": "Filter By", 26 | "High": "High", 27 | "Home": "Home", 28 | "Low": "Low", 29 | "Medium": "Medium", 30 | "Name is required.": "Name is required.", 31 | "Name": "Name", 32 | "New Project": "New Project", 33 | "New Task": "New Task", 34 | "No available projects.": "No available projects.", 35 | "No available tasks.": "No available tasks.", 36 | "Note": "Note", 37 | "Notes will be stored locally on the browser and will be persisted.": "Notes will be stored locally on the browser and will be persisted.", 38 | "Notes": "Notes", 39 | "Nothing to be completed. Enjoy your day.": "Nothing to be completed. Enjoy your day.", 40 | "PDF": "PDF", 41 | "Pending": "Pending", 42 | "Percentage": "Percentage", 43 | "Priority": "Priority", 44 | "Progress": "Progress", 45 | "Projects": "Projects", 46 | "Save": "Save", 47 | "Sort By": "Sort By", 48 | "TXT": "TXT", 49 | "Tiny Manager": "Tiny Manager", 50 | "Title is required.": "Title is required.", 51 | "Title": "Title", 52 | "Todos": "Todos", 53 | "Updated": "Updated", 54 | "You can enter your notes here...": "You can enter your notes here..." 55 | } 56 | -------------------------------------------------------------------------------- /src/TinyManager/assets/locales/es/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add New Project": "Agregar nuevo proyecto", 3 | "Add New Task": "Agregar nueva tarea", 4 | "Add Todo": "Añadir todo", 5 | "Add": "Agregar", 6 | "All": "Todos", 7 | "Cancel": "Cancelar", 8 | "Clear Note": "Nota clara", 9 | "Completed": "Terminado", 10 | "Confirm": "Confirmar", 11 | "Created": "Creado", 12 | "Default Notes": "Notas predeterminadas", 13 | "Delete All Projects": "Eliminar todos los proyectos", 14 | "Delete All Tasks": "Eliminar todas las tareas", 15 | "Delete All": "Eliminar todos", 16 | "Delete Project": "Eliminar proyecto", 17 | "Delete project and related tasks ?": "¿Eliminar proyecto y tareas relacionadas?", 18 | "Description": "Descripción", 19 | "Do you want to remove all the projects ?": "¿Quieres eliminar todos los proyectos?", 20 | "Do you want to remove all the tasks ?": "¿Quieres eliminar todas las tareas?", 21 | "Download Note": "Descargar Nota", 22 | "Edit Project": "Editar proyecto", 23 | "Edit Task": "Editar tarea", 24 | "Edit Todo": "Editar Todo", 25 | "Filter By": "Filtrado por", 26 | "High": "Alto", 27 | "Home": "Hogar", 28 | "Low": "Bajo", 29 | "Medium": "Medio", 30 | "Name is required.": "Se requiere el nombre.", 31 | "Name": "Nombre", 32 | "New Project": "Nuevo proyecto", 33 | "New Task": "Nueva tarea", 34 | "No available projects.": "No hay proyectos disponibles.", 35 | "No available tasks.": "No hay tareas disponibles.", 36 | "Note": "Nota", 37 | "Notes will be stored locally on the browser and will be persisted.": "Las notas se almacenarán localmente en el navegador y se conservarán.", 38 | "Notes": "notas", 39 | "Nothing to be completed. Enjoy your day.": "Nada por completar. Disfruta tu día.", 40 | "PDF": "PDF", 41 | "Pending": "Pendiente", 42 | "Percentage": "Porcentaje", 43 | "Priority": "Prioridad", 44 | "Progress": "Progreso", 45 | "Projects": "Proyectos", 46 | "Save": "Ahorrar", 47 | "Sort By": "Ordenar por", 48 | "TXT": "TXT", 49 | "Tiny Manager": "Pequeño gerente", 50 | "Title is required.": "Se requiere título.", 51 | "Title": "Título", 52 | "Todos": "Todos", 53 | "Updated": "Actualizado", 54 | "You can enter your notes here...": "Puede ingresar sus notas aquí..." 55 | } 56 | -------------------------------------------------------------------------------- /src/TinyManager/assets/locales/fr/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add New Project": "Ajouter un nouveau projet", 3 | "Add New Task": "Ajouter une nouvelle tâche", 4 | "Add Todo": "Ajouter une tâche", 5 | "Add": "Ajouter", 6 | "All": "Tout", 7 | "Cancel": "Annuler", 8 | "Clear Note": "Remarque claire", 9 | "Completed": "Complété", 10 | "Confirm": "Confirmer", 11 | "Created": "Établi", 12 | "Default Notes": "Remarques par défaut", 13 | "Delete All Projects": "Supprimer tous les projets", 14 | "Delete All Tasks": "Supprimer toutes les tâches", 15 | "Delete All": "Supprimer tout", 16 | "Delete Project": "Supprimer le projet", 17 | "Delete project and related tasks ?": "Supprimer le projet et les tâches associées ?", 18 | "Description": "La description", 19 | "Do you want to remove all the projects ?": "Voulez-vous supprimer tous les projets ?", 20 | "Do you want to remove all the tasks ?": "Voulez-vous supprimer toutes les tâches ?", 21 | "Download Note": "Télécharger la note", 22 | "Edit Project": "Modifier le projet", 23 | "Edit Task": "Modifier la tâche", 24 | "Edit Todo": "Modifier la tâche", 25 | "Filter By": "Filtrer par", 26 | "High": "Haut", 27 | "Home": "Maison", 28 | "Low": "Bas", 29 | "Medium": "Moyen", 30 | "Name is required.": "Le nom est requis.", 31 | "Name": "Nom", 32 | "New Project": "Nouveau projet", 33 | "New Task": "Nouvelle tâche", 34 | "No available projects.": "Aucun projet disponible.", 35 | "No available tasks.": "Aucune tâche disponible.", 36 | "Note": "Noter", 37 | "Notes will be stored locally on the browser and will be persisted.": "Les notes seront stockées localement sur le navigateur et seront conservées.", 38 | "Notes": "Remarques", 39 | "Nothing to be completed. Enjoy your day.": "Rien à compléter. Profitez de votre journée.", 40 | "PDF": "PDF", 41 | "Pending": "En attente", 42 | "Percentage": "Pourcentage", 43 | "Priority": "Priorité", 44 | "Progress": "Progrès", 45 | "Projects": "Projets", 46 | "Save": "Sauvegarder", 47 | "Sort By": "Trier par", 48 | "TXT": "TXT", 49 | "Tiny Manager": "Petit gestionnaire", 50 | "Title is required.": "Le titre est requis.", 51 | "Title": "Titre", 52 | "Todos": "À faire", 53 | "Updated": "Actualisé", 54 | "You can enter your notes here...": "Vous pouvez entrer vos notes ici..." 55 | } 56 | -------------------------------------------------------------------------------- /src/TinyManager/assets/locales/in/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add New Project": "नया प्रोजेक्ट जोड़ें", 3 | "Add New Task": "नया कार्य जोड़ें", 4 | "Add Todo": "टोडो जोड़ें", 5 | "Add": "जोड़ें", 6 | "All": "सभी", 7 | "Cancel": "रद्द करना", 8 | "Clear Note": "नोट साफ़ करें", 9 | "Completed": "पूरा हुआ", 10 | "Confirm": "पुष्टि करें", 11 | "Created": "बनाया था", 12 | "Default Notes": "डिफ़ॉल्ट नोट्स", 13 | "Delete All Projects": "सभी प्रोजेक्ट हटाएं", 14 | "Delete All Tasks": "सभी कार्य हटाएं", 15 | "Delete All": "सभी हटा दो", 16 | "Delete Project": "प्रोजेक्ट हटाएं", 17 | "Delete project and related tasks ?": "प्रोजेक्ट और संबंधित कार्य हटाएं?", 18 | "Description": "विवरण", 19 | "Do you want to remove all the projects ?": "क्या आप सभी प्रोजेक्ट हटाना चाहते हैं?", 20 | "Do you want to remove all the tasks ?": "क्या आप सभी कार्यों को हटाना चाहते हैं?", 21 | "Download Note": "नोट डाउनलोड करें", 22 | "Edit Project": "परियोजना संपादित करें", 23 | "Edit Task": "कार्य संपादित करें", 24 | "Edit Todo": "कार्य संपादित करें", 25 | "Filter By": "छनित करें", 26 | "High": "उच्च", 27 | "Home": "होम", 28 | "Low": "कम", 29 | "Medium": "मध्यम", 30 | "Name is required.": "नाम आवश्यक है।", 31 | "Name": "नाम", 32 | "New Project": "नया काम", 33 | "New Task": "नया कार्य", 34 | "No available projects.": "कोई प्रोजेक्ट उपलब्ध नहीं है.", 35 | "No available tasks.": "कोई उपलब्ध कार्य नहीं।", 36 | "Note": "टिप्पणी", 37 | "Notes will be stored locally on the browser and will be persisted.": "नोट्स स्थानीय रूप से ब्राउज़र पर संग्रहीत किए जाएंगे और बने रहेंगे।", 38 | "Notes": "टिप्पणियाँ", 39 | "Nothing to be completed. Enjoy your day.": "कुछ भी पूरा नहीं करना है। अपने दिन का आनंद लें।", 40 | "PDF": "PDF", 41 | "Pending": "लंबित", 42 | "Percentage": "प्रतिशत", 43 | "Priority": "वरीयता", 44 | "Progress": "प्रगति", 45 | "Projects": "परियोजनाओं", 46 | "Save": "बचाना", 47 | "Sort By": "क्रमबद्ध करें", 48 | "TXT": "TXT", 49 | "Tiny Manager": "छोटे प्रबंधक", 50 | "Title is required.": "शीर्षक की आवश्यकता है।", 51 | "Title": "शीर्षक", 52 | "Todos": "सब", 53 | "Updated": "अद्यतन", 54 | "You can enter your notes here...": "आप अपने नोट्स यहां दर्ज कर सकते हैं..." 55 | } 56 | -------------------------------------------------------------------------------- /src/TinyManager/assets/locales/jp/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add New Project": "新しいプロジェクトを追加", 3 | "Add New Task": "新しいタスクを追加", 4 | "Add Todo": "追加 - Todo", 5 | "Add": "追加", 6 | "All": "全て", 7 | "Cancel": "キャンセル", 8 | "Clear Note": "クリアノート", 9 | "Completed": "完了", 10 | "Confirm": "確認", 11 | "Created": "作成した", 12 | "Default Notes": "デフォルトのメモ", 13 | "Delete All Projects": "すべてのプロジェクトを削除", 14 | "Delete All Tasks": "すべてのタスクを削除", 15 | "Delete All": "すべて削除", 16 | "Delete Project": "プロジェクトを削除", 17 | "Delete project and related tasks ?": "プロジェクトと関連タスクを削除しますか?", 18 | "Description": "説明", 19 | "Do you want to remove all the projects ?": "すべてのプロジェクトを削除しますか?", 20 | "Do you want to remove all the tasks ?": "すべてのタスクを削除しますか?", 21 | "Download Note": "注をダウンロード", 22 | "Edit Project": "プロジェクトの編集", 23 | "Edit Task": "タスクの編集", 24 | "Edit Todo": "Todoを編集する", 25 | "Filter By": "フィルター", 26 | "High": "高い", 27 | "Home": "家", 28 | "Low": "低い", 29 | "Medium": "中くらい", 30 | "Name is required.": "名前が必要です。", 31 | "Name": "名前", 32 | "New Project": "新しい計画", 33 | "New Task": "新しい仕事", 34 | "No available projects.": "利用可能なプロジェクトはありません。", 35 | "No available tasks.": "利用可能なタスクはありません。", 36 | "Note": "ノート", 37 | "Notes will be stored locally on the browser and will be persisted.": "メモはブラウザにローカルに保存され、保持されます。", 38 | "Notes": "ノート", 39 | "Nothing to be completed. Enjoy your day.": "完了するものはありません。 一日をお楽しみください。", 40 | "PDF": "PDF", 41 | "Pending": "保留中", 42 | "Percentage": "パーセンテージ", 43 | "Priority": "優先順位", 44 | "Progress": "進捗", 45 | "Projects": "プロジェクト", 46 | "Save": "保存", 47 | "Sort By": "並び替え", 48 | "TXT": "TXT", 49 | "Tiny Manager": "小さなマネージャー", 50 | "Title is required.": "タイトルが必要です。", 51 | "Title": "題名", 52 | "Todos": "Todos", 53 | "Updated": "更新しました", 54 | "You can enter your notes here...": "ここにメモを入力できます..." 55 | } 56 | -------------------------------------------------------------------------------- /src/TinyManager/assets/locales/ru/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "Add New Project": "Добавить новый проект", 3 | "Add New Task": "Добавить новую задачу", 4 | "Add Todo": "Добавить дело", 5 | "Add": "Добавлять", 6 | "All": "Все", 7 | "Cancel": "Отмена", 8 | "Clear Note": "Очистить примечание", 9 | "Completed": "Завершенный", 10 | "Confirm": "Подтверждать", 11 | "Created": "Созданный", 12 | "Default Notes": "Примечания по умолчанию", 13 | "Delete All Projects": "Удалить все проекты", 14 | "Delete All Tasks": "Удалить все задачи", 15 | "Delete All": "Удалить все", 16 | "Delete Project": "Удалить проект", 17 | "Delete project and related tasks ?": "Удалить проект и связанные с ним задачи?", 18 | "Description": "Описание", 19 | "Do you want to remove all the projects ?": "Вы хотите удалить все проекты?", 20 | "Do you want to remove all the tasks ?": "Вы хотите удалить все задачи?", 21 | "Download Note": "Скачать примечание", 22 | "Edit Project": "Редактировать проект", 23 | "Edit Task": "Изменить задачу", 24 | "Edit Todo": "Изменить дело", 25 | "Filter By": "Сортировать по", 26 | "High": "Высокая", 27 | "Home": "Дом", 28 | "Low": "Низкий", 29 | "Medium": "Середина", 30 | "Name is required.": "Требуется имя.", 31 | "Name": "Имя", 32 | "New Project": "Новый проект", 33 | "New Task": "Новое задание", 34 | "No available projects.": "Нет доступных проектов.", 35 | "No available tasks.": "Нет доступных задач.", 36 | "Note": "Примечание", 37 | "Notes will be stored locally on the browser and will be persisted.": "Заметки будут храниться локально в браузере и будут сохраняться.", 38 | "Notes": "Заметки", 39 | "Nothing to be completed. Enjoy your day.": "Ничего не нужно завершать. Хорошего вам дня.", 40 | "PDF": "PDF", 41 | "Pending": "В ожидании", 42 | "Percentage": "Процент", 43 | "Priority": "приоритет", 44 | "Progress": "Прогресс", 45 | "Projects": "Проекты", 46 | "Save": "Сохранять", 47 | "Sort By": "Сортировать по", 48 | "TXT": "TXT", 49 | "Tiny Manager": "Крошечный менеджер", 50 | "Title is required.": "Заголовок обязателен.", 51 | "Title": "Заголовок", 52 | "Todos": "Todos", 53 | "Updated": "Обновлено", 54 | "You can enter your notes here...": "Здесь вы можете оставить свои заметки..." 55 | } 56 | -------------------------------------------------------------------------------- /src/TinyManager/components/CircularProgressWithLabel/CircularProgressWithLabel.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import Box from "@material-ui/core/Box"; 5 | import CircularProgress from "@material-ui/core/CircularProgress"; 6 | import Typography from "@material-ui/core/Typography"; 7 | 8 | const useStyles = makeStyles(() => ({ 9 | progress: { 10 | fontSize: "0.60rem", 11 | }, 12 | })); 13 | 14 | function CircularProgressWithLabel(props) { 15 | const classes = useStyles(); 16 | 17 | return ( 18 | 19 | 20 | 30 | {`${Math.round(props.value)}%`} 35 | 36 | 37 | ); 38 | } 39 | 40 | CircularProgressWithLabel.propTypes = { 41 | /** 42 | * The value of the progress indicator. 43 | * Value between 0 and 100. 44 | */ 45 | value: PropTypes.number.isRequired, 46 | }; 47 | 48 | CircularProgressWithLabel.defaultProps = { 49 | value: 0, 50 | }; 51 | 52 | export default CircularProgressWithLabel; 53 | -------------------------------------------------------------------------------- /src/TinyManager/components/CircularProgressWithLabel/CircularProgressWithLabel.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import CircularProgressWithLabel from "./CircularProgressWithLabel"; 4 | 5 | export default { 6 | title: "TinyManager/CircularProgressWithLabel", 7 | component: CircularProgressWithLabel, 8 | }; 9 | 10 | const Template = (args) => ; 11 | 12 | export const Default = Template.bind({}); 13 | Default.args = { 14 | value: 50, 15 | }; 16 | -------------------------------------------------------------------------------- /src/TinyManager/components/CircularProgressWithLabel/index.js: -------------------------------------------------------------------------------- 1 | import CircularProgressWithLabel from "./CircularProgressWithLabel"; 2 | 3 | export default CircularProgressWithLabel; 4 | -------------------------------------------------------------------------------- /src/TinyManager/components/ConfirmationDialog/ConfirmationDialog.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import Button from "@material-ui/core/Button"; 4 | import Dialog from "@material-ui/core/Dialog"; 5 | import DialogActions from "@material-ui/core/DialogActions"; 6 | import DialogContent from "@material-ui/core/DialogContent"; 7 | import DialogTitle from "@material-ui/core/DialogTitle"; 8 | 9 | import { identity } from "../utils"; 10 | 11 | function ConfirmationDialog(props) { 12 | const { 13 | title, 14 | content, 15 | open, 16 | translate, 17 | onClose, 18 | onConfirm, 19 | onCancel, 20 | } = props; 21 | 22 | const handleCancel = onCancel || onClose; 23 | 24 | return ( 25 | 26 | {title} 27 | {content} 28 | 29 | 30 | 33 | 34 | 35 | ); 36 | } 37 | 38 | ConfirmationDialog.propTypes = { 39 | title: PropTypes.any, 40 | content: PropTypes.any, 41 | open: PropTypes.bool, 42 | translate: PropTypes.func, 43 | onClose: PropTypes.func, 44 | onConfirm: PropTypes.func, 45 | onCancel: PropTypes.func, 46 | }; 47 | 48 | ConfirmationDialog.defaultProps = { 49 | translate: identity, 50 | }; 51 | 52 | export default ConfirmationDialog; 53 | -------------------------------------------------------------------------------- /src/TinyManager/components/ConfirmationDialog/ConfirmationDialog.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import ConfirmationDialog from "./ConfirmationDialog"; 4 | import { identity, noop } from "../utils"; 5 | 6 | export default { 7 | title: "TinyManager/ConfirmationDialog", 8 | component: ConfirmationDialog, 9 | }; 10 | 11 | const Template = (args) => ; 12 | 13 | export const Default = Template.bind({}); 14 | Default.args = { 15 | open: true, 16 | title: "Confirmation Dialog", 17 | content: "Do you want to continue?", 18 | onClose: noop, 19 | onCancel: noop, 20 | onConfirm: noop, 21 | translate: identity, 22 | }; 23 | -------------------------------------------------------------------------------- /src/TinyManager/components/ConfirmationDialog/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./ConfirmationDialog"; 2 | -------------------------------------------------------------------------------- /src/TinyManager/components/IconButton/IconButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import MuiIconButton from "@material-ui/core/IconButton"; 4 | 5 | import FilterListIcon from "@material-ui/icons/FilterList"; 6 | import EditIcon from "@material-ui/icons/Edit"; 7 | import AttachFile from "@material-ui/icons/AttachFile"; 8 | import InvertColors from "@material-ui/icons/InvertColors"; 9 | import Delete from "@material-ui/icons/Delete"; 10 | import Language from "@material-ui/icons/Language"; 11 | import GitHub from "@material-ui/icons/GitHub"; 12 | 13 | const icons = { 14 | filter: FilterListIcon, 15 | edit: EditIcon, 16 | attachment: AttachFile, 17 | invertColors: InvertColors, 18 | delete: Delete, 19 | language: Language, 20 | github: GitHub, 21 | }; 22 | 23 | function IconButton(props) { 24 | const { icon, ...rest } = props; 25 | const Icon = icons[icon]; 26 | return ( 27 | 28 | 29 | 30 | ); 31 | } 32 | 33 | IconButton.propTypes = { 34 | /** Icon to be displayed */ 35 | icon: PropTypes.string, 36 | }; 37 | 38 | export default IconButton; 39 | -------------------------------------------------------------------------------- /src/TinyManager/components/IconButton/IconButton.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import IconButton from "./IconButton"; 4 | 5 | export default { 6 | title: "TinyManager/IconButton", 7 | component: IconButton, 8 | }; 9 | 10 | const Template = (args) => ; 11 | 12 | export const Default = Template.bind({}); 13 | Default.args = { 14 | icon: "filter", 15 | }; 16 | -------------------------------------------------------------------------------- /src/TinyManager/components/IconButton/index.js: -------------------------------------------------------------------------------- 1 | import IconButton from "./IconButton"; 2 | 3 | export default IconButton; 4 | -------------------------------------------------------------------------------- /src/TinyManager/components/Loader/Loader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | import PropTypes from "prop-types"; 4 | import CircularProgress from "@material-ui/core/CircularProgress"; 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | 7 | const useStyles = makeStyles((theme) => ({ 8 | root: { 9 | height: "inherit", 10 | width: "inherit", 11 | position: "relative", 12 | display: "flex", 13 | justifyContent: "center", 14 | alignItems: "center", 15 | }, 16 | top: { 17 | color: "transparent", 18 | }, 19 | bottom: { 20 | color: theme.palette.primary.main, 21 | animationDuration: "250ms", 22 | position: "absolute", 23 | }, 24 | })); 25 | 26 | function Loader(props) { 27 | const { className } = props; 28 | 29 | const classes = useStyles(); 30 | 31 | return ( 32 |
33 | 41 | 49 |
50 | ); 51 | } 52 | 53 | Loader.propTypes = { 54 | /** 55 | * @ignore 56 | */ 57 | className: PropTypes.string, 58 | }; 59 | 60 | export default Loader; 61 | -------------------------------------------------------------------------------- /src/TinyManager/components/Loader/Loader.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Loader from "./Loader"; 4 | import { withWrapper } from "TinyManager/decorators"; 5 | 6 | export default { 7 | title: "TinyManager/Loader", 8 | component: Loader, 9 | decorators: [withWrapper], 10 | }; 11 | 12 | const Template = (args) => ; 13 | 14 | export const Default = Template.bind({}); 15 | Default.args = {}; 16 | -------------------------------------------------------------------------------- /src/TinyManager/components/Loader/index.js: -------------------------------------------------------------------------------- 1 | import Loader from "./Loader"; 2 | 3 | export default Loader; 4 | -------------------------------------------------------------------------------- /src/TinyManager/components/OutlinedSelect/OutlinedSelect.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import FormControl from "@material-ui/core/FormControl"; 4 | import InputLabel from "@material-ui/core/InputLabel"; 5 | import MenuItem from "@material-ui/core/MenuItem"; 6 | import Select from "@material-ui/core/Select"; 7 | 8 | function OutlinedSelect(props) { 9 | const { id, label, value, className, menu, disabled, onChange } = props; 10 | 11 | return ( 12 | 13 | {label} 14 | 29 | 30 | ); 31 | } 32 | 33 | OutlinedSelect.propTypes = { 34 | id: PropTypes.string, 35 | label: PropTypes.string, 36 | value: PropTypes.any, 37 | menu: PropTypes.array, 38 | className: PropTypes.string, 39 | disabled: PropTypes.bool, 40 | onChange: PropTypes.func, 41 | }; 42 | 43 | export default OutlinedSelect; 44 | -------------------------------------------------------------------------------- /src/TinyManager/components/OutlinedSelect/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./OutlinedSelect"; 2 | -------------------------------------------------------------------------------- /src/TinyManager/components/Paper/Paper.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | import MuiPaper from "@material-ui/core/Paper"; 3 | import { withStyles } from "@material-ui/core/styles"; 4 | 5 | const Paper = withStyles((theme) => ({ 6 | root: { 7 | display: "flex", 8 | alignItems: "center", 9 | justifyContent: "center", 10 | cursor: "pointer", 11 | boxShadow: theme.custom.shadow[0], 12 | borderRadius: theme.spacing(0.5), 13 | padding: theme.spacing(), 14 | height: theme.spacing(15), 15 | }, 16 | }))(MuiPaper); 17 | 18 | Paper.displayName = "Paper"; 19 | 20 | Paper.propTypes = { 21 | /** 22 | * @ignore 23 | */ 24 | className: PropTypes.string, 25 | }; 26 | 27 | export default Paper; 28 | -------------------------------------------------------------------------------- /src/TinyManager/components/Paper/Paper.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Paper from "./Paper"; 4 | import { withWrapper } from "TinyManager/decorators"; 5 | 6 | export default { 7 | title: "TinyManager/Paper", 8 | component: Paper, 9 | decorators: [withWrapper], 10 | }; 11 | 12 | const Template = (args) => ; 13 | 14 | export const Default = Template.bind({}); 15 | Default.args = {}; 16 | -------------------------------------------------------------------------------- /src/TinyManager/components/Paper/index.js: -------------------------------------------------------------------------------- 1 | import Paper from './Paper'; 2 | 3 | export default Paper; -------------------------------------------------------------------------------- /src/TinyManager/components/ProjectCard/ProjectCard.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import clsx from "clsx"; 3 | import PropTypes from "prop-types"; 4 | import Box from "@material-ui/core/Box"; 5 | import LinearProgress from "@material-ui/core/LinearProgress"; 6 | import Typography from "@material-ui/core/Typography"; 7 | import { makeStyles } from "@material-ui/core/styles"; 8 | 9 | import Paper from "../Paper"; 10 | import IconButton from "../IconButton"; 11 | import Types from "TinyManager/types"; 12 | 13 | const useStyles = makeStyles(() => ({ 14 | paper: { 15 | flexDirection: "column", 16 | alignItems: "flex-start", 17 | }, 18 | fullWidth: { 19 | width: "100%", 20 | }, 21 | })); 22 | 23 | function ProjectCard(props) { 24 | const { 25 | className, 26 | project, 27 | progress, 28 | onClick, 29 | onEdit, 30 | onDelete, 31 | showEditButton, 32 | showDeleteButton, 33 | } = props; 34 | 35 | const classes = useStyles(); 36 | 37 | const handleClick = useCallback( 38 | (event) => { 39 | if (onClick) { 40 | onClick(event, project); 41 | } 42 | }, 43 | [project, onClick] 44 | ); 45 | 46 | const handleEdit = useCallback( 47 | (event) => { 48 | if (onEdit) { 49 | onEdit(event, project); 50 | } 51 | }, 52 | [project, onEdit] 53 | ); 54 | 55 | const handleDelete = useCallback( 56 | (event) => { 57 | if (onDelete) { 58 | onDelete(event, project); 59 | } 60 | }, 61 | [project, onDelete] 62 | ); 63 | 64 | return ( 65 | 66 | 72 | 73 | {project.name} 74 | 75 | 76 | {showEditButton && } 77 | {showDeleteButton && ( 78 | 79 | )} 80 | 81 | 82 | 89 | {project.description} 90 | 91 | 97 | 98 | ); 99 | } 100 | 101 | ProjectCard.propTypes = { 102 | className: PropTypes.string, 103 | project: Types.ProjectType, 104 | progress: PropTypes.number, 105 | onClick: PropTypes.func, 106 | onEdit: PropTypes.func, 107 | onDelete: PropTypes.func, 108 | showEditButton: PropTypes.bool, 109 | showDeleteButton: PropTypes.bool, 110 | }; 111 | 112 | ProjectCard.defaultProps = { 113 | project: {}, 114 | progress: 0, 115 | showEditButton: false, 116 | }; 117 | 118 | export default ProjectCard; 119 | -------------------------------------------------------------------------------- /src/TinyManager/components/ProjectCard/ProjectCard.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import ProjectCard from "./ProjectCard"; 4 | import { withWrapper } from "TinyManager/decorators"; 5 | 6 | export default { 7 | title: "TinyManager/ProjectCard", 8 | component: ProjectCard, 9 | decorators: [withWrapper], 10 | }; 11 | 12 | const Template = (args) => ; 13 | 14 | export const Default = Template.bind({}); 15 | Default.args = { 16 | project: { 17 | name: "Tiny Manager", 18 | description: "Tiny manager for your self project", 19 | }, 20 | }; 21 | 22 | export const Progress = Template.bind({}); 23 | Progress.args = { 24 | ...Default.args, 25 | progress: 100, 26 | }; 27 | 28 | export const Actions = Template.bind({}); 29 | Actions.args = { 30 | ...Default.args, 31 | showEditButton: true, 32 | showDeleteButton: true, 33 | }; 34 | -------------------------------------------------------------------------------- /src/TinyManager/components/ProjectCard/index.js: -------------------------------------------------------------------------------- 1 | import ProjectCard from "./ProjectCard"; 2 | 3 | export default ProjectCard; 4 | -------------------------------------------------------------------------------- /src/TinyManager/components/ProjectForm/ProjectForm.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import Divider from "@material-ui/core/Divider"; 3 | import PropTypes from "prop-types"; 4 | import { makeStyles } from "@material-ui/core/styles"; 5 | import TextField from "@material-ui/core/TextField"; 6 | import Button from "@material-ui/core/Button"; 7 | import Grid from "@material-ui/core/Grid"; 8 | import Typography from "@material-ui/core/Typography"; 9 | 10 | import Paper from "../Paper"; 11 | import { ProjectType } from "TinyManager/types"; 12 | import { identity } from "../utils"; 13 | 14 | const useStyles = makeStyles((theme) => ({ 15 | paper: { 16 | padding: theme.spacing(2), 17 | height: "auto", 18 | }, 19 | })); 20 | 21 | function ProjectForm(props) { 22 | const { 23 | title, 24 | values, 25 | errors, 26 | disabled, 27 | translate, 28 | onSubmit, 29 | onChange, 30 | onCancel, 31 | } = props; 32 | 33 | const classes = useStyles(); 34 | 35 | const handleChange = useCallback( 36 | (event) => { 37 | if (onChange) { 38 | onChange(event); 39 | } 40 | }, 41 | [onChange] 42 | ); 43 | 44 | const handleCancel = useCallback( 45 | (event) => { 46 | if (onCancel) { 47 | onCancel(event); 48 | } 49 | }, 50 | [onCancel] 51 | ); 52 | 53 | const handleSubmit = useCallback( 54 | (event) => { 55 | event.preventDefault(); 56 | if (onSubmit) { 57 | onSubmit(event); 58 | } 59 | }, 60 | [onSubmit] 61 | ); 62 | 63 | return ( 64 |
65 | 66 | 67 | 68 | 69 | {title} 70 | 71 | 72 | 73 | 74 | 75 | 88 | 89 | 90 | 103 | 104 | 105 | 112 |   113 | 121 | 122 | 123 | 124 |
125 | ); 126 | } 127 | 128 | ProjectForm.propTypes = { 129 | /** 130 | * Title of the form 131 | */ 132 | title: PropTypes.string, 133 | /** 134 | * Values of the form 135 | */ 136 | values: ProjectType, 137 | /** 138 | * Errors in the form 139 | */ 140 | errors: ProjectType, 141 | /** 142 | * Disabled 143 | */ 144 | disabled: PropTypes.bool, 145 | /** 146 | * Translator 147 | */ 148 | translate: PropTypes.func, 149 | /** 150 | * Input on change handler 151 | */ 152 | onChange: PropTypes.func, 153 | /** 154 | * Form on submit handler 155 | */ 156 | onSubmit: PropTypes.func, 157 | /** 158 | * Form cancel handler 159 | */ 160 | onCancel: PropTypes.func, 161 | }; 162 | 163 | ProjectForm.defaultProps = { 164 | values: {}, 165 | errors: {}, 166 | disabled: false, 167 | title: "New Project", 168 | translate: identity, 169 | }; 170 | 171 | export default ProjectForm; 172 | -------------------------------------------------------------------------------- /src/TinyManager/components/ProjectForm/ProjectForm.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import ProjectForm from "./ProjectForm"; 4 | import { withLargeWrapper } from "TinyManager/decorators"; 5 | 6 | const argTypes = { 7 | values: { 8 | control: { 9 | type: null, 10 | }, 11 | }, 12 | errors: { 13 | control: { 14 | type: null, 15 | }, 16 | }, 17 | }; 18 | 19 | export default { 20 | title: "TinyManager/ProjectForm", 21 | component: ProjectForm, 22 | argTypes, 23 | decorators: [withLargeWrapper], 24 | }; 25 | 26 | const Template = (args) => ; 27 | 28 | export const Default = Template.bind({}); 29 | Default.args = {}; 30 | 31 | export const Disabled = Template.bind({}); 32 | Disabled.args = { 33 | disabled: true, 34 | }; 35 | -------------------------------------------------------------------------------- /src/TinyManager/components/ProjectForm/index.js: -------------------------------------------------------------------------------- 1 | import ProjectForm from "./ProjectForm"; 2 | 3 | export default ProjectForm; 4 | -------------------------------------------------------------------------------- /src/TinyManager/components/TaskCard/TaskCard.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import clsx from "clsx"; 3 | import PropTypes from "prop-types"; 4 | import { makeStyles, useTheme } from "@material-ui/core/styles"; 5 | import Typography from "@material-ui/core/Typography"; 6 | import IconButton from "@material-ui/core/IconButton"; 7 | import Grid from "@material-ui/core/Grid"; 8 | import Box from "@material-ui/core/Box"; 9 | 10 | import DeleteIcon from "@material-ui/icons/Delete"; 11 | import FlagIcon from "@material-ui/icons/Flag"; 12 | 13 | import Paper from "../Paper"; 14 | import CircularProgressWithLabel from "../CircularProgressWithLabel"; 15 | import { TaskType } from "TinyManager/types/index"; 16 | 17 | const useStyles = makeStyles((theme) => { 18 | const isDark = theme.palette.type === "dark"; 19 | return { 20 | paper: { 21 | height: theme.spacing(7), 22 | alignItems: "center", 23 | }, 24 | mediumPriority: { 25 | color: isDark && theme.palette.warning.light, 26 | backgroundColor: !isDark && theme.palette.warning.light, 27 | }, 28 | highPriority: { 29 | color: isDark && theme.palette.error.light, 30 | backgroundColor: !isDark && theme.palette.error.light, 31 | }, 32 | progress: { 33 | display: "flex", 34 | justifyContent: "flex-end", 35 | alignItems: "center", 36 | }, 37 | completedFlag: { 38 | color: theme.palette.success.light, 39 | marginRight: theme.spacing(0.5), 40 | }, 41 | }; 42 | }); 43 | 44 | const PRIORITY = { 45 | low: 0, 46 | medium: 1, 47 | high: 2, 48 | }; 49 | 50 | function TaskCard(props) { 51 | const { task, onClick, onDelete, className } = props; 52 | const { priority = 0, progress, title } = task; 53 | 54 | const classes = useStyles(); 55 | const theme = useTheme(); 56 | 57 | const handleClick = useCallback( 58 | (event) => { 59 | if (onClick) { 60 | onClick(event, task); 61 | } 62 | }, 63 | [onClick, task] 64 | ); 65 | 66 | const handleDelete = useCallback( 67 | (event) => { 68 | if (onDelete) { 69 | onDelete(event, task); 70 | } 71 | }, 72 | [onDelete, task] 73 | ); 74 | 75 | const completed = progress === 100; 76 | 77 | return ( 78 | 90 | 91 | 92 | 93 | {completed ? : null} 94 | 99 | {title} 100 | 101 | 102 | 103 | 110 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | ); 129 | } 130 | 131 | TaskCard.propTypes = { 132 | /** 133 | * Task details 134 | */ 135 | task: TaskType, 136 | /** 137 | * Task click handler 138 | */ 139 | onClick: PropTypes.func, 140 | /** 141 | * Task delete handler 142 | */ 143 | onDelete: PropTypes.func, 144 | /** 145 | * @ignore 146 | */ 147 | className: PropTypes.string, 148 | }; 149 | 150 | TaskCard.defaultProps = { 151 | task: {}, 152 | }; 153 | 154 | export default TaskCard; 155 | -------------------------------------------------------------------------------- /src/TinyManager/components/TaskCard/TaskCard.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import TaskCard from "./TaskCard"; 4 | import { withWrapper } from "TinyManager/decorators"; 5 | 6 | export default { 7 | title: "TinyManager/TaskCard", 8 | component: TaskCard, 9 | decorators: [withWrapper], 10 | argTypes: { 11 | task: { 12 | control: { 13 | type: null, 14 | }, 15 | }, 16 | }, 17 | }; 18 | 19 | const Template = (args) => ; 20 | 21 | export const Default = Template.bind({}); 22 | Default.args = { 23 | task: { 24 | title: "Initate production", 25 | progress: 90, 26 | }, 27 | }; 28 | 29 | export const LowPriority = Template.bind({}); 30 | LowPriority.args = { 31 | task: { 32 | ...Default.args.task, 33 | priority: 0, 34 | }, 35 | }; 36 | 37 | export const MediumPriority = Template.bind({}); 38 | MediumPriority.args = { 39 | task: { 40 | ...Default.args.task, 41 | priority: 1, 42 | }, 43 | }; 44 | 45 | export const HighPriority = Template.bind({}); 46 | HighPriority.args = { 47 | task: { 48 | ...Default.args.task, 49 | priority: 2, 50 | }, 51 | }; 52 | 53 | export const Completed = Template.bind({}); 54 | Completed.args = { 55 | task: { 56 | ...Default.args.task, 57 | progress: 100, 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/TinyManager/components/TaskCard/index.js: -------------------------------------------------------------------------------- 1 | import TaskCard from "./TaskCard"; 2 | 3 | export default TaskCard; 4 | -------------------------------------------------------------------------------- /src/TinyManager/components/TaskForm/TaskForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TextField from "@material-ui/core/TextField"; 3 | import Button from "@material-ui/core/Button"; 4 | import Grid from "@material-ui/core/Grid"; 5 | import Typography from "@material-ui/core/Typography"; 6 | import Divider from "@material-ui/core/Divider"; 7 | import Select from "@material-ui/core/Select"; 8 | import FormControl from "@material-ui/core/FormControl"; 9 | import InputLabel from "@material-ui/core/InputLabel"; 10 | import MenuItem from "@material-ui/core/MenuItem"; 11 | import PropTypes from "prop-types"; 12 | import { makeStyles } from "@material-ui/core/styles"; 13 | 14 | import Paper from "../Paper"; 15 | import { TaskType } from "TinyManager/types"; 16 | import { identity, noop } from "../utils"; 17 | 18 | const PRIORITIES = [ 19 | { 20 | value: 0, 21 | label: "Low", 22 | }, 23 | { 24 | value: 1, 25 | label: "Medium", 26 | }, 27 | { 28 | value: 2, 29 | label: "High", 30 | }, 31 | ]; 32 | 33 | const PERCENTAGES = new Array(11) 34 | .fill(0) 35 | .map((_, i) => ({ value: i * 10, label: `${i * 10}%` })); 36 | 37 | const useStyles = makeStyles((theme) => ({ 38 | paper: { 39 | padding: theme.spacing(2), 40 | height: "auto", 41 | }, 42 | })); 43 | 44 | function TaskForm(props) { 45 | const { 46 | title, 47 | values, 48 | errors, 49 | disabled, 50 | translate, 51 | onSubmit, 52 | onChange, 53 | onCancel, 54 | } = props; 55 | 56 | const classes = useStyles(); 57 | return ( 58 |
59 | 60 | 61 | 62 | 63 | {title} 64 | 65 | 66 | 67 | 68 | 69 | 82 | 83 | 84 | 90 | {translate("Percentage")} 91 | 105 | 106 | 107 | 108 | 114 | {translate("Priority")} 115 | 129 | 130 | 131 | 132 | 145 | 146 | 147 | 148 | 151 |   152 | 160 | 161 | 162 | 163 |
164 | ); 165 | } 166 | 167 | TaskForm.propTypes = { 168 | title: PropTypes.string, 169 | disabled: PropTypes.bool, 170 | values: TaskType, 171 | errors: TaskType, 172 | translate: PropTypes.func, 173 | onChange: PropTypes.func, 174 | onSubmit: PropTypes.func, 175 | onCancel: PropTypes.func, 176 | }; 177 | 178 | TaskForm.defaultProps = { 179 | values: {}, 180 | errors: {}, 181 | disabled: false, 182 | title: "New Task", 183 | translate: identity, 184 | onChange: noop, 185 | onSubmit: (e) => { 186 | e.preventDefault(); 187 | }, 188 | onCancel: noop, 189 | }; 190 | 191 | export default TaskForm; 192 | -------------------------------------------------------------------------------- /src/TinyManager/components/TaskForm/TaskForm.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import TaskForm from "./TaskForm"; 4 | import { withLargeWrapper } from "TinyManager/decorators"; 5 | 6 | export default { 7 | title: "TinyManager/TaskForm", 8 | component: TaskForm, 9 | decorators: [withLargeWrapper], 10 | }; 11 | 12 | const Template = (args) => { 13 | return ; 14 | }; 15 | 16 | export const Default = Template.bind({}); 17 | Default.args = {}; 18 | 19 | export const Disabled = Template.bind({}); 20 | Disabled.args = { 21 | disabled: true, 22 | }; 23 | -------------------------------------------------------------------------------- /src/TinyManager/components/TaskForm/index.js: -------------------------------------------------------------------------------- 1 | import TaskForm from "./TaskForm"; 2 | 3 | export default TaskForm; 4 | -------------------------------------------------------------------------------- /src/TinyManager/components/Todo/Todo.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import PropTypes from "prop-types"; 3 | import ListItem from "@material-ui/core/ListItem"; 4 | import ListItemIcon from "@material-ui/core/ListItemIcon"; 5 | import ListItemText from "@material-ui/core/ListItemText"; 6 | import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction"; 7 | import Checkbox from "@material-ui/core/Checkbox"; 8 | import { makeStyles } from "@material-ui/core/styles"; 9 | 10 | import IconButton from "TinyManager/components/IconButton"; 11 | import { TodoType } from "TinyManager/types"; 12 | 13 | const useStyles = makeStyles({ 14 | item: { 15 | listStyle: "none", 16 | }, 17 | }); 18 | 19 | function Todo(props) { 20 | const { todo, divider, onClick, onCheck, onDelete } = props; 21 | const { title, completed } = todo; 22 | 23 | const classes = useStyles(); 24 | 25 | const handleCheck = useCallback( 26 | (event) => { 27 | if (onCheck) { 28 | onCheck(event, todo); 29 | } 30 | }, 31 | [onCheck, todo] 32 | ); 33 | 34 | const handleDelete = useCallback( 35 | (event) => { 36 | if (onDelete) { 37 | onDelete(event, todo); 38 | } 39 | }, 40 | [onDelete, todo] 41 | ); 42 | 43 | const handleClick = useCallback( 44 | (event) => { 45 | if (onClick) { 46 | onClick(event, todo); 47 | } 48 | }, 49 | [onClick, todo] 50 | ); 51 | 52 | return ( 53 | 59 | 60 | 67 | 68 | 75 | 76 | 82 | 83 | 84 | ); 85 | } 86 | 87 | Todo.propTypes = { 88 | /** 89 | * Todo 90 | */ 91 | todo: TodoType, 92 | /** 93 | * Divider beneath todo 94 | */ 95 | divider: PropTypes.bool, 96 | /** 97 | * Todo on check handler 98 | */ 99 | onCheck: PropTypes.func, 100 | /** 101 | * Todo on delete handler 102 | */ 103 | onDelete: PropTypes.func, 104 | /** 105 | * Todo on click handler 106 | */ 107 | onClick: PropTypes.func, 108 | }; 109 | 110 | Todo.defaultProps = { 111 | todo: {}, 112 | divider: true, 113 | }; 114 | 115 | export default Todo; 116 | -------------------------------------------------------------------------------- /src/TinyManager/components/Todo/Todo.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Todo from "./Todo"; 4 | import { withWrapper } from "TinyManager/decorators"; 5 | 6 | const argTypes = { 7 | todo: { 8 | control: { 9 | type: null, 10 | }, 11 | }, 12 | divider: { 13 | control: { 14 | type: "boolean", 15 | }, 16 | }, 17 | }; 18 | 19 | export default { 20 | title: "TinyManager/Todo", 21 | component: Todo, 22 | argTypes, 23 | decorators: [withWrapper], 24 | }; 25 | 26 | const Template = (args) => { 27 | return ; 28 | }; 29 | 30 | export const Default = Template.bind({}); 31 | Default.args = { 32 | todo: { title: "Todo task" }, 33 | }; 34 | 35 | export const Completed = Template.bind({}); 36 | Completed.args = { 37 | todo: { ...Default.args.todo, completed: true }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/TinyManager/components/Todo/index.js: -------------------------------------------------------------------------------- 1 | import Todo from "./Todo"; 2 | 3 | export default Todo; 4 | -------------------------------------------------------------------------------- /src/TinyManager/components/TodoForm/TodoForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import TextField from "@material-ui/core/TextField"; 4 | import Grid from "@material-ui/core/Grid"; 5 | import Button from "@material-ui/core/Button"; 6 | import Typography from "@material-ui/core/Typography"; 7 | import Divider from "@material-ui/core/Divider"; 8 | import { makeStyles } from "@material-ui/core/styles"; 9 | 10 | import { TodoType } from "TinyManager/types"; 11 | import { identity, noop } from "../utils"; 12 | 13 | const useStyles = makeStyles((theme) => ({ 14 | form: { 15 | padding: theme.spacing(2), 16 | height: "auto", 17 | }, 18 | })); 19 | 20 | function TodoForm(props) { 21 | const { 22 | title, 23 | values, 24 | errors, 25 | disabled, 26 | translate, 27 | onChange, 28 | onSubmit, 29 | onCancel, 30 | } = props; 31 | 32 | const classes = useStyles(); 33 | return ( 34 |
35 | 36 | 37 | 38 | {title} 39 | 40 | 41 | 42 | 43 | 57 | 58 | 59 | 62 |   63 | 71 | 72 | 73 |
74 | ); 75 | } 76 | 77 | TodoForm.propTypes = { 78 | title: PropTypes.string, 79 | values: TodoType, 80 | errors: TodoType, 81 | disabled: PropTypes.bool, 82 | translate: PropTypes.func, 83 | onChange: PropTypes.func, 84 | onSubmit: PropTypes.func, 85 | onCancel: PropTypes.func, 86 | }; 87 | 88 | TodoForm.defaultProps = { 89 | title: "Add Todo", 90 | values: {}, 91 | errors: {}, 92 | disabled: false, 93 | translate: identity, 94 | onSubmit: (e) => e.preventDefault(), 95 | onChange: noop, 96 | onCancel: noop, 97 | }; 98 | 99 | export default TodoForm; 100 | -------------------------------------------------------------------------------- /src/TinyManager/components/TodoForm/TodoForm.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import TodoForm from "./TodoForm"; 4 | import { TodoType } from "TinyManager/types"; 5 | import { withLargeWrapper } from "TinyManager/decorators"; 6 | 7 | const argTypes = { 8 | title: { 9 | label: "title", 10 | description: "Title of form", 11 | type: { name: "string" }, 12 | control: { 13 | type: "string", 14 | }, 15 | }, 16 | disabled: { 17 | label: "disabled", 18 | description: "Disable state of form", 19 | type: { 20 | name: "boolean", 21 | }, 22 | control: { 23 | type: "boolean", 24 | }, 25 | }, 26 | values: { 27 | label: "values", 28 | description: "Values of the form", 29 | type: TodoType, 30 | control: { 31 | type: null, 32 | }, 33 | }, 34 | errors: { 35 | label: "errors", 36 | description: "Errors of the form", 37 | type: TodoType, 38 | control: { 39 | type: null, 40 | }, 41 | }, 42 | onChange: { 43 | label: "onChange", 44 | description: "Form change handler", 45 | type: "func", 46 | control: { 47 | type: null, 48 | }, 49 | }, 50 | onCancel: { 51 | label: "onCancel", 52 | description: "Form cancel handler", 53 | type: "func", 54 | control: { 55 | type: null, 56 | }, 57 | }, 58 | onSubmit: { 59 | label: "onSubmit", 60 | description: "Form submit handler", 61 | type: "func", 62 | control: { 63 | type: null, 64 | }, 65 | }, 66 | }; 67 | 68 | export default { 69 | title: "TinyManager/TodoForm", 70 | component: TodoForm, 71 | argTypes, 72 | decorators: [withLargeWrapper], 73 | }; 74 | 75 | const Template = (args) => ; 76 | 77 | export const Default = Template.bind({}); 78 | Default.args = {}; 79 | 80 | export const Disabled = Template.bind({}); 81 | Disabled.args = { disabled: true }; 82 | 83 | export const Title = Template.bind({}); 84 | Title.args = { title: "Make Coffee" }; 85 | 86 | export const Error = Template.bind({}); 87 | Error.args = { errors: { title: "Title is required" } }; 88 | -------------------------------------------------------------------------------- /src/TinyManager/components/TodoForm/index.js: -------------------------------------------------------------------------------- 1 | import TodoForm from "./TodoForm"; 2 | 3 | export default TodoForm; 4 | -------------------------------------------------------------------------------- /src/TinyManager/components/TodoList/TodoList.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import List from "@material-ui/core/List"; 4 | import Typography from "@material-ui/core/Typography"; 5 | import { makeStyles } from "@material-ui/core/styles"; 6 | 7 | import Todo from "../Todo"; 8 | import { TodoType } from "TinyManager/types"; 9 | import { noop } from "../utils"; 10 | 11 | const useStyles = makeStyles({ 12 | list: { 13 | overflow: "auto", 14 | height: "90%", 15 | }, 16 | }); 17 | 18 | function TodoList(props) { 19 | const { todos, translate, onClick, onCheck, onDelete } = props; 20 | 21 | const classes = useStyles(); 22 | 23 | if (todos.length === 0) { 24 | return ( 25 | 26 | {translate("Nothing to be completed. Enjoy your day.")} 27 | 28 | ); 29 | } 30 | 31 | return ( 32 | 33 | {todos.map((todo) => ( 34 | 41 | ))} 42 | 43 | ); 44 | } 45 | 46 | TodoList.propTypes = { 47 | /** 48 | * List of todos 49 | */ 50 | todos: PropTypes.arrayOf(TodoType), 51 | /** 52 | * Translate 53 | */ 54 | translate: PropTypes.func, 55 | /** 56 | * Todo on click handler 57 | */ 58 | onClick: PropTypes.func, 59 | /** 60 | * Todo on check handler 61 | */ 62 | onCheck: PropTypes.func, 63 | /** 64 | * Todo on delete handler 65 | */ 66 | onDelete: PropTypes.func, 67 | }; 68 | 69 | TodoList.defaultProps = { 70 | todos: [], 71 | onClick: noop, 72 | onCheck: noop, 73 | }; 74 | 75 | export default TodoList; 76 | -------------------------------------------------------------------------------- /src/TinyManager/components/TodoList/TodoList.stories.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import TodoList from "./TodoList"; 4 | import { withWrapper } from "TinyManager/decorators"; 5 | 6 | const argTypes = { 7 | todos: { 8 | name: "todos", 9 | description: "List of todo", 10 | type: { 11 | name: "array", 12 | }, 13 | control: { 14 | type: null, 15 | }, 16 | }, 17 | onTodoClick: { 18 | name: "onTodoClick", 19 | description: "Todo click handler", 20 | type: { 21 | name: "function", 22 | }, 23 | control: { 24 | type: null, 25 | }, 26 | }, 27 | }; 28 | 29 | export default { 30 | title: "TinyManager/TodoList", 31 | component: TodoList, 32 | argTypes, 33 | decorators: [withWrapper], 34 | }; 35 | 36 | const Template = (args) => { 37 | return ; 38 | }; 39 | 40 | export const Default = Template.bind({}); 41 | Default.args = { 42 | todos: new Array(10) 43 | .fill(0) 44 | .map((_, id) => ({ id, title: `Todo - ${id}`, completed: id % 2 === 0 })), 45 | }; 46 | 47 | export const Empty = Template.bind({}); 48 | Empty.args = { 49 | todos: [], 50 | }; 51 | -------------------------------------------------------------------------------- /src/TinyManager/components/TodoList/index.js: -------------------------------------------------------------------------------- 1 | import TodoList from "./TodoList"; 2 | 3 | export default TodoList; 4 | -------------------------------------------------------------------------------- /src/TinyManager/components/Topbar/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import PropTypes from "prop-types"; 4 | import MuiAppBar from "@material-ui/core/AppBar"; 5 | import Toolbar from "@material-ui/core/Toolbar"; 6 | import Button from "@material-ui/core/Button"; 7 | import Typography from "@material-ui/core/Typography"; 8 | import Menu from "@material-ui/core/Menu"; 9 | import MenuItem from "@material-ui/core/MenuItem"; 10 | import MuiLink from "@material-ui/core/Link"; 11 | import Box from "@material-ui/core/Box"; 12 | import { makeStyles, withStyles } from "@material-ui/core/styles"; 13 | import IconButton from "TinyManager/components/IconButton"; 14 | import { identity } from "TinyManager/components/utils"; 15 | import ListIcon from "@material-ui/icons/List"; 16 | 17 | const AppBar = withStyles((theme) => ({ 18 | root: { 19 | boxShadow: theme.custom.shadow[0], 20 | }, 21 | }))(MuiAppBar); 22 | 23 | const useStyles = makeStyles((theme) => ({ 24 | title: { flexGrow: 1, cursor: "pointer" }, 25 | icon: { 26 | color: theme.palette.primary.main, 27 | }, 28 | })); 29 | 30 | function DarkThemeButton(props) { 31 | const { onClick } = props; 32 | return ( 33 | 39 | ); 40 | } 41 | 42 | DarkThemeButton.propTypes = { 43 | onClick: PropTypes.func, 44 | }; 45 | 46 | function GithubIconButton() { 47 | return ( 48 | 55 | ); 56 | } 57 | 58 | function LanguageSelection(props) { 59 | const { languages, onChange } = props; 60 | 61 | const [languageAnchorEl, setLanguageAnchorEl] = useState(null); 62 | 63 | const openLanguageSelection = (event) => { 64 | setLanguageAnchorEl(event.currentTarget); 65 | }; 66 | 67 | const closeLanguageSelection = () => { 68 | setLanguageAnchorEl(null); 69 | }; 70 | 71 | const handleLanguageChange = (language) => (event) => { 72 | if (onChange) { 73 | onChange(event, language); 74 | closeLanguageSelection(); 75 | } 76 | }; 77 | 78 | return ( 79 | <> 80 | 87 | 94 | {languages.map(({ value, label }) => ( 95 | 96 | {label} 97 | 98 | ))} 99 | 100 | 101 | ); 102 | } 103 | 104 | LanguageSelection.propTypes = { 105 | languages: PropTypes.array, 106 | onChange: PropTypes.func, 107 | }; 108 | 109 | LanguageSelection.defaultProps = { 110 | languages: [], 111 | }; 112 | 113 | function Topbar(props) { 114 | const { 115 | onToggleDarkMode, 116 | languages = [], 117 | onlanguageChange, 118 | translate, 119 | } = props; 120 | 121 | const classes = useStyles(); 122 | 123 | return ( 124 | <> 125 | 126 | 127 | 128 | 129 | 130 | {translate("Tiny Manager")}  131 | 132 | 133 | 134 | 137 |   138 | 141 | 142 |   143 | 147 |   148 | 149 | 150 | 151 | 152 | 153 | ); 154 | } 155 | 156 | Topbar.propTypes = { 157 | onToggleDarkMode: PropTypes.func, 158 | onlanguageChange: PropTypes.func, 159 | languages: PropTypes.array, 160 | translate: PropTypes.func, 161 | }; 162 | 163 | Topbar.defaultProps = { 164 | translate: identity, 165 | }; 166 | 167 | export default Topbar; 168 | -------------------------------------------------------------------------------- /src/TinyManager/components/utils.js: -------------------------------------------------------------------------------- 1 | function noop() {} 2 | 3 | function identity(value) { 4 | return value; 5 | } 6 | 7 | export { noop, identity }; 8 | -------------------------------------------------------------------------------- /src/TinyManager/containers/Content/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Switch, Route } from "react-router-dom"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | 5 | import Topbar from "TinyManager/containers/Topbar"; 6 | import Home from "TinyManager/containers/Home"; 7 | import Projects from "TinyManager/containers/Projects"; 8 | 9 | const useStyles = makeStyles((theme) => ({ 10 | root: { 11 | padding: theme.spacing(1), 12 | height: "100%", 13 | }, 14 | main: { 15 | display: "flex", 16 | height: `calc(100% - ${theme.spacing(8)}px)`, // $topbar height 17 | width: "100%", 18 | overflow: "auto", 19 | }, 20 | })); 21 | 22 | function Content() { 23 | const classes = useStyles(); 24 | return ( 25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 |
33 |
34 | ); 35 | } 36 | 37 | export default Content; 38 | -------------------------------------------------------------------------------- /src/TinyManager/containers/Home/index.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from "react"; 2 | import Fade from "@material-ui/core/Fade"; 3 | import Typography from "@material-ui/core/Typography"; 4 | import Tabs from "@material-ui/core/Tabs"; 5 | import Tab from "@material-ui/core/Tab"; 6 | import FormControlLabel from "@material-ui/core/FormControlLabel"; 7 | import Checkbox from "@material-ui/core/Checkbox"; 8 | import { makeStyles } from "@material-ui/core/styles"; 9 | 10 | import Notes from "TinyManager/containers/Notes"; 11 | import Todos from "TinyManager/containers/Todos"; 12 | import QuoteService from "TinyManager/services/QuoteService"; 13 | import TinyManagerAPI from "TinyManager/services/TinyManagerAPI"; 14 | import { useTranslation } from "TinyManager/providers/TranslationProvider"; 15 | 16 | const TAB = { 17 | Todos: 0, 18 | Notes: 1, 19 | }; 20 | 21 | const useStyles = makeStyles(() => ({ 22 | container: { 23 | display: "flex", 24 | flex: 1, 25 | justifyContent: "center", 26 | }, 27 | content: { 28 | width: "100%", 29 | height: "100%", 30 | display: "flex", 31 | flex: 1, 32 | flexDirection: "column", 33 | alignItems: "center", 34 | maxWidth: 500, 35 | }, 36 | checkbox: { 37 | alignSelf: "flex-end", 38 | }, 39 | })); 40 | 41 | const quote = QuoteService.getQuote(); 42 | 43 | function Home() { 44 | const classes = useStyles(); 45 | const { t } = useTranslation(); 46 | 47 | const defaultNotes = TinyManagerAPI.fetchDefaultNotes(); 48 | 49 | const [tab, setTab] = useState(defaultNotes ? TAB.Notes : TAB.Todos); 50 | 51 | const [checked, setChecked] = useState(defaultNotes); 52 | 53 | const handleChangeTab = useCallback((event, tab) => { 54 | setTab(tab); 55 | }, []); 56 | 57 | const handleChangeDefaultNotes = useCallback((event) => { 58 | const { checked } = event.target; 59 | setChecked(checked); 60 | TinyManagerAPI.updateDefaultNotes(checked); 61 | }, []); 62 | 63 | return ( 64 | 65 |
66 |
67 |
68 | 69 | {quote} 70 | 71 |
72 |
73 | 80 | } 81 | label={t("Default Notes")} 82 | /> 83 |
84 |
85 | 91 | 92 | 93 | 94 |
95 | {tab === TAB.Todos && } 96 | {tab === TAB.Notes && } 97 |
98 |
99 |
100 | ); 101 | } 102 | 103 | export default Home; 104 | -------------------------------------------------------------------------------- /src/TinyManager/containers/Notes/index.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import debounce from "lodash.debounce"; 5 | import Box from "@material-ui/core/Box"; 6 | import TextField from "@material-ui/core/TextField"; 7 | import Typography from "@material-ui/core/Typography"; 8 | import IconButton from "@material-ui/core/IconButton"; 9 | import Menu from "@material-ui/core/Menu"; 10 | import MenuItem from "@material-ui/core/MenuItem"; 11 | import jsPDF from "jspdf"; 12 | 13 | import DownloadIcon from "@material-ui/icons/SaveAlt"; 14 | import ClearIcon from "@material-ui/icons/DeleteForever"; 15 | 16 | import TinyManagerAPI from "TinyManager/services/TinyManagerAPI"; 17 | import { useTranslation } from "TinyManager/providers/TranslationProvider"; 18 | import { formatDate } from "TinyManager/services/Utils"; 19 | 20 | const useStyles = makeStyles((theme) => ({ 21 | container: { 22 | display: "flex", 23 | width: "100%", 24 | overflow: "hidden", 25 | flexDirection: "column", 26 | flex: 1, 27 | }, 28 | action: { 29 | marginBottom: theme.spacing(1), 30 | }, 31 | input: { 32 | display: "flex", 33 | overflow: "auto", 34 | flex: 1, 35 | }, 36 | inputRoot: { 37 | flex: 1, 38 | }, 39 | inputControl: { 40 | flex: 1, 41 | display: "flex", 42 | flexDirection: "column", 43 | overflow: "hidden", 44 | }, 45 | caption: { 46 | display: "block", 47 | textAlign: "right", 48 | marginTop: theme.spacing(1), 49 | }, 50 | })); 51 | 52 | function DownloadButton(props) { 53 | const { notes } = props; 54 | 55 | const { t } = useTranslation(); 56 | const [anchorEl, setAnchorEl] = useState(null); 57 | 58 | const openMenu = useCallback((event) => { 59 | setAnchorEl(event.currentTarget); 60 | }, []); 61 | 62 | const closeMenu = useCallback(() => { 63 | setAnchorEl(null); 64 | }, []); 65 | 66 | const handleMenuItemClick = useCallback( 67 | (cb) => (event) => { 68 | closeMenu(); 69 | cb(event); 70 | }, 71 | [closeMenu] 72 | ); 73 | 74 | return ( 75 | <> 76 | 83 | 84 | 85 | 91 | downloadTxt(notes))}> 92 | {t("TXT")} 93 | 94 | downloadPdf(notes))}> 95 | {t("PDF")} 96 | 97 | 98 | 99 | ); 100 | } 101 | 102 | DownloadButton.propTypes = { 103 | notes: PropTypes.string, 104 | }; 105 | 106 | const updateStorageNotes = debounce(TinyManagerAPI.updateNotes, 150); 107 | 108 | function fileName(extension = "txt") { 109 | return `Note-${formatDate()}.${extension}`; 110 | } 111 | 112 | function downloadTxt(string) { 113 | const url = window.URL.createObjectURL(new Blob([string]), { 114 | type: "text/plain", 115 | }); 116 | 117 | const link = document.createElement("a"); 118 | link.href = url; 119 | link.setAttribute("download", fileName()); 120 | link.click(); 121 | } 122 | 123 | function downloadPdf(string) { 124 | new jsPDF().text(string, 10, 10).save(fileName("pdf")); 125 | } 126 | 127 | function Notes() { 128 | const classes = useStyles(); 129 | const { t } = useTranslation(); 130 | 131 | const [notes, setNotes] = useState(TinyManagerAPI.fetchNotes()); 132 | 133 | const handleChange = useCallback((event) => { 134 | const { value } = event.target; 135 | setNotes(value); 136 | updateStorageNotes(value); 137 | }, []); 138 | 139 | const handleClearNote = useCallback(() => { 140 | handleChange({ target: { value: "" } }); 141 | }, [handleChange]); 142 | 143 | const handleKeyDown = useCallback( 144 | (event) => { 145 | const isSave = event.key === "s" && event.ctrlKey === true; 146 | if (isSave) { 147 | event.preventDefault(); 148 | downloadTxt(notes); 149 | } 150 | }, 151 | [notes] 152 | ); 153 | 154 | return ( 155 |
156 | 162 | 163 | 169 | 170 | 171 | 172 |
173 | 193 |
194 | 199 | * 200 | {t( 201 | "Notes will be stored locally on the browser and will be persisted." 202 | )} 203 | 204 |
205 | ); 206 | } 207 | 208 | export default Notes; 209 | -------------------------------------------------------------------------------- /src/TinyManager/containers/Projects/ProjectForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useFormik } from "formik"; 3 | import PropTypes from "prop-types"; 4 | import { makeStyles } from "@material-ui/core/styles"; 5 | 6 | import ProjectForm from "TinyManager/components/ProjectForm"; 7 | import { ProjectType } from "TinyManager/types/index"; 8 | import { merge } from "TinyManager/services/Utils"; 9 | import { useTranslation } from "TinyManager/providers/TranslationProvider"; 10 | 11 | const useStyles = makeStyles(() => ({ 12 | container: { 13 | maxWidth: 500, 14 | marginRight: "auto", 15 | marginLeft: "auto", 16 | }, 17 | })); 18 | 19 | function ProjectFormContainer(props) { 20 | const { onCancel, onSubmit, initialValues } = props; 21 | 22 | const classes = useStyles(); 23 | const { t } = useTranslation(); 24 | 25 | const { 26 | values, 27 | errors, 28 | isSubmitting, 29 | handleChange, 30 | handleSubmit, 31 | } = useFormik({ 32 | initialValues: merge({ name: "", description: "" }, initialValues), 33 | validate: (values) => { 34 | const errors = {}; 35 | 36 | if (!values.name) { 37 | errors.name = t("Name is required."); 38 | } 39 | 40 | return errors; 41 | }, 42 | onSubmit: (values, { setSubmitting }) => { 43 | onSubmit(values); 44 | setSubmitting(false); 45 | }, 46 | }); 47 | 48 | const edit = values && values.id; 49 | const title = edit ? t("Edit Project") : t("New Project"); 50 | 51 | return ( 52 |
53 | 63 |
64 | ); 65 | } 66 | 67 | ProjectFormContainer.propTypes = { 68 | initialValues: ProjectType, 69 | onCancel: PropTypes.func, 70 | onSubmit: PropTypes.func, 71 | }; 72 | 73 | export default ProjectFormContainer; 74 | -------------------------------------------------------------------------------- /src/TinyManager/containers/Projects/ProjectList.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useMemo, useState } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import Grid from "@material-ui/core/Grid"; 5 | import Button from "@material-ui/core/Button"; 6 | import Typography from "@material-ui/core/Typography"; 7 | import Fade from "@material-ui/core/Fade"; 8 | 9 | import Loader from "TinyManager/components/Loader"; 10 | import ProjectCard from "TinyManager/components/ProjectCard"; 11 | import TinyManagerAPI from "TinyManager/services/TinyManagerAPI"; 12 | import { useTranslation } from "TinyManager/providers/TranslationProvider"; 13 | import useDialog from "TinyManager/hooks/useDialog"; 14 | import ConfirmationDialog from "TinyManager/components/ConfirmationDialog/ConfirmationDialog"; 15 | import OutlinedSelect from "TinyManager/components/OutlinedSelect"; 16 | 17 | function sortProjectsBy(projects, filter) { 18 | return projects.sort((a, b) => { 19 | if (b[filter] < a[filter]) { 20 | return -1; 21 | } 22 | if (b[filter] > a[filter]) { 23 | return 1; 24 | } 25 | return 0; 26 | }); 27 | } 28 | 29 | function filterProjectsBy(projects, filter) { 30 | switch (filter) { 31 | case "pending": 32 | return projects.filter((t) => t.progress !== 100); 33 | case "completed": 34 | return projects.filter((t) => t.progress === 100); 35 | case "all": 36 | default: 37 | return projects; 38 | } 39 | } 40 | const useStyles = makeStyles((theme) => ({ 41 | container: { 42 | display: "flex", 43 | flexDirection: "column", 44 | flex: 1, 45 | overflow: "hidden", 46 | }, 47 | list: { 48 | flex: 1, 49 | marginTop: theme.spacing(), 50 | overflow: "auto", 51 | padding: theme.spacing(3.5), 52 | }, 53 | card: { 54 | marginBottom: theme.spacing(3.5), 55 | }, 56 | action: { 57 | marginTop: theme.spacing(1), 58 | }, 59 | })); 60 | 61 | function ProjectList(props) { 62 | const { onClick, onNew } = props; 63 | 64 | const classes = useStyles(); 65 | const { t } = useTranslation(); 66 | const [loading, setLoading] = useState(true); 67 | const [projects, setProjects] = useState([]); 68 | const [project, setProject] = useState(null); 69 | const [deleteDialog, setDeleteDialog] = useState(false); 70 | const [filterBy, setFilterBy] = useState("all"); 71 | const [sortBy, setSortBy] = useState("name"); 72 | 73 | const [ 74 | deleteAllProjectDialog, 75 | openDeleteAllProjectDialog, 76 | closeDeleteAllProjectDialog, 77 | ] = useDialog(); 78 | 79 | const sortByMenu = useMemo( 80 | () => [ 81 | { 82 | label: t("Progress"), 83 | value: "progress", 84 | }, 85 | { 86 | label: t("Name"), 87 | value: "name", 88 | }, 89 | ], 90 | [t] 91 | ); 92 | 93 | const filterByMenu = useMemo( 94 | () => [ 95 | { label: t("All"), value: "all" }, 96 | { label: t("Pending"), value: "pending" }, 97 | { label: t("Completed"), value: "completed" }, 98 | ], 99 | [t] 100 | ); 101 | 102 | useEffect(() => { 103 | TinyManagerAPI.fetchProjects() 104 | .then(async (projects) => { 105 | const tasks = await Promise.all( 106 | projects.map((project) => 107 | TinyManagerAPI.fetchTasks(Number(project.id)) 108 | ) 109 | ); 110 | 111 | projects = projects.map((project, i) => { 112 | const projectTasks = tasks[i]; 113 | project.tasks = projectTasks; 114 | project.progress = 115 | projectTasks && projectTasks.length 116 | ? projectTasks.reduce( 117 | (progress, task) => 118 | progress + 119 | ((100 / projectTasks.length) * Number(task.progress)) / 100, 120 | 0 121 | ) 122 | : 0; 123 | return project; 124 | }); 125 | 126 | setLoading(false); 127 | setProjects(projects); 128 | }) 129 | .catch(() => { 130 | setLoading(false); 131 | }); 132 | }, []); 133 | 134 | const handleProjectClick = (event, project) => { 135 | onClick(project.id); 136 | }; 137 | 138 | const openDeleteDialog = useCallback((event, project) => { 139 | event.stopPropagation(); 140 | setDeleteDialog(true); 141 | setProject(project); 142 | }, []); 143 | 144 | const closeDeleteDialog = useCallback(() => { 145 | setDeleteDialog(false); 146 | setProject(null); 147 | }, []); 148 | 149 | const handleDeleteProject = useCallback(() => { 150 | const { id } = project; 151 | 152 | TinyManagerAPI.removeProject(id).then(() => { 153 | setProjects((projects) => projects.filter((p) => p.id !== id)); 154 | }); 155 | 156 | closeDeleteDialog(); 157 | }, [project, closeDeleteDialog]); 158 | 159 | const handleDeleteAllProject = useCallback(() => { 160 | if (projects.length) { 161 | const { projectIds, taskIds } = projects.reduce( 162 | ({ projectIds, taskIds }, project) => { 163 | return { 164 | projectIds: [...projectIds, project.id], 165 | taskIds: [...taskIds, ...project.tasks.map((t) => t.id)], 166 | }; 167 | }, 168 | { projectIds: [], taskIds: [] } 169 | ); 170 | 171 | TinyManagerAPI.removeBulkProjects(projectIds).then(async () => { 172 | await TinyManagerAPI.removeBulkTasks(taskIds); 173 | setProjects([]); 174 | closeDeleteAllProjectDialog(); 175 | }); 176 | } 177 | }, [closeDeleteAllProjectDialog, projects]); 178 | 179 | const handleChangeFilterBy = useCallback((event) => { 180 | setFilterBy(event.target.value); 181 | }, []); 182 | 183 | const handleChangeSortBy = useCallback((event) => { 184 | setSortBy(event.target.value); 185 | }, []); 186 | 187 | const hasProjects = projects && projects.length; 188 | 189 | let $projects = sortProjectsBy(projects, sortBy); 190 | $projects = filterProjectsBy(projects, filterBy); 191 | 192 | if (loading) { 193 | return ; 194 | } 195 | 196 | return ( 197 | 198 |
199 |
200 | 201 | {t("Projects")} 202 | 203 | 206 |    207 | 215 |
216 |
217 | 226 |   227 | 235 |
236 |
237 | {hasProjects && $projects.length ? ( 238 | 239 | {$projects.map((project) => ( 240 | 241 | 249 | 250 | ))} 251 | 252 | ) : ( 253 | 254 | {t("No available projects.")} 255 | 256 | )} 257 |
258 | 268 | 276 |
277 |
278 | ); 279 | } 280 | 281 | ProjectList.propTypes = { 282 | onNew: PropTypes.func, 283 | onClick: PropTypes.func, 284 | }; 285 | 286 | export default ProjectList; 287 | -------------------------------------------------------------------------------- /src/TinyManager/containers/Projects/ProjectView.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useMemo, useState } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import Typography from "@material-ui/core/Typography"; 5 | import Grid from "@material-ui/core/Grid"; 6 | import Button from "@material-ui/core/Button"; 7 | import Fade from "@material-ui/core/Fade"; 8 | import Dialog from "@material-ui/core/Dialog"; 9 | 10 | import TinyManagerAPI from "TinyManager/services/TinyManagerAPI"; 11 | import useDialog from "TinyManager/hooks/useDialog"; 12 | import { useTranslation } from "TinyManager/providers/TranslationProvider"; 13 | import TaskFormContainer from "TinyManager/containers/Projects/TaskForm"; 14 | import ProjectFormContainer from "TinyManager/containers/Projects/ProjectForm"; 15 | import ProjectCard from "TinyManager/components/ProjectCard"; 16 | import Loader from "TinyManager/components/Loader"; 17 | import TaskCard from "TinyManager/components/TaskCard"; 18 | import ConfirmationDialog from "TinyManager/components/ConfirmationDialog"; 19 | import OutlinedSelect from "TinyManager/components/OutlinedSelect"; 20 | 21 | const useStyles = makeStyles((theme) => ({ 22 | container: { 23 | display: "flex", 24 | flexDirection: "column", 25 | flex: 1, 26 | }, 27 | noTasksMessageWrapper: { 28 | marginTop: theme.spacing(), 29 | marginRight: "auto", 30 | marginLeft: "auto", 31 | maxWidth: 500, 32 | }, 33 | noTasksMessage: { 34 | minWidth: 500, 35 | }, 36 | selection: { 37 | marginRight: theme.spacing(), 38 | }, 39 | list: { 40 | padding: `${theme.spacing(3)}px ${theme.spacing(2)}px`, 41 | height: "85%", 42 | overflow: "auto", 43 | alignContent: "baseline", 44 | }, 45 | action: { 46 | maxWidth: 500, 47 | marginRight: "auto", 48 | marginLeft: "auto", 49 | }, 50 | })); 51 | 52 | function sortTasksBy(tasks, filter) { 53 | return tasks.sort((a, b) => { 54 | if (b[filter] < a[filter]) { 55 | return -1; 56 | } 57 | if (b[filter] > a[filter]) { 58 | return 1; 59 | } 60 | return 0; 61 | }); 62 | } 63 | 64 | function filterTasksBy(tasks, filter) { 65 | switch (filter) { 66 | case "pending": 67 | return tasks.filter((t) => t.progress !== 100); 68 | case "completed": 69 | return tasks.filter((t) => t.progress === 100); 70 | case "all": 71 | default: 72 | return tasks; 73 | } 74 | } 75 | 76 | function ProjectView(props) { 77 | const { match, redirectToProjectList } = props; 78 | const { params } = match; 79 | const { projectId } = params; 80 | 81 | const classes = useStyles(); 82 | 83 | const [{ loading, project, tasks, task }, setStore] = useState({ 84 | loading: true, 85 | project: {}, 86 | tasks: [], 87 | task: {}, 88 | }); 89 | 90 | const [sortBy, setSortBy] = useState("createdAt"); 91 | const [filterBy, setFilterBy] = useState("all"); 92 | const [taskDialog, openTaskDialog, closeTaskDialog] = useDialog(); 93 | const [projectDialog, openProjectDialog, closeProjectDialog] = useDialog(); 94 | const [ 95 | deleteAllTaskDialog, 96 | openDeleteAllTaskDialog, 97 | closeDeleteAllTaskDialog, 98 | ] = useDialog(); 99 | const { t } = useTranslation(); 100 | 101 | const sortByMenu = useMemo( 102 | () => [ 103 | { 104 | label: t("Priority"), 105 | value: "priority", 106 | }, 107 | { 108 | label: t("Progress"), 109 | value: "progress", 110 | }, 111 | { 112 | label: t("Title"), 113 | value: "title", 114 | }, 115 | { 116 | label: t("Created"), 117 | value: "createdAt", 118 | }, 119 | { 120 | label: t("Updated"), 121 | value: "updatedAt", 122 | }, 123 | ], 124 | [t] 125 | ); 126 | 127 | const filterByMenu = useMemo( 128 | () => [ 129 | { label: t("All"), value: "all" }, 130 | { label: t("Pending"), value: "pending" }, 131 | { label: t("Completed"), value: "completed" }, 132 | ], 133 | [t] 134 | ); 135 | 136 | const handleCloseTaskDialog = useCallback(() => { 137 | closeTaskDialog(false); 138 | setStore((store) => ({ ...store, task: {} })); 139 | }, [closeTaskDialog]); 140 | 141 | const handleChangeSortBy = useCallback((event) => { 142 | setSortBy(event.target.value); 143 | }, []); 144 | 145 | const handleChangeFilterBy = useCallback((event) => { 146 | setFilterBy(event.target.value); 147 | }, []); 148 | 149 | const handleUpdateTask = useCallback( 150 | (task) => { 151 | TinyManagerAPI.updateTask(task.id, task).then((task) => { 152 | setStore((store) => ({ 153 | ...store, 154 | tasks: store.tasks.map((t) => (t.id === task.id ? task : t)), 155 | })); 156 | handleCloseTaskDialog(); 157 | }); 158 | }, 159 | [handleCloseTaskDialog] 160 | ); 161 | 162 | const handleUpdateProject = useCallback( 163 | (project) => { 164 | TinyManagerAPI.updateProject(project.id, project).then((project) => { 165 | setStore((store) => ({ 166 | ...store, 167 | project, 168 | })); 169 | closeProjectDialog(); 170 | }); 171 | }, 172 | [closeProjectDialog] 173 | ); 174 | 175 | const handleAddNewTask = useCallback( 176 | (task) => { 177 | TinyManagerAPI.addTask( 178 | Object.assign({ projectId: Number(projectId) }, task) 179 | ).then((task) => { 180 | setStore((store) => ({ ...store, tasks: [task, ...store.tasks] })); 181 | handleCloseTaskDialog(); 182 | }); 183 | }, 184 | [handleCloseTaskDialog, projectId] 185 | ); 186 | 187 | const handleTaskFormSubmit = useCallback( 188 | (task) => { 189 | const handler = task.id ? handleUpdateTask : handleAddNewTask; 190 | handler(task); 191 | }, 192 | [handleUpdateTask, handleAddNewTask] 193 | ); 194 | 195 | const handleTaskClick = useCallback( 196 | (e, task = {}) => { 197 | setStore((store) => ({ ...store, task })); 198 | openTaskDialog(); 199 | }, 200 | [openTaskDialog] 201 | ); 202 | 203 | const handleTaskDelete = useCallback((e, task) => { 204 | TinyManagerAPI.removeTask(task.id); 205 | setStore((store) => ({ 206 | ...store, 207 | tasks: store.tasks.filter((t) => t.id !== task.id), 208 | })); 209 | }, []); 210 | 211 | const handleProjectEditClick = useCallback(() => { 212 | openProjectDialog(); 213 | }, [openProjectDialog]); 214 | 215 | const handleDeleteAllTask = useCallback(() => { 216 | if (tasks.length) { 217 | TinyManagerAPI.removeBulkTasks(tasks.map(({ id }) => id)).then(() => { 218 | setStore((store) => ({ ...store, tasks: [] })); 219 | closeDeleteAllTaskDialog(); 220 | }); 221 | } 222 | }, [closeDeleteAllTaskDialog, tasks]); 223 | 224 | useEffect(() => { 225 | if (projectId) { 226 | setStore((store) => ({ ...store, loading: true })); 227 | TinyManagerAPI.fetchProject(Number(projectId)) 228 | .then((project) => { 229 | TinyManagerAPI.fetchTasks(Number(projectId)).then((tasks) => { 230 | setStore((store) => ({ ...store, loading: false, project, tasks })); 231 | }); 232 | }) 233 | .catch(redirectToProjectList); 234 | } else { 235 | setStore((store) => ({ ...store, loading: false })); 236 | redirectToProjectList(); 237 | } 238 | }, [projectId, redirectToProjectList]); 239 | 240 | if (loading) { 241 | return ; 242 | } 243 | 244 | const hasTasks = tasks && tasks.length; 245 | 246 | let $tasks = sortTasksBy(tasks, sortBy); 247 | $tasks = filterTasksBy($tasks, filterBy); 248 | 249 | return ( 250 | 251 |
252 | 253 | 254 | 262 | progress + 263 | ((100 / tasks.length) * Number(task.progress)) / 100, 264 | 0 265 | ) 266 | : 0 267 | } 268 | /> 269 | 270 | 271 | 278 |    279 | 287 | 288 | 289 | 298 | 306 | 307 | 308 | {$tasks && $tasks.length ? ( 309 | 310 | {$tasks.map((task) => ( 311 | 312 | 317 | 318 | ))} 319 | 320 | ) : ( 321 |
322 | 323 | {t("No available tasks.")} 324 | 325 |
326 | )} 327 | 328 | 333 | 334 | 335 | 336 | 341 | 342 | 349 |
350 |
351 | ); 352 | } 353 | 354 | ProjectView.propTypes = { 355 | redirectToProjectList: PropTypes.func, 356 | match: PropTypes.shape({ 357 | params: PropTypes.object, 358 | }), 359 | }; 360 | 361 | export default ProjectView; 362 | -------------------------------------------------------------------------------- /src/TinyManager/containers/Projects/TaskForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { useFormik } from "formik"; 4 | 5 | import TaskForm from "TinyManager/components/TaskForm"; 6 | import { TaskType } from "TinyManager/types"; 7 | import { useTranslation } from "TinyManager/providers/TranslationProvider"; 8 | 9 | function TaskFormContainer(props) { 10 | const { onCancel, onSubmit, initialValues } = props; 11 | const { t } = useTranslation(); 12 | 13 | const formik = useFormik({ 14 | initialValues: { 15 | title: "", 16 | note: "", 17 | priority: 0, 18 | progress: 0, 19 | ...initialValues, 20 | }, 21 | validate: (values) => { 22 | const errors = {}; 23 | 24 | if (!values.title) { 25 | errors.title = t("Title is required."); 26 | } 27 | 28 | return errors; 29 | }, 30 | onSubmit: (values) => { 31 | onSubmit(values); 32 | }, 33 | enableReinitialize: true, 34 | }); 35 | 36 | const isEdit = initialValues && initialValues.id; 37 | const formTitle = isEdit ? t("Edit Task") : t("New Task"); 38 | 39 | return ( 40 | 50 | ); 51 | } 52 | 53 | TaskFormContainer.propTypes = { 54 | initialValues: TaskType, 55 | onCancel: PropTypes.func, 56 | onSubmit: PropTypes.func, 57 | }; 58 | 59 | export default TaskFormContainer; 60 | -------------------------------------------------------------------------------- /src/TinyManager/containers/Projects/index.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Route, Switch } from "react-router-dom"; 4 | import { makeStyles } from "@material-ui/core/styles"; 5 | 6 | import { join, withProps } from "TinyManager/services/Utils"; 7 | import TinyManagerAPI from "TinyManager/services/TinyManagerAPI"; 8 | import ProjectView from "TinyManager/containers/Projects/ProjectView"; 9 | import ProjectList from "TinyManager/containers/Projects/ProjectList"; 10 | import ProjectForm from "TinyManager/containers/Projects/ProjectForm"; 11 | 12 | const useStyles = makeStyles((theme) => ({ 13 | container: { 14 | flex: 1, 15 | display: "flex", 16 | justifyContent: "center", 17 | }, 18 | content: { 19 | flex: 1, 20 | display: "flex", 21 | padding: theme.spacing(3.5), 22 | }, 23 | })); 24 | 25 | function Projects(props) { 26 | const { match, history } = props; 27 | const { path, url } = match; 28 | 29 | const classes = useStyles(); 30 | 31 | const redirect = useCallback( 32 | (url) => { 33 | history.push(url); 34 | }, 35 | [history] 36 | ); 37 | 38 | const redirectToNewProject = useCallback(() => { 39 | redirect(join(url, "/new")); 40 | }, [redirect, url]); 41 | 42 | const redirectToProjectView = useCallback( 43 | (projectId) => { 44 | redirect(join(url, "/", projectId)); 45 | }, 46 | [url, redirect] 47 | ); 48 | 49 | const redirectToProjectList = useCallback(() => { 50 | redirect(url); 51 | }, [redirect, url]); 52 | 53 | const handleAddNewProject = useCallback( 54 | (values) => { 55 | TinyManagerAPI.addProject(values).then(redirectToProjectList); 56 | }, 57 | [redirectToProjectList] 58 | ); 59 | 60 | return ( 61 |
62 |
63 | 64 | 71 | 77 | 84 | 85 |
86 |
87 | ); 88 | } 89 | 90 | Projects.propTypes = { 91 | history: PropTypes.object, 92 | match: PropTypes.shape({ 93 | path: PropTypes.string, 94 | url: PropTypes.string, 95 | }), 96 | }; 97 | 98 | export default Projects; 99 | -------------------------------------------------------------------------------- /src/TinyManager/containers/Todos/TodoFormDialog.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Dialog, DialogContent } from "@material-ui/core"; 4 | 5 | import TodoForm from "TinyManager/components/TodoForm"; 6 | import { TodoType } from "TinyManager/types"; 7 | import { merge } from "TinyManager/services/Utils"; 8 | import { useTranslation } from "TinyManager/providers/TranslationProvider"; 9 | import { useFormik } from "formik"; 10 | 11 | function TodoFormDialog(props) { 12 | const { initialValue = {}, open, disabled, onClose, onSubmit } = props; 13 | const { t } = useTranslation(); 14 | 15 | const { values, errors, handleChange, handleSubmit } = useFormik({ 16 | initialValues: merge({ title: "" }, initialValue), 17 | validate: (values) => { 18 | const errors = {}; 19 | 20 | if (!values.title) { 21 | errors.title = t("Title is required."); 22 | } 23 | 24 | return errors; 25 | }, 26 | onSubmit: (values) => { 27 | onSubmit(values); 28 | }, 29 | enableReinitialize: true, 30 | }); 31 | 32 | const isEdit = values && values.id; 33 | const formTitle = isEdit ? t("Edit Todo") : t("Add Todo"); 34 | 35 | return ( 36 | 37 | 38 | 48 | 49 | 50 | ); 51 | } 52 | 53 | TodoFormDialog.propTypes = { 54 | initialValue: TodoType, 55 | open: PropTypes.bool, 56 | disabled: PropTypes.bool, 57 | onClose: PropTypes.func, 58 | onSubmit: PropTypes.func, 59 | }; 60 | 61 | export default TodoFormDialog; 62 | -------------------------------------------------------------------------------- /src/TinyManager/containers/Todos/index.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useMemo, useState } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { makeStyles } from "@material-ui/core/styles"; 4 | import Box from "@material-ui/core/Box"; 5 | import Divider from "@material-ui/core/Divider"; 6 | import Button from "@material-ui/core/Button"; 7 | 8 | import TodoFormDialog from "./TodoFormDialog"; 9 | import Loader from "TinyManager/components/Loader"; 10 | import TodoList from "TinyManager/components/TodoList"; 11 | import TinyManagerAPI from "TinyManager/services/TinyManagerAPI"; 12 | import { useTranslation } from "TinyManager/providers/TranslationProvider"; 13 | import useDialog from "TinyManager/hooks/useDialog"; 14 | import ConfirmationDialog from "TinyManager/components/ConfirmationDialog/ConfirmationDialog"; 15 | import OutlinedSelect from "TinyManager/components/OutlinedSelect"; 16 | 17 | const useStyles = makeStyles((theme) => ({ 18 | container: { 19 | flex: 1, 20 | overflow: "hidden", 21 | width: "100%", 22 | display: "flex", 23 | padding: theme.spacing(1), 24 | }, 25 | todosContainer: { 26 | flex: 1, 27 | height: "100%", 28 | textAlign: "right", 29 | }, 30 | todosFilter: { 31 | marginRight: theme.spacing(), 32 | }, 33 | })); 34 | 35 | export const FILTERS = { 36 | Completed: "completed", 37 | Pending: "pending", 38 | All: "all", 39 | }; 40 | 41 | function Todos() { 42 | const [{ todos, todo, loading, saving, edit }, setStore] = useState({ 43 | todos: [], 44 | todo: { title: "" }, 45 | loading: false, 46 | saving: false, 47 | edit: false, 48 | }); 49 | 50 | const [ 51 | deleteAllDialog, 52 | openDeleteAllDialog, 53 | closeDeleteAllDialog, 54 | ] = useDialog(); 55 | 56 | const [filter, setFilter] = useState(FILTERS.All); 57 | const { t } = useTranslation(); 58 | const classes = useStyles(); 59 | 60 | const filterByMenu = useMemo( 61 | () => 62 | Object.keys(FILTERS).map((key) => ({ 63 | label: t(key), 64 | value: FILTERS[key], 65 | })), 66 | [t] 67 | ); 68 | 69 | const filterTodos = useCallback( 70 | (todos = []) => { 71 | switch (filter) { 72 | case FILTERS.Completed: 73 | return todos.filter((t) => t.completed); 74 | case FILTERS.Pending: 75 | return todos.filter((t) => !t.completed); 76 | case FILTERS.All: 77 | default: 78 | return todos; 79 | } 80 | }, 81 | [filter] 82 | ); 83 | 84 | const fetchTodos = useCallback(() => { 85 | setStore((store) => ({ ...store, loading: true })); 86 | 87 | TinyManagerAPI.fetchTodos().then((todos) => { 88 | setStore((store) => ({ 89 | ...store, 90 | todos: filterTodos(todos), 91 | loading: false, 92 | })); 93 | }); 94 | }, [filterTodos]); 95 | 96 | const handleOpenDialog = useCallback((event, todo = { title: "" }) => { 97 | setStore((store) => ({ ...store, edit: true, todo: { ...todo } })); 98 | }, []); 99 | 100 | const handleCloseDialog = useCallback(() => { 101 | setStore((store) => ({ 102 | ...store, 103 | edit: false, 104 | todo: { title: "" }, 105 | errors: {}, 106 | })); 107 | }, []); 108 | 109 | const handleAddNewTodo = useCallback( 110 | (todo) => { 111 | TinyManagerAPI.addTodo(todo) 112 | .then((todo) => { 113 | setStore((store) => ({ 114 | ...store, 115 | saving: false, 116 | todos: [todo, ...store.todos], 117 | })); 118 | handleCloseDialog(); 119 | }) 120 | .catch(() => { 121 | setStore((store) => ({ ...store, saving: false })); 122 | alert("Error adding todo"); 123 | }); 124 | }, 125 | [handleCloseDialog] 126 | ); 127 | 128 | const handleEditTodo = useCallback( 129 | (todo) => { 130 | TinyManagerAPI.updateTodo(todo) 131 | .then((todo) => { 132 | setStore((store) => { 133 | return { 134 | ...store, 135 | saving: false, 136 | todos: store.todos.map((t) => (t.id === todo.id ? todo : t)), 137 | }; 138 | }); 139 | handleCloseDialog(); 140 | }) 141 | .catch(() => { 142 | setStore((store) => ({ ...store, saving: false })); 143 | alert("Error editing todo"); 144 | }); 145 | }, 146 | [handleCloseDialog] 147 | ); 148 | 149 | const handleSubmit = useCallback( 150 | (todo) => { 151 | setStore((store) => ({ ...store, saving: true })); 152 | const handle = todo.id ? handleEditTodo : handleAddNewTodo; 153 | handle(todo); 154 | }, 155 | [handleAddNewTodo, handleEditTodo] 156 | ); 157 | 158 | const handleTodoCheck = useCallback((e, todo) => { 159 | if (todo && todo.id) { 160 | TinyManagerAPI.updateTodo( 161 | Object.assign({}, todo, { completed: !todo.completed }) 162 | ).then(() => { 163 | setStore((store) => ({ 164 | ...store, 165 | todos: store.todos.map((t) => 166 | t.id === todo.id ? { ...t, completed: !t.completed } : t 167 | ), 168 | })); 169 | }); 170 | } 171 | }, []); 172 | 173 | const handleTodoDelete = useCallback((e, todo) => { 174 | if (todo && todo.id) { 175 | TinyManagerAPI.removeTodo(todo).then(() => { 176 | setStore((store) => ({ 177 | ...store, 178 | todos: store.todos.filter((t) => t.id !== todo.id), 179 | })); 180 | }); 181 | } 182 | }, []); 183 | 184 | const handleDeleteAllTodos = useCallback(() => { 185 | if (todos.length) { 186 | TinyManagerAPI.removeBulkTodos(todos.map(({ id }) => id)).then(() => { 187 | setStore((store) => ({ ...store, todos: [] })); 188 | }); 189 | } 190 | closeDeleteAllDialog(); 191 | }, [todos, closeDeleteAllDialog]); 192 | 193 | const handleFilterChange = useCallback((event) => { 194 | setFilter(event.target.value); 195 | }, []); 196 | 197 | useEffect(() => { 198 | fetchTodos(); 199 | }, [fetchTodos]); 200 | 201 | return ( 202 |
203 | 204 | {loading ? ( 205 | 206 | ) : ( 207 |
208 | 215 | 224 | 225 | 233 |    234 | 241 | 242 | 243 | 244 | 251 |
252 | )} 253 | {edit && ( 254 | 261 | )} 262 | 270 |
271 | ); 272 | } 273 | 274 | Todos.propTypes = { 275 | className: PropTypes.string, 276 | }; 277 | 278 | export default Todos; 279 | -------------------------------------------------------------------------------- /src/TinyManager/containers/Topbar/index.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useMemo } from "react"; 2 | 3 | import Topbar from "TinyManager/components/Topbar"; 4 | import { useTheme } from "TinyManager/providers/ThemeProvider"; 5 | import { useTranslation } from "TinyManager/providers/TranslationProvider"; 6 | 7 | function TopbarContainer() { 8 | const { toggleDarkMode } = useTheme(); 9 | const { t, i18n } = useTranslation(); 10 | 11 | const handleLanguageChange = useCallback( 12 | (event, lang) => { 13 | i18n.changeLanguage(lang); 14 | }, 15 | [i18n] 16 | ); 17 | 18 | const languages = useMemo( 19 | () => [ 20 | { label: "English", value: "en" }, 21 | { label: "Français", value: "fr" }, 22 | { label: "Español", value: "es" }, 23 | { label: "Pусский", value: "ru" }, 24 | { label: "Deutsch", value: "de" }, 25 | { label: "हिंदी", value: "in" }, 26 | { label: "日本語", value: "jp" }, 27 | { label: "中文", value: "cn" }, 28 | ], 29 | [] 30 | ); 31 | 32 | return ( 33 | 39 | ); 40 | } 41 | 42 | export default TopbarContainer; 43 | -------------------------------------------------------------------------------- /src/TinyManager/decorators/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function wrapper(Story, width = 350) { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | 11 | export function withWrapper(Story) { 12 | return wrapper(Story); 13 | } 14 | 15 | export function withLargeWrapper(Story) { 16 | return wrapper(Story, 500); 17 | } 18 | -------------------------------------------------------------------------------- /src/TinyManager/hooks/useDialog.js: -------------------------------------------------------------------------------- 1 | import { useCallback, useMemo, useState } from "react"; 2 | 3 | function useDialog() { 4 | const [open, setOpen] = useState(false); 5 | 6 | const openDialog = useCallback(() => { 7 | setOpen(true); 8 | }, []); 9 | 10 | const closeDialog = useCallback(() => { 11 | setOpen(false); 12 | }, []); 13 | 14 | const toggleDialog = useCallback(() => { 15 | setOpen((open) => !open); 16 | }, []); 17 | 18 | const value = useMemo(() => [open, openDialog, closeDialog, toggleDialog], [ 19 | open, 20 | openDialog, 21 | closeDialog, 22 | toggleDialog, 23 | ]); 24 | 25 | return value; 26 | } 27 | 28 | export default useDialog; 29 | -------------------------------------------------------------------------------- /src/TinyManager/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { HashRouter } from "react-router-dom"; 3 | 4 | import Content from "TinyManager/containers/Content"; 5 | import ThemeProvider from "TinyManager/providers/ThemeProvider"; 6 | import TranslationProvider from "TinyManager/providers/TranslationProvider"; 7 | import SnackbarProvider from "TinyManager/providers/SnackbarProvider"; 8 | 9 | function TinyManager() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | 23 | export default TinyManager; 24 | -------------------------------------------------------------------------------- /src/TinyManager/providers/SnackbarProvider.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { SnackbarProvider as NotistackSnackbarProvider } from "notistack"; 4 | 5 | function SnackbarProvider(props) { 6 | const { children } = props; 7 | return ( 8 | 9 | {children} 10 | 11 | ); 12 | } 13 | 14 | SnackbarProvider.propTypes = { 15 | children: PropTypes.element, 16 | }; 17 | 18 | export default SnackbarProvider; 19 | 20 | export { useSnackbar } from "notistack"; 21 | -------------------------------------------------------------------------------- /src/TinyManager/providers/ThemeProvider.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { CssBaseline } from "@material-ui/core"; 4 | import { MuiThemeProvider, createMuiTheme } from "@material-ui/core/styles"; 5 | import { 6 | indigo, 7 | orange, 8 | grey, 9 | red, 10 | deepOrange, 11 | } from "@material-ui/core/colors"; 12 | 13 | import TinyManagerAPI from "TinyManager/services/TinyManagerAPI"; 14 | 15 | const ThemeContext = React.createContext(); 16 | 17 | function ThemeProvider(props) { 18 | const { children, darkMode: darkModeProp = false } = props; 19 | 20 | const [darkMode, setDarkMode] = React.useState( 21 | darkModeProp || TinyManagerAPI.fetchDarkMode() 22 | ); 23 | 24 | const toggleDarkMode = React.useCallback(() => { 25 | setDarkMode((darkMode) => { 26 | TinyManagerAPI.updateDarkMode(!darkMode); 27 | return !darkMode; 28 | }); 29 | }, []); 30 | 31 | const theme = createMuiTheme({ 32 | palette: { 33 | type: darkMode ? "dark" : "light", 34 | primary: darkMode ? orange : indigo, 35 | secondary: grey, 36 | error: darkMode ? deepOrange : red, 37 | }, 38 | typography: { 39 | fontFamily: "'Fira Sans', sans-serif", 40 | }, 41 | props: { 42 | MuiTextField: { 43 | variant: "outlined", 44 | }, 45 | MuiButton: { 46 | size: "small", 47 | }, 48 | }, 49 | overrides: { 50 | MuiButton: { root: { textTransform: "none" } }, 51 | }, 52 | custom: { 53 | shadow: ["0px 0px 30px rgba(0,0,0,0.2)"], 54 | }, 55 | }); 56 | 57 | return ( 58 | 59 | 60 | 61 | {children} 62 | 63 | 64 | ); 65 | } 66 | 67 | export const useTheme = () => React.useContext(ThemeContext); 68 | 69 | ThemeProvider.propTypes = { 70 | /** 71 | * Children of theme provider ( element ) 72 | */ 73 | children: PropTypes.element, 74 | /** 75 | * Darkmode theme type boolean 76 | */ 77 | darkMode: PropTypes.bool, 78 | }; 79 | 80 | export default ThemeProvider; 81 | -------------------------------------------------------------------------------- /src/TinyManager/providers/TranslationProvider.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useMemo } from "react"; 2 | import i18n from "i18next"; 3 | import { initReactI18next } from "react-i18next"; 4 | import PropTypes from "prop-types"; 5 | 6 | import cn from "TinyManager/assets/locales/cn/translations.json"; 7 | import de from "TinyManager/assets/locales/de/translations.json"; 8 | import en from "TinyManager/assets/locales/en/translations.json"; 9 | import es from "TinyManager/assets/locales/es/translations.json"; 10 | import fr from "TinyManager/assets/locales/fr/translations.json"; 11 | import ind from "TinyManager/assets/locales/in/translations.json"; 12 | import ru from "TinyManager/assets/locales/ru/translations.json"; 13 | import jp from "TinyManager/assets/locales/jp/translations.json"; 14 | 15 | const TranslationContext = createContext({}); 16 | 17 | const resources = { 18 | cn: { translation: cn }, 19 | de: { translation: de }, 20 | en: { translation: en }, 21 | es: { translation: es }, 22 | fr: { translation: fr }, 23 | in: { translation: ind }, 24 | jp: { translation: jp }, 25 | ru: { translation: ru }, 26 | }; 27 | 28 | i18n.use(initReactI18next).init({ 29 | resources, 30 | lng: "en", 31 | fallbackLng: "en", 32 | keySeparator: false, 33 | interpolation: { 34 | escapeValue: false, 35 | }, 36 | }); 37 | 38 | function TranslationProvider(props) { 39 | const { children } = props; 40 | 41 | const value = useMemo(() => ({}), []); 42 | return ( 43 | 44 | {children} 45 | 46 | ); 47 | } 48 | 49 | TranslationProvider.propTypes = { 50 | children: PropTypes.element, 51 | }; 52 | 53 | export default TranslationProvider; 54 | 55 | export { useTranslation, Trans as Translate } from "react-i18next"; 56 | -------------------------------------------------------------------------------- /src/TinyManager/services/DbService.js: -------------------------------------------------------------------------------- 1 | import Dexie from "dexie"; 2 | 3 | const db = new Dexie("TinyManager"); 4 | 5 | function initateDB() { 6 | db.version(1).stores({ 7 | projects: "++id,name", 8 | }); 9 | 10 | db.version(1).stores({ 11 | todos: "++id,title,completed", 12 | }); 13 | 14 | db.version(1).stores({ 15 | tasks: "++id,title,projectId", 16 | }); 17 | 18 | db.projects.defineClass({ 19 | name: String, 20 | description: String, 21 | createdAt: Date, 22 | updatedAt: Date, 23 | }); 24 | 25 | db.todos.defineClass({ 26 | title: String, 27 | completed: Boolean, 28 | }); 29 | 30 | db.tasks.defineClass({ 31 | title: String, 32 | note: String, 33 | priority: Number, 34 | progress: Number, 35 | projectId: Number, 36 | createdAt: Date, 37 | updatedAt: Date, 38 | }); 39 | } 40 | 41 | function getTable(tableName) { 42 | const table = db.table(tableName); 43 | return { 44 | name: table.name, 45 | }; 46 | } 47 | 48 | async function find(tableName, query = {}) { 49 | const { where } = query; 50 | 51 | let table = db[tableName]; 52 | 53 | if (where) { 54 | table = table.where(where); 55 | } 56 | 57 | return table.reverse().toArray(); 58 | } 59 | 60 | function findOne(tableName, query) { 61 | return db[tableName].get(query); 62 | } 63 | 64 | function insert(tableName, record) { 65 | record = { ...record, createdAt: new Date(), updatedAt: new Date() }; 66 | return db[tableName] 67 | .add(record) 68 | .then((recordId) => findOne(tableName, recordId)); 69 | } 70 | 71 | function update(tableName, record) { 72 | record = { ...record, updatedAt: new Date() }; 73 | return db[tableName].update(record.id, record).then((updateCount) => { 74 | if (updateCount) { 75 | return findOne(tableName, record.id); 76 | } else { 77 | return new Error("Error updating record"); 78 | } 79 | }); 80 | } 81 | 82 | function remove(tableName, record) { 83 | return db[tableName].delete(record.id); 84 | } 85 | 86 | function removeBulk(tableName, ids) { 87 | return db[tableName].bulkDelete(ids); 88 | } 89 | 90 | initateDB(); 91 | 92 | export default { 93 | find, 94 | findOne, 95 | insert, 96 | update, 97 | remove, 98 | removeBulk, 99 | getTable, 100 | }; 101 | -------------------------------------------------------------------------------- /src/TinyManager/services/DbService.spec.js: -------------------------------------------------------------------------------- 1 | import DbService from "./DbService"; 2 | 3 | describe("DbService", () => { 4 | it("must be defined", () => { 5 | expect(DbService).toBeDefined(); 6 | expect(DbService).toBeTruthy(); 7 | }); 8 | 9 | describe("getTable", () => { 10 | const { getTable } = DbService; 11 | 12 | it("must be defined", () => { 13 | expect(getTable).toBeDefined(); 14 | }); 15 | 16 | it("must return table", () => { 17 | const table = getTable("projects"); 18 | expect(table).toBeDefined(); 19 | expect(table.name).toBe("projects"); 20 | }); 21 | }); 22 | 23 | describe("find", () => { 24 | const { find } = DbService; 25 | 26 | it("must be defined", () => { 27 | expect(find).toBeDefined(); 28 | }); 29 | }); 30 | 31 | describe("findOne", () => { 32 | const { findOne } = DbService; 33 | 34 | it("must be defined", () => { 35 | expect(findOne).toBeDefined(); 36 | }); 37 | }); 38 | 39 | describe("insert", () => { 40 | const { insert } = DbService; 41 | 42 | it("must be defined", () => { 43 | expect(insert).toBeDefined(); 44 | }); 45 | }); 46 | 47 | describe("update", () => { 48 | const { update } = DbService; 49 | 50 | it("must be defined", () => { 51 | expect(update).toBeDefined(); 52 | }); 53 | }); 54 | 55 | describe("remove", () => { 56 | const { remove } = DbService; 57 | 58 | it("must be defined", () => { 59 | expect(remove).toBeDefined(); 60 | }); 61 | }); 62 | 63 | describe("DB", () => { 64 | const { getTable } = DbService; 65 | 66 | it("must be initialized", () => { 67 | const projectsTable = getTable("projects"); 68 | 69 | expect(projectsTable).toBeDefined(); 70 | expect(projectsTable.name).toEqual("projects"); 71 | 72 | const todosTable = getTable("todos"); 73 | 74 | expect(todosTable).toBeDefined(); 75 | expect(todosTable.name).toEqual("todos"); 76 | 77 | const tasksTable = getTable("tasks"); 78 | 79 | expect(tasksTable).toBeDefined(); 80 | expect(tasksTable.name).toEqual("tasks"); 81 | 82 | const updatesTable = getTable("updates"); 83 | 84 | expect(updatesTable).toBeDefined(); 85 | expect(updatesTable.name).toEqual("updates"); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /src/TinyManager/services/LocalStorageService.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_NOTES = "dn"; 2 | const DARK_MODE = "dm"; 3 | const NOTES = "ns"; 4 | 5 | function setItem(key, value, service) { 6 | service.setItem(key, JSON.stringify(value)); 7 | } 8 | 9 | function getItem(key, service) { 10 | try { 11 | return JSON.parse(service.getItem(key)); 12 | } catch (err) { 13 | return; 14 | } 15 | } 16 | 17 | function setDefaultNotes(value, service = localStorage) { 18 | setItem(DEFAULT_NOTES, value, service); 19 | } 20 | 21 | function getDefaultNotes(service = localStorage) { 22 | return getItem(DEFAULT_NOTES, service); 23 | } 24 | 25 | function setDarkMode(value, service = localStorage) { 26 | setItem(DARK_MODE, value, service); 27 | } 28 | 29 | function getDarkMode(service = localStorage) { 30 | return getItem(DARK_MODE, service); 31 | } 32 | 33 | function setNotes(value, service = localStorage) { 34 | setItem(NOTES, value, service); 35 | } 36 | 37 | function getNotes(service = localStorage) { 38 | return getItem(NOTES, service); 39 | } 40 | 41 | export default { 42 | setDefaultNotes, 43 | getDefaultNotes, 44 | setDarkMode, 45 | getDarkMode, 46 | setNotes, 47 | getNotes, 48 | DEFAULT_NOTES, 49 | DARK_MODE, 50 | NOTES, 51 | }; 52 | -------------------------------------------------------------------------------- /src/TinyManager/services/LocalStorageService.spec.js: -------------------------------------------------------------------------------- 1 | import LocalStorageService from "./LocalStorageService"; 2 | 3 | describe("LocalStorageService", () => { 4 | const getItem = jest.fn(); 5 | const setItem = jest.fn(); 6 | 7 | const service = { 8 | getItem, 9 | setItem, 10 | }; 11 | 12 | beforeEach(() => { 13 | getItem.mockClear(); 14 | setItem.mockClear(); 15 | }); 16 | 17 | it("must be defined", () => { 18 | expect(LocalStorageService).toBeDefined(); 19 | }); 20 | 21 | describe("DEFAULT_NOTES", () => { 22 | const { DEFAULT_NOTES } = LocalStorageService; 23 | 24 | it("must be defined", () => { 25 | expect(DEFAULT_NOTES).toBeDefined(); 26 | }); 27 | }); 28 | 29 | describe("setDefaultNotes", () => { 30 | const { setDefaultNotes, DEFAULT_NOTES } = LocalStorageService; 31 | 32 | it("must be defined", () => { 33 | expect(setDefaultNotes).toBeDefined(); 34 | }); 35 | 36 | it("must set local storage DEFAULT_NOTES value", () => { 37 | setDefaultNotes(true, service); 38 | expect(service.setItem).toHaveBeenCalledTimes(1); 39 | expect(service.setItem).toHaveBeenCalledWith(DEFAULT_NOTES, "true"); 40 | }); 41 | }); 42 | 43 | describe("getDefaultNotes", () => { 44 | const { getDefaultNotes } = LocalStorageService; 45 | 46 | it("must be defined", () => { 47 | expect(getDefaultNotes).toBeDefined(); 48 | }); 49 | 50 | it("must get local storage DEFAULT_NOTES value", () => { 51 | service.getItem.mockImplementation(() => true); 52 | 53 | const defaultNotes = getDefaultNotes(service); 54 | expect(service.getItem).toHaveBeenCalledTimes(1); 55 | expect(defaultNotes).toBeTruthy(); 56 | }); 57 | }); 58 | 59 | describe("setDarkMode", () => { 60 | const { setDarkMode, DARK_MODE } = LocalStorageService; 61 | 62 | it("must be defined", () => { 63 | expect(setDarkMode).toBeDefined(); 64 | }); 65 | 66 | it("must set local storage DARK_MODE value", () => { 67 | setDarkMode(true, service); 68 | expect(service.setItem).toHaveBeenCalledTimes(1); 69 | expect(service.setItem).toHaveBeenCalledWith(DARK_MODE, "true"); 70 | }); 71 | }); 72 | 73 | describe("getDarkMode", () => { 74 | const { getDarkMode } = LocalStorageService; 75 | 76 | it("must be defined", () => { 77 | expect(getDarkMode).toBeDefined(); 78 | }); 79 | 80 | it("must get local storage DARK_MODE value", () => { 81 | service.getItem.mockImplementation(() => true); 82 | 83 | const darkMode = getDarkMode(service); 84 | expect(service.getItem).toHaveBeenCalledTimes(1); 85 | expect(darkMode).toBeTruthy(); 86 | }); 87 | }); 88 | 89 | describe("setNotes", () => { 90 | const { setNotes, NOTES } = LocalStorageService; 91 | 92 | it("must be defined", () => { 93 | expect(setNotes).toBeDefined(); 94 | }); 95 | 96 | it("must set local storage NOTES value", () => { 97 | setNotes("test-note", service); 98 | expect(service.setItem).toHaveBeenCalledTimes(1); 99 | expect(service.setItem).toHaveBeenCalledWith(NOTES, '"test-note"'); 100 | }); 101 | }); 102 | 103 | describe("getNotes", () => { 104 | const { getNotes } = LocalStorageService; 105 | 106 | it("must be defined", () => { 107 | expect(getNotes).toBeDefined(); 108 | }); 109 | 110 | it("must get local storage NOTES value", () => { 111 | service.getItem.mockImplementation(() => JSON.stringify("test-note")); 112 | 113 | const notes = getNotes(service); 114 | expect(service.getItem).toHaveBeenCalledTimes(1); 115 | expect(notes).toBe("test-note"); 116 | }); 117 | }); 118 | }); 119 | -------------------------------------------------------------------------------- /src/TinyManager/services/QuoteService.js: -------------------------------------------------------------------------------- 1 | import quotes from "TinyManager/assets/quotes"; 2 | 3 | const count = quotes.length; 4 | 5 | function getRandomInt(max) { 6 | return Math.floor(Math.random() * Math.floor(max)); 7 | } 8 | 9 | function getQuote() { 10 | const randomNumber = getRandomInt(count - 1); 11 | return quotes[randomNumber]; 12 | } 13 | 14 | export default { 15 | getQuote, 16 | }; 17 | -------------------------------------------------------------------------------- /src/TinyManager/services/QuoteService.spec.js: -------------------------------------------------------------------------------- 1 | import QuoteService from "./QuoteService"; 2 | 3 | describe("QuoteService", () => { 4 | describe("getQuote", () => { 5 | const { getQuote } = QuoteService; 6 | 7 | it("must be defined", () => { 8 | expect(getQuote).toBeDefined(); 9 | }); 10 | 11 | it("must return a quote", () => { 12 | const quote = getQuote(); 13 | expect(quote).toBeDefined(); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/TinyManager/services/TinyManagerAPI.js: -------------------------------------------------------------------------------- 1 | import DbService from "TinyManager/services/DbService"; 2 | import LocalStorageService from "TinyManager/services/LocalStorageService"; 3 | 4 | function fetchProjects() { 5 | return DbService.find("projects"); 6 | } 7 | 8 | function fetchProject(projectId) { 9 | return DbService.findOne("projects", projectId); 10 | } 11 | 12 | function fetchTasks(projectId) { 13 | return DbService.find("tasks", { where: { projectId } }); 14 | } 15 | 16 | function fetchTask(taskId) { 17 | return DbService.findOne("tasks", taskId); 18 | } 19 | 20 | function addProject(project) { 21 | return DbService.insert("projects", project); 22 | } 23 | 24 | function addTask(task) { 25 | return DbService.insert("tasks", task); 26 | } 27 | 28 | function updateProject(projectId, project) { 29 | return DbService.update("projects", project); 30 | } 31 | 32 | function updateTask(taskId, task) { 33 | return DbService.update("tasks", task); 34 | } 35 | 36 | function removeProject(projectId) { 37 | return DbService.remove("projects", { id: projectId }); 38 | } 39 | 40 | function removeBulkProjects(projectIds) { 41 | return DbService.removeBulk("projects", projectIds); 42 | } 43 | 44 | function removeTask(taskId) { 45 | return DbService.remove("tasks", { id: taskId }); 46 | } 47 | 48 | function removeBulkTasks(taskIds) { 49 | return DbService.removeBulk("tasks", taskIds); 50 | } 51 | 52 | function fetchTodos() { 53 | return DbService.find("todos"); 54 | } 55 | 56 | function addTodo(todo) { 57 | return DbService.insert("todos", todo); 58 | } 59 | 60 | function updateTodo(todo) { 61 | return DbService.update("todos", todo); 62 | } 63 | 64 | function removeTodo(todo) { 65 | return DbService.remove("todos", todo); 66 | } 67 | 68 | function removeBulkTodos(todoIds) { 69 | return DbService.removeBulk("todos", todoIds); 70 | } 71 | 72 | function fetchNotes() { 73 | return LocalStorageService.getNotes() || ""; 74 | } 75 | 76 | function updateNotes(notes) { 77 | return LocalStorageService.setNotes(notes); 78 | } 79 | 80 | function updateDefaultNotes(value) { 81 | LocalStorageService.setDefaultNotes(value); 82 | } 83 | 84 | function fetchDefaultNotes() { 85 | return LocalStorageService.getDefaultNotes(); 86 | } 87 | 88 | function updateDarkMode(value) { 89 | LocalStorageService.setDarkMode(value); 90 | } 91 | 92 | function fetchDarkMode() { 93 | return LocalStorageService.getDarkMode(); 94 | } 95 | 96 | export default { 97 | fetchProjects, 98 | fetchProject, 99 | fetchTasks, 100 | fetchTask, 101 | addProject, 102 | addTask, 103 | updateProject, 104 | updateTask, 105 | removeTodo, 106 | removeBulkTodos, 107 | removeProject, 108 | removeBulkProjects, 109 | removeTask, 110 | removeBulkTasks, 111 | fetchTodos, 112 | addTodo, 113 | updateTodo, 114 | fetchNotes, 115 | updateNotes, 116 | updateDefaultNotes, 117 | fetchDefaultNotes, 118 | updateDarkMode, 119 | fetchDarkMode, 120 | }; 121 | -------------------------------------------------------------------------------- /src/TinyManager/services/Utils.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import moment from "moment"; 3 | 4 | function join(...strings) { 5 | return strings.join(""); 6 | } 7 | 8 | function merge(...objects) { 9 | return objects.reduce( 10 | (accumulator, object) => ({ ...accumulator, ...object }), 11 | {} 12 | ); 13 | } 14 | 15 | function noop() { 16 | /* No Action */ 17 | } 18 | 19 | // eslint-disable-next-line no-unused-vars 20 | function withProps(Component, props) { 21 | // eslint-disable-next-line react/display-name 22 | return function (innerProps) { 23 | return ; 24 | }; 25 | } 26 | 27 | function formatDate(date = new Date(), format = "DD-MM-YYYY") { 28 | return date && moment(date).format(format); 29 | } 30 | 31 | export { join, merge, noop, withProps, formatDate }; 32 | -------------------------------------------------------------------------------- /src/TinyManager/types/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from "prop-types"; 2 | 3 | export const ProjectType = PropTypes.shape({ 4 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 5 | name: PropTypes.string, 6 | description: PropTypes.string, 7 | }); 8 | 9 | export const TaskType = PropTypes.shape({ 10 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 11 | title: PropTypes.string, 12 | note: PropTypes.string, 13 | priority: PropTypes.number, 14 | progress: PropTypes.number, 15 | }); 16 | 17 | export const TodoType = PropTypes.shape({ 18 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 19 | title: PropTypes.string, 20 | completed: PropTypes.bool, 21 | }); 22 | 23 | export default { 24 | ProjectType, 25 | TaskType, 26 | TodoType, 27 | }; 28 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | #root { 11 | height: 100vh; 12 | width: 100vw; 13 | overflow: hidden; 14 | } 15 | 16 | a { 17 | color: inherit; 18 | text-decoration: none; 19 | } 20 | 21 | a:visited { 22 | color: inherit; 23 | } 24 | 25 | a[disabled], 26 | .disabled-link { 27 | pointer-events: none; 28 | } 29 | 30 | /* width */ 31 | ::-webkit-scrollbar { 32 | width: 4px; 33 | } 34 | 35 | /* Track */ 36 | ::-webkit-scrollbar-track { 37 | background: #f1f1f1; 38 | } 39 | 40 | /* Handle */ 41 | ::-webkit-scrollbar-thumb { 42 | background: #888; 43 | border-radius: 20px; 44 | } 45 | 46 | /* Handle on hover */ 47 | ::-webkit-scrollbar-thumb:hover { 48 | background: #555; 49 | } 50 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import TinyManager from "TinyManager"; 5 | import * as serviceWorker from "serviceWorker"; 6 | 7 | import "./index.css"; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById("root") 14 | ); 15 | 16 | serviceWorker.register(); 17 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/storybook.spec.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import initStoryshots from "@storybook/addon-storyshots"; 3 | 4 | initStoryshots({ 5 | configPath: path.resolve(__dirname, "../.storybook"), 6 | }); 7 | --------------------------------------------------------------------------------