diff --git a/api/dev-containers-test/.env.taskview b/api/dev-containers-test/.env.taskview index 890cb1a..2e626bf 100644 --- a/api/dev-containers-test/.env.taskview +++ b/api/dev-containers-test/.env.taskview @@ -16,7 +16,7 @@ SMTP_PASSWORD="smtp-password" SMTP_ENCRYPTION="ssl" SMTP_FROM_NAME="TaskViewApiServer" SMTP_FROM_EMAIL="smtp" -CORS_ALLOWED_ORIGINS="http://localhost:5173,http://127.0.0.1:5173,http://localhost:3000,http://127.0.0.1:3000" +CORS_ALLOWED_ORIGINS="http://localhost:5173,http://127.0.0.1:5173,http://localhost:3000,http://localhost:8888,http://127.0.0.1:3000,http://127.0.0.1:8888" # Constant value APP_URL="https://taskview.handscream.com" \ No newline at end of file diff --git a/api/dev-containers-test/docker-compose.yml b/api/dev-containers-test/docker-compose.yml index 0bc0e9e..e624680 100644 --- a/api/dev-containers-test/docker-compose.yml +++ b/api/dev-containers-test/docker-compose.yml @@ -14,7 +14,7 @@ services: timeout: 5s retries: 5 migration: - image: gimanhead/taskview-ce-db-migration:1.18.2 + image: gimanhead/taskview-ce-db-migration:latest restart: "no" depends_on: db: @@ -23,12 +23,12 @@ services: - ./.env.taskview taskview-ce-webapp: - image: gimanhead/taskview-ce-webapp:1.18.2 + image: gimanhead/taskview-ce-webapp:latest restart: "no" ports: - "8888:80" taskview-api-server: - image: gimanhead/taskview-ce-api-server:1.18.2 + image: gimanhead/taskview-ce-api-server:latest restart: "no" ports: - "1725:1401" diff --git a/package.json b/package.json index 18ef0b8..cf75730 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "taskview-ce-monorepo", - "version": "1.19.6", + "version": "1.20.1", "private": true, "description": "TaskView CE monorepo containing web, API, and packages", "workspaces": [ diff --git a/web/.claude/settings.json b/web/.claude/settings.json new file mode 100644 index 0000000..3288de3 --- /dev/null +++ b/web/.claude/settings.json @@ -0,0 +1,5 @@ +{ + "enabledPlugins": { + "context7@claude-plugins-official": true + } +} diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100755 index 0000000..9142239 --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,13 @@ +# editorconfig.org +root = true + +[*] +indent_size = 2 +indent_style = space +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/web/.github/workflows/ci.yml b/web/.github/workflows/ci.yml new file mode 100644 index 0000000..789120e --- /dev/null +++ b/web/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: ci + +on: push + +jobs: + ci: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest] + node: [22] + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Install node + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node }} + cache: pnpm + + - name: Install dependencies + run: pnpm install + + - name: Lint + run: pnpm run lint + + - name: Build + run: pnpm run build + + - name: Typecheck + run: pnpm run typecheck diff --git a/web/.gitignore b/web/.gitignore index 468bac4..34cdd11 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -1,2 +1,38 @@ -!build-upload.sh -tsconfig.tsbuildinfo \ No newline at end of file +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Auto-generated type declarations +auto-imports.d.ts +components.d.ts +tsconfig.app.tsbuildinfo +tsconfig.node.tsbuildinfo + +# Environment variables +.env +Info.plist +e2e_logs +e2e_pgdata +e2e_updates +playwright-report \ No newline at end of file diff --git a/web/android/.gitignore b/web/android/.gitignore index 48354a3..d9936bd 100644 --- a/web/android/.gitignore +++ b/web/android/.gitignore @@ -54,8 +54,8 @@ captures/ # Keystore files # Uncomment the following lines if you do not want to check your keystore files in. -#*.jks -#*.keystore +*.jks +*.keystore # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild diff --git a/web/android/app/build.gradle b/web/android/app/build.gradle index ab71d00..a83eaee 100644 --- a/web/android/app/build.gradle +++ b/web/android/app/build.gradle @@ -1,28 +1,25 @@ apply plugin: 'com.android.application' android { - namespace "com.handscream.taskview.app" - compileSdk rootProject.ext.compileSdkVersion + namespace = "com.handscream.taskview.app" + compileSdk = rootProject.ext.compileSdkVersion defaultConfig { applicationId "com.handscreamgnl.taskview.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1196 - versionName "1.19.6" + versionCode 1201 + versionName "1.20.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 - ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' + ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' } - setProperty("archivesBaseName", "TaskView-$versionName") - } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.debug } } } diff --git a/web/android/app/src/main/AndroidManifest.xml b/web/android/app/src/main/AndroidManifest.xml index d41af82..fbde4da 100644 --- a/web/android/app/src/main/AndroidManifest.xml +++ b/web/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + + + - - - - - - + - + diff --git a/web/android/app/src/main/res/drawable-land-hdpi/splash.png b/web/android/app/src/main/res/drawable-land-hdpi/splash.png new file mode 100644 index 0000000..e31573b Binary files /dev/null and b/web/android/app/src/main/res/drawable-land-hdpi/splash.png differ diff --git a/web/android/app/src/main/res/drawable-land-mdpi/splash.png b/web/android/app/src/main/res/drawable-land-mdpi/splash.png new file mode 100644 index 0000000..f7a6492 Binary files /dev/null and b/web/android/app/src/main/res/drawable-land-mdpi/splash.png differ diff --git a/web/android/app/src/main/res/drawable-land-xhdpi/splash.png b/web/android/app/src/main/res/drawable-land-xhdpi/splash.png new file mode 100644 index 0000000..8077255 Binary files /dev/null and b/web/android/app/src/main/res/drawable-land-xhdpi/splash.png differ diff --git a/web/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/web/android/app/src/main/res/drawable-land-xxhdpi/splash.png new file mode 100644 index 0000000..14c6c8f Binary files /dev/null and b/web/android/app/src/main/res/drawable-land-xxhdpi/splash.png differ diff --git a/web/android/app/src/main/res/drawable-land-xxxhdpi/splash.png b/web/android/app/src/main/res/drawable-land-xxxhdpi/splash.png new file mode 100644 index 0000000..244ca25 Binary files /dev/null and b/web/android/app/src/main/res/drawable-land-xxxhdpi/splash.png differ diff --git a/web/android/app/src/main/res/drawable-port-hdpi/splash.png b/web/android/app/src/main/res/drawable-port-hdpi/splash.png new file mode 100644 index 0000000..74faaa5 Binary files /dev/null and b/web/android/app/src/main/res/drawable-port-hdpi/splash.png differ diff --git a/web/android/app/src/main/res/drawable-port-mdpi/splash.png b/web/android/app/src/main/res/drawable-port-mdpi/splash.png new file mode 100644 index 0000000..e944f4a Binary files /dev/null and b/web/android/app/src/main/res/drawable-port-mdpi/splash.png differ diff --git a/web/android/app/src/main/res/drawable-port-xhdpi/splash.png b/web/android/app/src/main/res/drawable-port-xhdpi/splash.png new file mode 100644 index 0000000..564a82f Binary files /dev/null and b/web/android/app/src/main/res/drawable-port-xhdpi/splash.png differ diff --git a/web/android/app/src/main/res/drawable-port-xxhdpi/splash.png b/web/android/app/src/main/res/drawable-port-xxhdpi/splash.png new file mode 100644 index 0000000..bfabe68 Binary files /dev/null and b/web/android/app/src/main/res/drawable-port-xxhdpi/splash.png differ diff --git a/web/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/web/android/app/src/main/res/drawable-port-xxxhdpi/splash.png new file mode 100644 index 0000000..6929071 Binary files /dev/null and b/web/android/app/src/main/res/drawable-port-xxxhdpi/splash.png differ diff --git a/web/android/build.gradle b/web/android/build.gradle index e6d4bd3..f8f0e43 100644 --- a/web/android/build.gradle +++ b/web/android/build.gradle @@ -7,8 +7,8 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.7.3' - classpath 'com.google.gms:google-services:4.4.2' + classpath 'com.android.tools.build:gradle:8.13.0' + classpath 'com.google.gms:google-services:4.4.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/web/android/capacitor.settings.gradle b/web/android/capacitor.settings.gradle index 56ae2d1..29285d7 100644 --- a/web/android/capacitor.settings.gradle +++ b/web/android/capacitor.settings.gradle @@ -1,21 +1,21 @@ // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN include ':capacitor-android' -project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/android/capacitor') +project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@8.1.0_@capacitor+core@8.1.0/node_modules/@capacitor/android/capacitor') include ':capacitor-app' -project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/app/android') +project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/app/android') include ':capacitor-browser' -project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@7.0.3_@capacitor+core@7.4.5/node_modules/@capacitor/browser/android') +project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/browser/android') include ':capacitor-device' -project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/device/android') +project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/device/android') include ':capacitor-preferences' -project(':capacitor-preferences').projectDir = new File('../../../node_modules/.pnpm/@capacitor+preferences@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/preferences/android') +project(':capacitor-preferences').projectDir = new File('../../../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/preferences/android') include ':capacitor-splash-screen' -project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@7.0.4_@capacitor+core@7.4.5/node_modules/@capacitor/splash-screen/android') +project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/splash-screen/android') include ':capgo-capacitor-updater' -project(':capgo-capacitor-updater').projectDir = new File('../../../node_modules/.pnpm/@capgo+capacitor-updater@7.41.1_@capacitor+core@7.4.5/node_modules/@capgo/capacitor-updater/android') +project(':capgo-capacitor-updater').projectDir = new File('../../../node_modules/.pnpm/@capgo+capacitor-updater@8.43.2_@capacitor+core@8.1.0/node_modules/@capgo/capacitor-updater/android') diff --git a/web/android/gradle/wrapper/gradle-wrapper.jar b/web/android/gradle/wrapper/gradle-wrapper.jar index 033e24c..1b33c55 100644 Binary files a/web/android/gradle/wrapper/gradle-wrapper.jar and b/web/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/web/android/gradle/wrapper/gradle-wrapper.properties b/web/android/gradle/wrapper/gradle-wrapper.properties index c1d5e01..7705927 100644 --- a/web/android/gradle/wrapper/gradle-wrapper.properties +++ b/web/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/web/android/gradlew b/web/android/gradlew index fcb6fca..23d15a9 100755 --- a/web/android/gradlew +++ b/web/android/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,7 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -111,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -201,16 +204,16 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/web/android/gradlew.bat b/web/android/gradlew.bat index 93e3f59..5eed7ee 100644 --- a/web/android/gradlew.bat +++ b/web/android/gradlew.bat @@ -1,92 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/web/android/variables.gradle b/web/android/variables.gradle index 2c8e408..ee4ba41 100644 --- a/web/android/variables.gradle +++ b/web/android/variables.gradle @@ -1,16 +1,16 @@ ext { - minSdkVersion = 23 - compileSdkVersion = 35 - targetSdkVersion = 35 - androidxActivityVersion = '1.9.2' - androidxAppCompatVersion = '1.7.0' - androidxCoordinatorLayoutVersion = '1.2.0' - androidxCoreVersion = '1.15.0' - androidxFragmentVersion = '1.8.4' - coreSplashScreenVersion = '1.0.1' - androidxWebkitVersion = '1.12.1' + minSdkVersion = 24 + compileSdkVersion = 36 + targetSdkVersion = 36 + androidxActivityVersion = '1.11.0' + androidxAppCompatVersion = '1.7.1' + androidxCoordinatorLayoutVersion = '1.3.0' + androidxCoreVersion = '1.17.0' + androidxFragmentVersion = '1.8.9' + coreSplashScreenVersion = '1.2.0' + androidxWebkitVersion = '1.14.0' junitVersion = '4.13.2' - androidxJunitVersion = '1.2.1' - androidxEspressoCoreVersion = '3.6.1' - cordovaAndroidVersion = '10.1.1' + androidxJunitVersion = '1.3.0' + androidxEspressoCoreVersion = '3.7.0' + cordovaAndroidVersion = '14.0.1' } \ No newline at end of file diff --git a/web/capacitor.config.ts b/web/capacitor.config.ts index 5390c5e..0f094af 100644 --- a/web/capacitor.config.ts +++ b/web/capacitor.config.ts @@ -1,30 +1,31 @@ -import type { CapacitorConfig } from '@capacitor/cli'; +import type { CapacitorConfig } from '@capacitor/cli' const config: CapacitorConfig = { - appId: 'com.handscream.taskview.app', - appName: 'TaskView', - webDir: 'dist', + appId: 'com.handscream.taskview.app', + appName: 'TaskView', + webDir: 'dist', + zoomEnabled: false, + android: { zoomEnabled: false, - android: { - zoomEnabled: false, + }, + ios: { + zoomEnabled: false, + webContentsDebuggingEnabled: true, + }, + server: { + hostname: 'app.taskview.tech', + }, + plugins: { + CapacitorUpdater: { + autoUpdate: false, }, - ios: { - zoomEnabled: false, + CapacitorCookies: { + enabled: true, }, - server: { - hostname: 'app.taskview.tech', + CapacitorHttp: { + enabled: true, }, - plugins: { - CapacitorUpdater: { - autoUpdate: false, - }, - CapacitorCookies: { - enabled: true, - }, - CapacitorHttp: { - enabled: true, - }, - }, -}; + }, +} -export default config; +export default config diff --git a/web/e2e/auth.spec.ts b/web/e2e/auth.spec.ts new file mode 100644 index 0000000..6c708d1 --- /dev/null +++ b/web/e2e/auth.spec.ts @@ -0,0 +1,29 @@ +import { test, expect } from '@playwright/test' +import { TEST_USER } from './fixtures/auth' + +test.describe('Login', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/') + }) + + test('shows login page with welcome message', async ({ page }) => { + await expect(page.getByRole('heading', { level: 1 })).toContainText(/welcome|возвращением/i) + }) + + test('user can login with password and reach dashboard', async ({ page }) => { + // Switch to Password tab (default is Magic link) + await page.getByRole('tab', { name: /password|пароль/i }).click() + + // Fill credentials + await page.getByTestId('login-input').fill(TEST_USER.login) + await page.getByTestId('password-input').fill(TEST_USER.password) + + // Submit + await page.getByTestId('sign-in-button').click() + + // Should redirect to user dashboard + await expect(page).toHaveURL(/\/user/) + // Dashboard should be visible (sidebar or main content) + await expect(page.locator('body')).not.toContainText(/invalid|неверный|error|ошибка/i) + }) +}) diff --git a/web/e2e/docker-compose.yml b/web/e2e/docker-compose.yml new file mode 100644 index 0000000..95a674d --- /dev/null +++ b/web/e2e/docker-compose.yml @@ -0,0 +1,51 @@ +# E2E test stack: PostgreSQL + migration + API +# Run from repo root: bash build-dockers.sh (builds images first) +# Then: docker compose -f se/web-nuxt-ui/e2e/docker-compose.yml up -d +# +# API is exposed on 1401 so web-nuxt-ui (useTaskViewMainUrl) can connect. + +services: + db: + image: postgres:17 + restart: unless-stopped + env_file: + - ../../dockers-check/.env.postgresql + volumes: + - ./e2e_pgdata:/var/lib/postgresql/data + ports: + - "5433:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U tvdbuser -d taskviewdb"] + interval: 5s + timeout: 5s + retries: 5 + + migration: + image: gimanhead/taskview-se-db-migration:latest + restart: "no" + depends_on: + db: + condition: service_healthy + env_file: + - ../../dockers-check/.env.taskview + + api: + image: gimanhead/taskview-se-api-server:latest + restart: "no" + ports: + - "1401:1401" + depends_on: + db: + condition: service_healthy + migration: + condition: service_completed_successfully + env_file: + - ../../dockers-check/.env.taskview + volumes: + - ./e2e_logs:/usr/src/app/logs + - ./e2e_updates:/usr/src/app/updates + +volumes: + e2e_pgdata: + e2e_logs: + e2e_updates: diff --git a/web/e2e/fixtures/auth.ts b/web/e2e/fixtures/auth.ts new file mode 100644 index 0000000..9b8cce1 --- /dev/null +++ b/web/e2e/fixtures/auth.ts @@ -0,0 +1,8 @@ +/** + * E2E auth fixtures. Credentials match DB seed from community migrations + * (user / user1!#Q from app_permissions.sql). + */ +export const TEST_USER = { + login: 'user', + password: 'user1!#Q', +} diff --git a/web/e2e/global-setup.ts b/web/e2e/global-setup.ts new file mode 100644 index 0000000..ec43e69 --- /dev/null +++ b/web/e2e/global-setup.ts @@ -0,0 +1,25 @@ +import { execSync } from 'node:child_process' +import { readFileSync, existsSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +function loadEnv(dir: string) { + const envPath = path.join(dir, '.env') + if (!existsSync(envPath)) return + for (const line of readFileSync(envPath, 'utf-8').split('\n')) { + const m = line.match(/^([^#=]+)=(.*)$/) + if (m) process.env[m[1].trim()] = m[2].trim() + } +} + +export default async function globalSetup() { + const e2eDir = path.dirname(fileURLToPath(import.meta.url)) + const projectDir = path.join(e2eDir, '..') + loadEnv(projectDir) + const setupScript = path.join(e2eDir, 'docker-setup.sh') + execSync(`bash "${setupScript}"`, { + stdio: 'inherit', + cwd: path.join(e2eDir, '../../..'), + env: process.env, + }) +} diff --git a/web/e2e/global-teardown.ts b/web/e2e/global-teardown.ts new file mode 100644 index 0000000..76553d9 --- /dev/null +++ b/web/e2e/global-teardown.ts @@ -0,0 +1,16 @@ +import { execSync } from 'node:child_process' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +export default async function globalTeardown() { + const e2eDir = path.dirname(fileURLToPath(import.meta.url)) + const teardownScript = path.join(e2eDir, 'docker-teardown.sh') + try { + execSync(`bash "${teardownScript}"`, { + stdio: 'inherit', + cwd: path.join(e2eDir, '../../..'), + }) + } catch { + console.warn('[e2e] Teardown: some containers may have already been stopped') + } +} diff --git a/web/e2e/projects.spec.ts b/web/e2e/projects.spec.ts new file mode 100644 index 0000000..cab8d82 --- /dev/null +++ b/web/e2e/projects.spec.ts @@ -0,0 +1,71 @@ +import { test, expect } from '@playwright/test' +import { login } from './test-helpers' + +async function addProject(page: import('@playwright/test').Page, name: string) { + const input = page.getByTestId('project-add-input').first() + await input.waitFor({ state: 'visible', timeout: 15000 }) + await input.fill(name) + await input.press('Enter') + await expect(page.getByText(name).first()).toBeVisible({ timeout: 10000 }) +} + +async function openProjectMenu(page: import('@playwright/test').Page, projectName: string) { + const row = page.getByTestId(`project-row-${projectName}`) + await row.first().waitFor({ state: 'visible', timeout: 10000 }) + await row.first().getByTestId('project-menu-trigger').click() +} + +test.describe('Projects', () => { + test.setTimeout(15_000) + + test.beforeEach(async ({ page }) => { + await page.goto('/') + await login(page) + }) + + test('user can add project', async ({ page }) => { + const projectName = `Test Project ${Date.now()}` + await addProject(page, projectName) + }) + + test('user can edit project', async ({ page }) => { + const projectName = `Edit Me ${Date.now()}` + await addProject(page, projectName) + const newName = `Edited ${Date.now()}` + await openProjectMenu(page, projectName) + await page.getByTestId('context-menu-edit').click() + await page.getByTestId('project-edit-name').fill(newName) + await page.getByTestId('project-edit-save').click() + + await expect(page.getByText(newName).first()).toBeVisible({ timeout: 5000 }) + }) + + test('user can delete project', async ({ page }) => { + const projectName = `Delete Me ${Date.now()}` + await addProject(page, projectName) + await openProjectMenu(page, projectName) + await page.getByTestId('context-menu-delete').click() + await page.getByRole('button', { name: /^yes|^да$/i }).click() + await expect(page.getByText(projectName)).toHaveCount(0) + }) + + test('user can archive project', async ({ page }) => { + const projectName = `Archive Me ${Date.now()}` + await addProject(page, projectName) + await openProjectMenu(page, projectName) + await page.getByTestId('context-menu-move-to-archive').click() + await page.getByTestId('archive-list-collapsible').click() + await expect(page.getByTestId(`project-row-${projectName}`)).toBeVisible() + }) + + test('user can unarchive project', async ({ page }) => { + const projectName = `Unarchive Me ${Date.now()}` + await addProject(page, projectName) + await openProjectMenu(page, projectName) + await page.getByTestId('context-menu-move-to-archive').click() + await page.getByTestId('archive-list-collapsible').click() + await openProjectMenu(page, projectName) + await page.getByTestId('context-menu-restore-from-archive').click({ force: true }) + await expect(page.getByText(projectName).first()).toBeVisible({ timeout: 5000 }) + }) +}) diff --git a/web/e2e/test-helpers.ts b/web/e2e/test-helpers.ts new file mode 100644 index 0000000..5899f29 --- /dev/null +++ b/web/e2e/test-helpers.ts @@ -0,0 +1,8 @@ +import { TEST_USER } from './fixtures/auth' + +export async function login(page: Page) { + await page.getByRole('tab', { name: /password|пароль/i }).click() + await page.getByTestId('login-input').fill(TEST_USER.login) + await page.getByTestId('password-input').fill(TEST_USER.password) + await page.getByTestId('sign-in-button').click() +} \ No newline at end of file diff --git a/web/eslint.config.ts b/web/eslint.config.ts new file mode 100644 index 0000000..99002c8 --- /dev/null +++ b/web/eslint.config.ts @@ -0,0 +1,95 @@ +import js from '@eslint/js' +import eslintPluginVue from 'eslint-plugin-vue' +import ts from 'typescript-eslint' +import { fileURLToPath } from 'node:url' + +export default ts.config( + js.configs.recommended, + ...ts.configs.recommended, + ...eslintPluginVue.configs['flat/recommended'], + { + files: ['*.vue', '**/*.vue'], + languageOptions: { + parserOptions: { + parser: '@typescript-eslint/parser', + tsconfigRootDir: fileURLToPath(new URL('.', import.meta.url)), + }, + }, + rules: { + 'vue/multi-word-component-names': 'off', + 'no-undef': 'off', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + + // Formatting rules + 'vue/script-indent': ['error', 2, { baseIndent: 0 }], + 'vue/html-indent': ['error', 2], + 'vue/max-attributes-per-line': [ + 'error', + { + singleline: 1, + multiline: 1, + }, + ], + 'vue/first-attribute-linebreak': [ + 'error', + { + singleline: 'ignore', + multiline: 'below', + }, + ], + 'vue/html-closing-bracket-newline': [ + 'error', + { + singleline: 'never', + multiline: 'always', + }, + ], + 'vue/html-self-closing': [ + 'error', + { + html: { + void: 'always', + normal: 'always', + component: 'always', + }, + svg: 'always', + math: 'always', + }, + ], + 'vue/attribute-hyphenation': ['error', 'always'], + 'vue/v-bind-style': ['error', 'shorthand'], + 'vue/v-on-style': ['error', 'shorthand'], + 'vue/v-slot-style': ['error', 'shorthand'], + }, + }, + { + files: ['**/*.ts', '**/*.js', '**/*.vue'], + rules: { + semi: ['error', 'never'], + quotes: ['error', 'single'], + 'comma-dangle': ['error', 'always-multiline'], + 'keyword-spacing': ['error', { before: true, after: true }], + 'space-before-blocks': ['error', 'always'], + 'space-before-function-paren': ['error', { anonymous: 'always', named: 'never', asyncArrow: 'always' }], + 'space-infix-ops': 'error', + 'space-in-parens': ['error', 'never'], + 'object-curly-spacing': ['error', 'always'], + 'array-bracket-spacing': ['error', 'never'], + 'comma-spacing': ['error', { before: false, after: true }], + 'key-spacing': ['error', { beforeColon: false, afterColon: true }], + 'arrow-spacing': ['error', { before: true, after: true }], + }, + }, + { + files: ['**/*.ts', '**/*.js'], + rules: { + indent: ['error', 2], + }, + }, + { + files: ['**/*.vue'], + rules: { + indent: 'off', + }, + }, +) diff --git a/web/index.html b/web/index.html index 044c5e8..575b875 100644 --- a/web/index.html +++ b/web/index.html @@ -1,14 +1,27 @@ - + - - - - - Task View - - -
- - - + + + + + + + TaskView + + + + +
+ + + + \ No newline at end of file diff --git a/web/ios/App/App.xcodeproj/project.pbxproj b/web/ios/App/App.xcodeproj/project.pbxproj index b2433eb..6f67556 100644 --- a/web/ios/App/App.xcodeproj/project.pbxproj +++ b/web/ios/App/App.xcodeproj/project.pbxproj @@ -3,37 +3,31 @@ archiveVersion = 1; classes = { }; - objectVersion = 48; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ 2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; }; + 4D22ABE92AF431CB00220026 /* CapApp-SPM in Frameworks */ = {isa = PBXBuildFile; productRef = 4D22ABE82AF431CB00220026 /* CapApp-SPM */; }; 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; }; 504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; }; 504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; }; 504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; }; 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; - A75AC5A184BA226F87D9DFB4 /* Pods_TaskView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33826060E12180E2E6AE45B /* Pods_TaskView.framework */; }; - AA2DDEAF2BD5EE2800137350 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = AA2DDEAE2BD5EE2800137350 /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = ""; }; - 3DDB5660BE7C0283C0A61E6B /* Pods-TaskView.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TaskView.release.xcconfig"; path = "Pods/Target Support Files/Pods-TaskView/Pods-TaskView.release.xcconfig"; sourceTree = ""; }; - 4ED8FB2BBB1F064473F2BF99 /* Pods-TaskView.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TaskView.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TaskView/Pods-TaskView.debug.xcconfig"; sourceTree = ""; }; 50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = ""; }; - 504EC3041FED79650016851F /* TaskView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TaskView.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 504EC3041FED79650016851F /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; 504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; - AA2DDEAE2BD5EE2800137350 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; }; - C33826060E12180E2E6AE45B /* Pods_TaskView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TaskView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; + 958DCC722DB07C7200EA8C5F /* debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = debug.xcconfig; path = ../debug.xcconfig; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -41,36 +35,26 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A75AC5A184BA226F87D9DFB4 /* Pods_TaskView.framework in Frameworks */, + 4D22ABE92AF431CB00220026 /* CapApp-SPM in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = { - isa = PBXGroup; - children = ( - C33826060E12180E2E6AE45B /* Pods_TaskView.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 504EC2FB1FED79650016851F = { isa = PBXGroup; children = ( - AA2DDEAE2BD5EE2800137350 /* PrivacyInfo.xcprivacy */, + 958DCC722DB07C7200EA8C5F /* debug.xcconfig */, 504EC3061FED79650016851F /* App */, 504EC3051FED79650016851F /* Products */, - 7F8756D8B27F46E3366F6CEA /* Pods */, - 27E2DDA53C4D2A4D1A88CE4A /* Frameworks */, ); sourceTree = ""; }; 504EC3051FED79650016851F /* Products */ = { isa = PBXGroup; children = ( - 504EC3041FED79650016851F /* TaskView.app */, + 504EC3041FED79650016851F /* App.app */, ); name = Products; sourceTree = ""; @@ -90,37 +74,27 @@ path = App; sourceTree = ""; }; - 7F8756D8B27F46E3366F6CEA /* Pods */ = { - isa = PBXGroup; - children = ( - FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */, - AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */, - 4ED8FB2BBB1F064473F2BF99 /* Pods-TaskView.debug.xcconfig */, - 3DDB5660BE7C0283C0A61E6B /* Pods-TaskView.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 504EC3031FED79650016851F /* TaskView */ = { + 504EC3031FED79650016851F /* App */ = { isa = PBXNativeTarget; - buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "TaskView" */; + buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */; buildPhases = ( - 6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */, 504EC3001FED79650016851F /* Sources */, 504EC3011FED79650016851F /* Frameworks */, 504EC3021FED79650016851F /* Resources */, - 9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); - name = TaskView; + name = App; + packageProductDependencies = ( + 4D22ABE82AF431CB00220026 /* CapApp-SPM */, + ); productName = App; - productReference = 504EC3041FED79650016851F /* TaskView.app */; + productReference = 504EC3041FED79650016851F /* App.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -129,8 +103,8 @@ 504EC2FC1FED79650016851F /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 920; - LastUpgradeCheck = 920; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 0920; TargetAttributes = { 504EC3031FED79650016851F = { CreatedOnToolsVersion = 9.2; @@ -148,11 +122,14 @@ Base, ); mainGroup = 504EC2FB1FED79650016851F; + packageReferences = ( + D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */, + ); productRefGroup = 504EC3051FED79650016851F /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 504EC3031FED79650016851F /* TaskView */, + 504EC3031FED79650016851F /* App */, ); }; /* End PBXProject section */ @@ -163,7 +140,6 @@ buildActionMask = 2147483647; files = ( 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */, - AA2DDEAF2BD5EE2800137350 /* PrivacyInfo.xcprivacy in Resources */, 50B271D11FEDC1A000F3C39B /* public in Resources */, 504EC30F1FED79650016851F /* Assets.xcassets in Resources */, 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */, @@ -174,42 +150,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-TaskView-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TaskView/Pods-TaskView-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 504EC3001FED79650016851F /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -243,6 +183,7 @@ /* Begin XCBuildConfiguration section */ 504EC3141FED79650016851F /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -289,7 +230,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -340,28 +281,30 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; VALIDATE_PRODUCT = YES; }; name = Release; }; 504EC3171FED79650016851F /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4ED8FB2BBB1F064473F2BF99 /* Pods-TaskView.debug.xcconfig */; + baseConfigurationReference = 958DCC722DB07C7200EA8C5F /* debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1.19.7; + CURRENT_PROJECT_VERSION = 1.20.1; DEVELOPMENT_TEAM = H2W2SG48JT; INFOPLIST_FILE = App/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = TaskView; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.19.7; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.20.1; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -373,18 +316,18 @@ }; 504EC3181FED79650016851F /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3DDB5660BE7C0283C0A61E6B /* Pods-TaskView.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1.19.7; + CURRENT_PROJECT_VERSION = 1.20.1; DEVELOPMENT_TEAM = H2W2SG48JT; INFOPLIST_FILE = App/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = TaskView; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.19.7; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.20.1; PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; @@ -405,7 +348,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "TaskView" */ = { + 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = { isa = XCConfigurationList; buildConfigurations = ( 504EC3171FED79650016851F /* Debug */, @@ -415,6 +358,21 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "CapApp-SPM"; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 4D22ABE82AF431CB00220026 /* CapApp-SPM */ = { + isa = XCSwiftPackageProductDependency; + package = D4C12C0A2AAA248700AAC8A2 /* XCLocalSwiftPackageReference "CapApp-SPM" */; + productName = "CapApp-SPM"; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 504EC2FC1FED79650016851F /* Project object */; } diff --git a/web/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/web/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..44f4b69 --- /dev/null +++ b/web/ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,51 @@ +{ + "originHash" : "e5bfee72a3975177edbeb9593e2d4a7624097d5319fbcc2894685c2c76545af8", + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "3f99050e75bbc6fe71fc323adabb039756680016", + "version" : "5.11.1" + } + }, + { + "identity" : "bigint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/attaswift/BigInt.git", + "state" : { + "revision" : "e07e00fa1fd435143a2dcf8b7eec9a7710b2fdfe", + "version" : "5.7.0" + } + }, + { + "identity" : "capacitor-swift-pm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ionic-team/capacitor-swift-pm.git", + "state" : { + "revision" : "1f324fe7bdbdc8c5cc28bea48a231545a8c2e823", + "version" : "8.1.0" + } + }, + { + "identity" : "version", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mrackwitz/Version.git", + "state" : { + "revision" : "fd4b0eb5756aa7f1c33977fb626cf37d2140a3a0", + "version" : "0.8.0" + } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation.git", + "state" : { + "revision" : "22787ffb59de99e5dc1fbfe80b19c97a904ad48d", + "version" : "0.9.20" + } + } + ], + "version" : 3 +} diff --git a/web/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png b/web/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png new file mode 100644 index 0000000..adf6ba0 Binary files /dev/null and b/web/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png differ diff --git a/web/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png b/web/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png new file mode 100644 index 0000000..33ea6c9 Binary files /dev/null and b/web/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png differ diff --git a/web/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png b/web/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png new file mode 100644 index 0000000..33ea6c9 Binary files /dev/null and b/web/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png differ diff --git a/web/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png b/web/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png new file mode 100644 index 0000000..33ea6c9 Binary files /dev/null and b/web/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png differ diff --git a/web/ios/App/App/Base.lproj/LaunchScreen.storyboard b/web/ios/App/App/Base.lproj/LaunchScreen.storyboard index 3ed14eb..e7ae5d7 100644 --- a/web/ios/App/App/Base.lproj/LaunchScreen.storyboard +++ b/web/ios/App/App/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + @@ -24,7 +24,7 @@ - + diff --git a/web/ios/App/CapApp-SPM/.gitignore b/web/ios/App/CapApp-SPM/.gitignore new file mode 100644 index 0000000..3b29812 --- /dev/null +++ b/web/ios/App/CapApp-SPM/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/web/ios/App/CapApp-SPM/Package.swift b/web/ios/App/CapApp-SPM/Package.swift new file mode 100644 index 0000000..f1c501c --- /dev/null +++ b/web/ios/App/CapApp-SPM/Package.swift @@ -0,0 +1,37 @@ +// swift-tools-version: 5.9 +import PackageDescription + +// DO NOT MODIFY THIS FILE - managed by Capacitor CLI commands +let package = Package( + name: "CapApp-SPM", + platforms: [.iOS(.v15)], + products: [ + .library( + name: "CapApp-SPM", + targets: ["CapApp-SPM"]) + ], + dependencies: [ + .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.1.0"), + .package(name: "CapacitorApp", path: "../../../../../node_modules/.pnpm/@capacitor+app@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/app"), + .package(name: "CapacitorBrowser", path: "../../../../../node_modules/.pnpm/@capacitor+browser@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/browser"), + .package(name: "CapacitorDevice", path: "../../../../../node_modules/.pnpm/@capacitor+device@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/device"), + .package(name: "CapacitorPreferences", path: "../../../../../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/preferences"), + .package(name: "CapacitorSplashScreen", path: "../../../../../node_modules/.pnpm/@capacitor+splash-screen@8.0.1_@capacitor+core@8.1.0/node_modules/@capacitor/splash-screen"), + .package(name: "CapgoCapacitorUpdater", path: "../../../../../node_modules/.pnpm/@capgo+capacitor-updater@8.43.2_@capacitor+core@8.1.0/node_modules/@capgo/capacitor-updater") + ], + targets: [ + .target( + name: "CapApp-SPM", + dependencies: [ + .product(name: "Capacitor", package: "capacitor-swift-pm"), + .product(name: "Cordova", package: "capacitor-swift-pm"), + .product(name: "CapacitorApp", package: "CapacitorApp"), + .product(name: "CapacitorBrowser", package: "CapacitorBrowser"), + .product(name: "CapacitorDevice", package: "CapacitorDevice"), + .product(name: "CapacitorPreferences", package: "CapacitorPreferences"), + .product(name: "CapacitorSplashScreen", package: "CapacitorSplashScreen"), + .product(name: "CapgoCapacitorUpdater", package: "CapgoCapacitorUpdater") + ] + ) + ] +) diff --git a/web/ios/App/CapApp-SPM/README.md b/web/ios/App/CapApp-SPM/README.md new file mode 100644 index 0000000..5e22a2f --- /dev/null +++ b/web/ios/App/CapApp-SPM/README.md @@ -0,0 +1,5 @@ +# CapApp-SPM + +This SPM is used to host SPM dependencies for you Capacitor project + +Do not modify the contents of it or there may be unintended consequences. diff --git a/web/ios/App/CapApp-SPM/Sources/CapApp-SPM/CapApp-SPM.swift b/web/ios/App/CapApp-SPM/Sources/CapApp-SPM/CapApp-SPM.swift new file mode 100644 index 0000000..945afec --- /dev/null +++ b/web/ios/App/CapApp-SPM/Sources/CapApp-SPM/CapApp-SPM.swift @@ -0,0 +1 @@ +public let isCapacitorApp = true diff --git a/web/ios/debug.xcconfig b/web/ios/debug.xcconfig new file mode 100644 index 0000000..53ce18d --- /dev/null +++ b/web/ios/debug.xcconfig @@ -0,0 +1 @@ +CAPACITOR_DEBUG = true diff --git a/web/package.json b/web/package.json index 89b3a63..adff879 100644 --- a/web/package.json +++ b/web/package.json @@ -1,99 +1,84 @@ { - "name": "taskview-ce-webapp", - "version": "1.19.6", + "name": "web-nuxt-ui", "private": true, + "type": "module", + "version": "1.20.1", "scripts": { "dev": "vite", - "build:dev": "BRANCH_MODE=dev pnpm run build", - "build": "run-p type-check build-only", - "preview": "vite preview", - "test:unit": "vitest --environment jsdom --root src/", - "test:e2e": "start-server-and-test preview :4173 'cypress run --e2e'", - "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress open --e2e'", + "build": "pnpm run typecheck && vite build", "build-only": "vite build", - "type-check": "vue-tsc --noEmit --project tsconfig.json", - "lint": "eslint . --config eslint.config.mjs --fix", - "lint:biome": "biome lint .", - "format": "biome format --write .", - "check": "biome check --write ." + "preview": "vite preview", + "lint": "eslint src", + "lint:fix": "eslint src --fix", + "format": "eslint src --fix", + "typecheck": "vue-tsc -p ./tsconfig.app.json", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:headed": "playwright test --headed", + "test:e2e:install": "playwright install chromium", + "test:e2e:skip-build": "SKIP_BUILD=1 playwright test" }, "dependencies": { - "@capacitor/android": "^7.0.0", - "@capacitor/app": "7.0.1", - "@capacitor/browser": "^7.0.3", - "@capacitor/core": "^7.0.0", - "@capacitor/device": "7.0.1", - "@capacitor/ios": "^7.0.0", - "@capacitor/preferences": "7.0.1", - "@capacitor/splash-screen": "^7.0.1", - "@capgo/capacitor-updater": "^7.7.7", + "@capacitor/android": "^8.1.0", + "@capacitor/app": "8.0.1", + "@capacitor/browser": "^8.0.1", + "@capacitor/core": "^8.1.0", + "@capacitor/device": "8.0.1", + "@capacitor/ios": "^8.1.0", + "@capacitor/preferences": "8.0.1", + "@capacitor/splash-screen": "^8.0.1", + "@capgo/capacitor-updater": "^8.43.2", "@dagrejs/dagre": "^1.1.5", - "@tiptap/extension-color": "^3.0.0", - "@tiptap/extension-link": "^3.0.0", - "@tiptap/extension-list-item": "^3.0.0", - "@tiptap/extension-placeholder": "^3.0.0", - "@tiptap/extension-text-style": "^3.0.0", - "@tiptap/pm": "^3.0.0", - "@tiptap/starter-kit": "^3.0.0", - "@tiptap/vue-3": "^3.0.0", - "@typescript-eslint/eslint-plugin": "^8.24.1", - "@typescript-eslint/parser": "^8.24.1", + "@iconify-json/carbon": "^1.2.18", + "@iconify-json/lucide": "^1.2.90", + "@iconify-json/mdi": "^1.2.3", + "@iconify/vue": "^5.0.0", + "@internationalized/date": "^3.10.1", + "@nuxt/ui": "^4.4.0", + "@tanstack/table-core": "^8.21.3", + "@tiptap/core": "^3.18.0", + "@tiptap/extension-drag-handle-vue-3": "^3.18.0", + "@tiptap/extension-text-align": "^3.18.0", + "@tiptap/extension-underline": "^3.18.0", + "@tiptap/pm": "^3.18.0", + "@tiptap/starter-kit": "^3.18.0", + "@tiptap/vue-3": "^3.18.0", + "@types/qs": "^6.14.0", + "@unovis/ts": "^1.6.2", + "@unovis/vue": "^1.6.2", "@vue-flow/background": "^1.3.2", "@vue-flow/controls": "^1.1.3", - "@vue-flow/core": "^1.46.4", + "@vue-flow/core": "^1.48.1", "@vue-flow/minimap": "^1.5.4", "@vue-flow/node-resizer": "^1.5.0", "@vue-flow/node-toolbar": "^1.1.1", - "@vue/eslint-config-typescript": "^14.4.0", - "@vueuse/components": "^10.9.0", - "@vueuse/core": "^10.9.0", - "echarts": "^5.5.1", - "globals": "^15.15.0", - "motion-v": "^1.1.0-alpha.1", - "pinia": "^2.0.28", + "@vueuse/core": "^14.1.0", + "arktype": "2.1.20", + "axios": "^1.13.4", + "date-fns": "^4.1.0", + "pinia": "^2.3.1", + "qs": "^6.14.1", + "scule": "^1.3.0", + "tailwindcss": "^4.1.18", "taskview-api": "workspace:^", - "vue": "^3.2.45", - "vue-router": "4.3.2", - "vuedraggable": "^4.1.0" + "vue": "^3.5.27", + "vue-i18n": "^11.2.8", + "vue-router": "^4.6.4", + "vuedraggable": "^4.1.0", + "zod": "^4.3.5" }, "devDependencies": { - "@biomejs/biome": "^2.3.8", "@capacitor/assets": "^3.0.5", - "@capacitor/cli": "^7.0.0", - "@date-io/date-fns": "^3.2.0", - "@mdi/js": "^7.1.96", - "@rushstack/eslint-patch": "^1.1.4", - "@types/jsdom": "^20.0.1", - "@types/node": "^18.19.119", - "@types/qs": "^6.9.7", - "@vitejs/plugin-vue": "^4.0.0", - "@vitejs/plugin-vue-jsx": "^3.0.0", - "@vue/eslint-config-prettier": "^7.0.0", - "@vue/test-utils": "^2.2.6", - "@vue/tsconfig": "0.7.0", - "autoprefixer": "^10.4.20", - "axios": "^1.2.3", - "check-password-strength": "^2.0.7", - "eslint": "9.20.1", - "eslint-plugin-vue": "^9.3.0", - "jsdom": "^20.0.3", - "npm-run-all": "^4.1.5", - "postcss": "^8.4.49", - "prettier": "^2.7.1", - "qs": "^6.11.0", - "sass": "^1.69.7", - "sass-loader": "^13.3.3", - "start-server-and-test": "^1.15.2", - "tailwindcss": "^3.4.16", - "typescript": "^5.7.3", - "typescript-eslint": "^8.22.0", - "vite": "^4.0.0", - "vitest": "^0.25.6", - "vue-i18n": "11.1.10", - "vue-tsc": "3.0.3", - "vuetify": "3.7.9" + "@capacitor/cli": "^8.1.0", + "@playwright/test": "^1.49.0", + "@vitejs/plugin-vue": "^6.0.3", + "eslint": "^9.39.2", + "eslint-plugin-vue": "^10.7.0", + "playwright": "^1.49.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.53.1", + "vite": "^7.3.1", + "vue-tsc": "^3.2.2" }, - "engines": { - "node": ">=24 <25" - } + "packageManager": "pnpm@10.28.1" } \ No newline at end of file diff --git a/web/playwright.config.ts b/web/playwright.config.ts new file mode 100644 index 0000000..274f974 --- /dev/null +++ b/web/playwright.config.ts @@ -0,0 +1,28 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './e2e', + // globalSetup: './e2e/global-setup.ts', + // globalTeardown: './e2e/global-teardown.ts', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:5173', + trace: 'on-first-retry', + screenshot: 'only-on-failure', + }, + projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }], + webServer: process.env.CI + ? undefined + : { + command: 'pnpm dev', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI, + timeout: 120_000, + }, + timeout: 15_000, + expect: { timeout: 5_000 }, +}) diff --git a/web/public/tv-logo.svg b/web/public/logo.svg similarity index 100% rename from web/public/tv-logo.svg rename to web/public/logo.svg diff --git a/web/renovate.json b/web/renovate.json new file mode 100644 index 0000000..7180aab --- /dev/null +++ b/web/renovate.json @@ -0,0 +1,13 @@ +{ + "extends": [ + "github>nuxt/renovate-config-nuxt" + ], + "lockFileMaintenance": { + "enabled": true + }, + "packageRules": [{ + "matchDepTypes": ["resolutions"], + "enabled": false + }], + "postUpdateOptions": ["pnpmDedupe"] +} diff --git a/web/src/App.vue b/web/src/App.vue index cba2be0..bd79a9d 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,49 +1,9 @@ - diff --git a/web/src/assets/css/main.css b/web/src/assets/css/main.css new file mode 100644 index 0000000..b33a1ba --- /dev/null +++ b/web/src/assets/css/main.css @@ -0,0 +1,83 @@ +@import "tailwindcss" theme(static); + +@import "@nuxt/ui"; + +@theme static { + --font-sans: 'Public Sans', sans-serif; +} + +@theme { + --tv-ui-bg-elevated: #fff; + --color-tv-ui-bg-elevated: var(--tv-ui-bg-elevated); +} + +/* Safe area utilities */ +@utility pt-safe { + padding-top: var(--tv-safe-area-inset-top); +} + +@utility pb-safe { + padding-bottom: var(--tv-safe-area-inset-bottom); +} + +@utility pl-safe { + padding-left: var(--tv-safe-area-inset-left); +} + +@utility pr-safe { + padding-right: var(--tv-safe-area-inset-right); +} + +@utility px-safe { + padding-left: var(--tv-safe-area-inset-left); + padding-right: var(--tv-safe-area-inset-right); +} + +@utility py-safe { + padding-top: var(--tv-safe-area-inset-top); + padding-bottom: var(--tv-safe-area-inset-bottom); +} + +@utility p-safe { + padding-top: var(--tv-safe-area-inset-top); + padding-right: var(--tv-safe-area-inset-right); + padding-bottom: var(--tv-safe-area-inset-bottom); + padding-left: var(--tv-safe-area-inset-left); +} + +@utility mt-safe { + margin-top: var(--tv-safe-area-inset-top); +} + +@utility mb-safe { + margin-bottom: var(--tv-safe-area-inset-bottom); +} + +@utility ml-safe { + margin-left: var(--tv-safe-area-inset-left); +} + +@utility mr-safe { + margin-right: var(--tv-safe-area-inset-right); +} + +@layer base { + html { + touch-action: manipulation; + } + + :root { + --tv-safe-area-inset-top: env(safe-area-inset-top, 0px); + --tv-safe-area-inset-bottom: env(safe-area-inset-bottom, 0px); + --tv-safe-area-inset-left: env(safe-area-inset-left, 0px); + --tv-safe-area-inset-right: env(safe-area-inset-right, 0px); + /* --tv-safe-area-inset-top: 30px; + --tv-safe-area-inset-bottom: 30px; + --tv-safe-area-inset-left: 30px; + --tv-safe-area-inset-right: 30px; */ + } + + .dark { + --tv-ui-bg-elevated: var(--color-neutral-800); + } +} diff --git a/web/src/assets/images/avatar-1.jpeg b/web/src/assets/images/avatar-1.jpeg new file mode 100644 index 0000000..432992b Binary files /dev/null and b/web/src/assets/images/avatar-1.jpeg differ diff --git a/web/src/components/UserMenu.vue b/web/src/components/UserMenu.vue new file mode 100644 index 0000000..5b58411 --- /dev/null +++ b/web/src/components/UserMenu.vue @@ -0,0 +1,264 @@ + + + diff --git a/web/src/components/features/account/AccountSettings.vue b/web/src/components/features/account/AccountSettings.vue new file mode 100644 index 0000000..b462357 --- /dev/null +++ b/web/src/components/features/account/AccountSettings.vue @@ -0,0 +1,49 @@ + + + diff --git a/web/src/components/features/account/parts/DeleteAccountButton.vue b/web/src/components/features/account/parts/DeleteAccountButton.vue new file mode 100644 index 0000000..b893b09 --- /dev/null +++ b/web/src/components/features/account/parts/DeleteAccountButton.vue @@ -0,0 +1,33 @@ + + + diff --git a/web/src/components/features/account/parts/DeleteAccountCodeModal.vue b/web/src/components/features/account/parts/DeleteAccountCodeModal.vue new file mode 100644 index 0000000..b124e6e --- /dev/null +++ b/web/src/components/features/account/parts/DeleteAccountCodeModal.vue @@ -0,0 +1,68 @@ + + + diff --git a/web/src/components/features/auth/ForgotPassword.vue b/web/src/components/features/auth/ForgotPassword.vue new file mode 100644 index 0000000..d07ba61 --- /dev/null +++ b/web/src/components/features/auth/ForgotPassword.vue @@ -0,0 +1,142 @@ + + + diff --git a/web/src/components/features/auth/LoginByCode.vue b/web/src/components/features/auth/LoginByCode.vue new file mode 100644 index 0000000..b952f49 --- /dev/null +++ b/web/src/components/features/auth/LoginByCode.vue @@ -0,0 +1,211 @@ + + + diff --git a/web/src/components/features/auth/LoginByPassword.vue b/web/src/components/features/auth/LoginByPassword.vue new file mode 100644 index 0000000..8719669 --- /dev/null +++ b/web/src/components/features/auth/LoginByPassword.vue @@ -0,0 +1,147 @@ + + + diff --git a/web/src/components/features/auth/LoginForm.vue b/web/src/components/features/auth/LoginForm.vue new file mode 100644 index 0000000..1057f8d --- /dev/null +++ b/web/src/components/features/auth/LoginForm.vue @@ -0,0 +1,127 @@ + + + diff --git a/web/src/components/features/auth/ResetPassword.vue b/web/src/components/features/auth/ResetPassword.vue new file mode 100644 index 0000000..02ca090 --- /dev/null +++ b/web/src/components/features/auth/ResetPassword.vue @@ -0,0 +1,231 @@ + + + diff --git a/web/src/components/features/auth/ServerSelector.vue b/web/src/components/features/auth/ServerSelector.vue new file mode 100644 index 0000000..ff0cc39 --- /dev/null +++ b/web/src/components/features/auth/ServerSelector.vue @@ -0,0 +1,191 @@ + + + diff --git a/web/src/components/features/auth/SocialButtons.vue b/web/src/components/features/auth/SocialButtons.vue new file mode 100644 index 0000000..f9f226c --- /dev/null +++ b/web/src/components/features/auth/SocialButtons.vue @@ -0,0 +1,67 @@ + + + diff --git a/web/src/components/features/auth/auth.helper.ts b/web/src/components/features/auth/auth.helper.ts new file mode 100644 index 0000000..b713b3b --- /dev/null +++ b/web/src/components/features/auth/auth.helper.ts @@ -0,0 +1,9 @@ +import { useUserStore } from '@/stores/user.store' +import { Router } from 'vue-router' + +export const redirectToUser = async (router: Router) => { + const userStore: ReturnType = useUserStore() + if (userStore.accessToken) { + await router.push({ name: 'user', params: { user: userStore.login } }) + } +} \ No newline at end of file diff --git a/web/src/components/features/base/TvCollapseSidebarDesktop.vue b/web/src/components/features/base/TvCollapseSidebarDesktop.vue new file mode 100644 index 0000000..449f62c --- /dev/null +++ b/web/src/components/features/base/TvCollapseSidebarDesktop.vue @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/web/src/components/features/base/TvContextMenu.vue b/web/src/components/features/base/TvContextMenu.vue new file mode 100644 index 0000000..515f0af --- /dev/null +++ b/web/src/components/features/base/TvContextMenu.vue @@ -0,0 +1,64 @@ + + + diff --git a/web/src/components/features/base/TvGoalLikeItem.vue b/web/src/components/features/base/TvGoalLikeItem.vue new file mode 100644 index 0000000..7bb9b06 --- /dev/null +++ b/web/src/components/features/base/TvGoalLikeItem.vue @@ -0,0 +1,19 @@ + + \ No newline at end of file diff --git a/web/src/components/features/base/TvMobileBottomNav.vue b/web/src/components/features/base/TvMobileBottomNav.vue new file mode 100644 index 0000000..358c8d9 --- /dev/null +++ b/web/src/components/features/base/TvMobileBottomNav.vue @@ -0,0 +1,110 @@ + + + diff --git a/web/src/components/features/collaboration/CollaborationPanel.vue b/web/src/components/features/collaboration/CollaborationPanel.vue new file mode 100644 index 0000000..5aeae7c --- /dev/null +++ b/web/src/components/features/collaboration/CollaborationPanel.vue @@ -0,0 +1,163 @@ + + + diff --git a/web/src/components/features/collaboration/parts/members/MemberAddInput.vue b/web/src/components/features/collaboration/parts/members/MemberAddInput.vue new file mode 100644 index 0000000..0f43013 --- /dev/null +++ b/web/src/components/features/collaboration/parts/members/MemberAddInput.vue @@ -0,0 +1,63 @@ + + + diff --git a/web/src/components/features/collaboration/parts/members/MemberEditModal.vue b/web/src/components/features/collaboration/parts/members/MemberEditModal.vue new file mode 100644 index 0000000..97a1933 --- /dev/null +++ b/web/src/components/features/collaboration/parts/members/MemberEditModal.vue @@ -0,0 +1,111 @@ + + + diff --git a/web/src/components/features/collaboration/parts/members/MemberItem.vue b/web/src/components/features/collaboration/parts/members/MemberItem.vue new file mode 100644 index 0000000..88a8bfa --- /dev/null +++ b/web/src/components/features/collaboration/parts/members/MemberItem.vue @@ -0,0 +1,78 @@ + + + diff --git a/web/src/components/features/collaboration/parts/members/MembersList.vue b/web/src/components/features/collaboration/parts/members/MembersList.vue new file mode 100644 index 0000000..28de627 --- /dev/null +++ b/web/src/components/features/collaboration/parts/members/MembersList.vue @@ -0,0 +1,131 @@ + + + diff --git a/web/src/components/features/collaboration/parts/roles/PermissionsEditor.vue b/web/src/components/features/collaboration/parts/roles/PermissionsEditor.vue new file mode 100644 index 0000000..9ff011d --- /dev/null +++ b/web/src/components/features/collaboration/parts/roles/PermissionsEditor.vue @@ -0,0 +1,128 @@ + + + diff --git a/web/src/components/features/collaboration/parts/roles/RoleAddInput.vue b/web/src/components/features/collaboration/parts/roles/RoleAddInput.vue new file mode 100644 index 0000000..36bedfc --- /dev/null +++ b/web/src/components/features/collaboration/parts/roles/RoleAddInput.vue @@ -0,0 +1,55 @@ + + + diff --git a/web/src/components/features/collaboration/parts/roles/RoleDeleteDialog.vue b/web/src/components/features/collaboration/parts/roles/RoleDeleteDialog.vue new file mode 100644 index 0000000..81d70b5 --- /dev/null +++ b/web/src/components/features/collaboration/parts/roles/RoleDeleteDialog.vue @@ -0,0 +1,53 @@ + + + diff --git a/web/src/components/features/collaboration/parts/roles/RoleEditModal.vue b/web/src/components/features/collaboration/parts/roles/RoleEditModal.vue new file mode 100644 index 0000000..269cedd --- /dev/null +++ b/web/src/components/features/collaboration/parts/roles/RoleEditModal.vue @@ -0,0 +1,71 @@ + + + diff --git a/web/src/components/features/collaboration/parts/roles/RoleItem.vue b/web/src/components/features/collaboration/parts/roles/RoleItem.vue new file mode 100644 index 0000000..e294023 --- /dev/null +++ b/web/src/components/features/collaboration/parts/roles/RoleItem.vue @@ -0,0 +1,40 @@ + + + diff --git a/web/src/components/features/collaboration/parts/roles/RolesList.vue b/web/src/components/features/collaboration/parts/roles/RolesList.vue new file mode 100644 index 0000000..ccbf341 --- /dev/null +++ b/web/src/components/features/collaboration/parts/roles/RolesList.vue @@ -0,0 +1,156 @@ + + + diff --git a/web/src/components/features/collaboration/types.ts b/web/src/components/features/collaboration/types.ts new file mode 100644 index 0000000..98a09fa --- /dev/null +++ b/web/src/components/features/collaboration/types.ts @@ -0,0 +1,68 @@ +export interface Permission { + id: string + key: string + name: string + description: string + group: 'tasks' | 'lists' | 'members' | 'roles' +} + +export interface Role { + id: string + name: string + permissions: string[] + isSystem?: boolean +} + +export interface Member { + id: string + email: string + name?: string + avatar?: string + roleIds: string[] + status: 'pending' | 'active' + invitedAt: Date +} + +export const PERMISSIONS: Permission[] = [ + // Tasks + { id: 'view_tasks', key: 'view_tasks', name: 'View Tasks', description: 'Can view tasks in the project', group: 'tasks' }, + { id: 'create_tasks', key: 'create_tasks', name: 'Create Tasks', description: 'Can create new tasks', group: 'tasks' }, + { id: 'edit_tasks', key: 'edit_tasks', name: 'Edit Tasks', description: 'Can edit existing tasks', group: 'tasks' }, + { id: 'delete_tasks', key: 'delete_tasks', name: 'Delete Tasks', description: 'Can delete tasks', group: 'tasks' }, + + // Lists + { id: 'view_lists', key: 'view_lists', name: 'View Lists', description: 'Can view lists in the project', group: 'lists' }, + { id: 'create_lists', key: 'create_lists', name: 'Create Lists', description: 'Can create new lists', group: 'lists' }, + { id: 'edit_lists', key: 'edit_lists', name: 'Edit Lists', description: 'Can edit existing lists', group: 'lists' }, + { id: 'delete_lists', key: 'delete_lists', name: 'Delete Lists', description: 'Can delete lists', group: 'lists' }, + + // Members + { id: 'view_members', key: 'view_members', name: 'View Members', description: 'Can view project members', group: 'members' }, + { id: 'invite_members', key: 'invite_members', name: 'Invite Members', description: 'Can invite new members', group: 'members' }, + { id: 'remove_members', key: 'remove_members', name: 'Remove Members', description: 'Can remove members from project', group: 'members' }, + + // Roles + { id: 'view_roles', key: 'view_roles', name: 'View Roles', description: 'Can view roles', group: 'roles' }, + { id: 'manage_roles', key: 'manage_roles', name: 'Manage Roles', description: 'Can create, edit and delete roles', group: 'roles' }, +] + +export const DEFAULT_ROLES: Role[] = [ + { + id: 'admin', + name: 'Admin', + permissions: PERMISSIONS.map(p => p.id), + isSystem: true, + }, + { + id: 'member', + name: 'Member', + permissions: ['view_tasks', 'create_tasks', 'edit_tasks', 'view_lists', 'create_lists', 'edit_lists', 'view_members'], + isSystem: true, + }, + { + id: 'viewer', + name: 'Viewer', + permissions: ['view_tasks', 'view_lists', 'view_members'], + isSystem: true, + }, +] diff --git a/web/src/components/features/graph/AddNewTaskToGraph.vue b/web/src/components/features/graph/AddNewTaskToGraph.vue new file mode 100644 index 0000000..58e57d8 --- /dev/null +++ b/web/src/components/features/graph/AddNewTaskToGraph.vue @@ -0,0 +1,94 @@ + + + diff --git a/web/src/components/features/graph/ProjectGraph.vue b/web/src/components/features/graph/ProjectGraph.vue new file mode 100644 index 0000000..937b8aa --- /dev/null +++ b/web/src/components/features/graph/ProjectGraph.vue @@ -0,0 +1,274 @@ + + + + + diff --git a/web/src/components/features/graph/TaskNode.vue b/web/src/components/features/graph/TaskNode.vue new file mode 100644 index 0000000..1591713 --- /dev/null +++ b/web/src/components/features/graph/TaskNode.vue @@ -0,0 +1,78 @@ + + + diff --git a/web/src/components/features/graph/composables/useLayout.ts b/web/src/components/features/graph/composables/useLayout.ts new file mode 100644 index 0000000..cc0063e --- /dev/null +++ b/web/src/components/features/graph/composables/useLayout.ts @@ -0,0 +1,120 @@ +import dagre from '@dagrejs/dagre' +import { type Edge, type Node, Position, useVueFlow } from '@vue-flow/core' +import { ref } from 'vue' + +/** + * Composable to run the layout algorithm on the graph. + * It uses the `dagre` library to calculate the layout of the nodes and edges. + */ +export function useLayout() { + const { findNode } = useVueFlow() + + const graph = ref(new dagre.graphlib.Graph()) + + const previousDirection = ref('LR') + + function layout(nodes: Node[], edges: Edge[], direction: 'LR' | 'TB') { + // we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there + const dagreGraph = new dagre.graphlib.Graph() + + graph.value = dagreGraph + + dagreGraph.setDefaultEdgeLabel(() => ({})) + + const isHorizontal = direction === 'LR' + dagreGraph.setGraph({ + rankdir: direction, + // align: 'UL', // Align to upper left + nodesep: 50, // Minimum space between nodes + ranksep: 100, // Minimum space between ranks + marginx: 20, + marginy: 20, + }) + + previousDirection.value = direction + + for (const node of nodes) { + // if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type) + const graphNode = findNode(node.id) + + dagreGraph.setNode(node.id, { + width: graphNode?.dimensions.width || 150, + height: graphNode?.dimensions.height || 50, + }) + } + + for (const edge of edges) { + dagreGraph.setEdge(edge.source, edge.target) + } + + dagre.layout(dagreGraph) + + // set nodes with updated positions + const layoutedNodes = nodes.map((node) => { + const nodeWithPosition = dagreGraph.node(node.id) + + return { + ...node, + targetPosition: isHorizontal ? Position.Left : Position.Top, + sourcePosition: isHorizontal ? Position.Right : Position.Bottom, + position: { x: nodeWithPosition.x, y: nodeWithPosition.y }, + } + }) + + // For TB mode, invert Y coordinates to put root at top + if (!isHorizontal) { + const maxY = Math.max(...layoutedNodes.map((node) => node.position.y)) + + layoutedNodes.forEach((node) => { + const graphNode = findNode(node.id) + const nodeHeight = graphNode?.dimensions.height || 50 + + // Invert Y coordinate and adjust for node height to keep center aligned + node.position.y = maxY - node.position.y + nodeHeight + }) + } + + return layoutedNodes + } + + // function layout(nodes: Node[], edges: Edge[], direction: 'LR' | 'TB') { + // // we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there + // const dagreGraph = new dagre.graphlib.Graph() + + // graph.value = dagreGraph + + // dagreGraph.setDefaultEdgeLabel(() => ({})) + + // const isHorizontal = direction === 'LR' + // dagreGraph.setGraph({ rankdir: direction }) + + // previousDirection.value = direction + + // for (const node of nodes) { + // // if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type) + // const graphNode = findNode(node.id) + + // dagreGraph.setNode(node.id, { width: graphNode.dimensions.width || 150, height: graphNode.dimensions.height || 50 }) + // } + + // for (const edge of edges) { + // dagreGraph.setEdge(edge.source, edge.target) + // } + + // dagre.layout(dagreGraph) + + // // set nodes with updated positions + // return nodes.map((node) => { + // const nodeWithPosition = dagreGraph.node(node.id) + + // return { + // ...node, + // targetPosition: isHorizontal ? Position.Left : Position.Top, + // sourcePosition: isHorizontal ? Position.Right : Position.Bottom, + // position: { x: nodeWithPosition.x, y: nodeWithPosition.y }, + // } + // }) + // } + + return { graph, layout, previousDirection } +} diff --git a/web/src/components/features/kanban/KanbanBoard.vue b/web/src/components/features/kanban/KanbanBoard.vue new file mode 100644 index 0000000..dadbcfd --- /dev/null +++ b/web/src/components/features/kanban/KanbanBoard.vue @@ -0,0 +1,309 @@ + + + \ No newline at end of file diff --git a/web/src/components/features/kanban/parts/KanbanAddStatus.vue b/web/src/components/features/kanban/parts/KanbanAddStatus.vue new file mode 100644 index 0000000..1bfd8a8 --- /dev/null +++ b/web/src/components/features/kanban/parts/KanbanAddStatus.vue @@ -0,0 +1,38 @@ + + + diff --git a/web/src/components/features/kanban/parts/KanbanAddTask.vue b/web/src/components/features/kanban/parts/KanbanAddTask.vue new file mode 100644 index 0000000..99df78e --- /dev/null +++ b/web/src/components/features/kanban/parts/KanbanAddTask.vue @@ -0,0 +1,53 @@ + + + diff --git a/web/src/components/features/kanban/parts/KanbanDeleteModal.vue b/web/src/components/features/kanban/parts/KanbanDeleteModal.vue new file mode 100644 index 0000000..f2198e6 --- /dev/null +++ b/web/src/components/features/kanban/parts/KanbanDeleteModal.vue @@ -0,0 +1,61 @@ + + + diff --git a/web/src/components/features/kanban/parts/KanbanEditModal.vue b/web/src/components/features/kanban/parts/KanbanEditModal.vue new file mode 100644 index 0000000..1212675 --- /dev/null +++ b/web/src/components/features/kanban/parts/KanbanEditModal.vue @@ -0,0 +1,73 @@ + + + diff --git a/web/src/components/features/kanban/parts/KanbanTitleMenu.vue b/web/src/components/features/kanban/parts/KanbanTitleMenu.vue new file mode 100644 index 0000000..7f667d2 --- /dev/null +++ b/web/src/components/features/kanban/parts/KanbanTitleMenu.vue @@ -0,0 +1,67 @@ + + + diff --git a/web/src/components/features/lists/ListsArchive.vue b/web/src/components/features/lists/ListsArchive.vue new file mode 100644 index 0000000..e0fa1ba --- /dev/null +++ b/web/src/components/features/lists/ListsArchive.vue @@ -0,0 +1,43 @@ + + + diff --git a/web/src/components/features/lists/ListsContainer.vue b/web/src/components/features/lists/ListsContainer.vue new file mode 100644 index 0000000..5ec2e51 --- /dev/null +++ b/web/src/components/features/lists/ListsContainer.vue @@ -0,0 +1,112 @@ + + + diff --git a/web/src/components/features/lists/ListsList.vue b/web/src/components/features/lists/ListsList.vue new file mode 100644 index 0000000..8a0220b --- /dev/null +++ b/web/src/components/features/lists/ListsList.vue @@ -0,0 +1,146 @@ + + + diff --git a/web/src/components/features/lists/parts/ListAddInput.vue b/web/src/components/features/lists/parts/ListAddInput.vue new file mode 100644 index 0000000..b7091db --- /dev/null +++ b/web/src/components/features/lists/parts/ListAddInput.vue @@ -0,0 +1,51 @@ + + + diff --git a/web/src/components/features/lists/parts/ListDeleteDialog.vue b/web/src/components/features/lists/parts/ListDeleteDialog.vue new file mode 100644 index 0000000..14b856e --- /dev/null +++ b/web/src/components/features/lists/parts/ListDeleteDialog.vue @@ -0,0 +1,51 @@ + + + diff --git a/web/src/components/features/lists/parts/ListEditModal.vue b/web/src/components/features/lists/parts/ListEditModal.vue new file mode 100644 index 0000000..df3eee1 --- /dev/null +++ b/web/src/components/features/lists/parts/ListEditModal.vue @@ -0,0 +1,71 @@ + + + diff --git a/web/src/components/features/lists/parts/ListsListBase.vue b/web/src/components/features/lists/parts/ListsListBase.vue new file mode 100644 index 0000000..70db1d7 --- /dev/null +++ b/web/src/components/features/lists/parts/ListsListBase.vue @@ -0,0 +1,191 @@ + + + diff --git a/web/src/components/features/lists/types.ts b/web/src/components/features/lists/types.ts new file mode 100644 index 0000000..3c9ee35 --- /dev/null +++ b/web/src/components/features/lists/types.ts @@ -0,0 +1,3 @@ +import type { GoalListItem } from 'taskview-api' + +export type List = GoalListItem diff --git a/web/src/components/features/main/MainScreen.vue b/web/src/components/features/main/MainScreen.vue new file mode 100644 index 0000000..92d9545 --- /dev/null +++ b/web/src/components/features/main/MainScreen.vue @@ -0,0 +1,25 @@ + + + diff --git a/web/src/components/features/main/screen-main/DesktopScreen.vue b/web/src/components/features/main/screen-main/DesktopScreen.vue new file mode 100644 index 0000000..f83ae04 --- /dev/null +++ b/web/src/components/features/main/screen-main/DesktopScreen.vue @@ -0,0 +1,47 @@ + + diff --git a/web/src/components/features/main/screen-main/parts/MainScreenAddTaskDialog.vue b/web/src/components/features/main/screen-main/parts/MainScreenAddTaskDialog.vue new file mode 100644 index 0000000..d547de2 --- /dev/null +++ b/web/src/components/features/main/screen-main/parts/MainScreenAddTaskDialog.vue @@ -0,0 +1,252 @@ + + + diff --git a/web/src/components/features/main/screen-main/parts/MainScreenWidgetTemplate.vue b/web/src/components/features/main/screen-main/parts/MainScreenWidgetTemplate.vue new file mode 100644 index 0000000..a92574d --- /dev/null +++ b/web/src/components/features/main/screen-main/parts/MainScreenWidgetTemplate.vue @@ -0,0 +1,56 @@ + + + diff --git a/web/src/components/features/main/screen-main/parts/NoGoals.vue b/web/src/components/features/main/screen-main/parts/NoGoals.vue new file mode 100644 index 0000000..afd5599 --- /dev/null +++ b/web/src/components/features/main/screen-main/parts/NoGoals.vue @@ -0,0 +1,28 @@ + + diff --git a/web/src/components/features/main/screen-main/parts/SearchAll.vue b/web/src/components/features/main/screen-main/parts/SearchAll.vue new file mode 100644 index 0000000..99cd874 --- /dev/null +++ b/web/src/components/features/main/screen-main/parts/SearchAll.vue @@ -0,0 +1,141 @@ + + + diff --git a/web/src/components/features/main/screen-main/parts/WidgetBase.vue b/web/src/components/features/main/screen-main/parts/WidgetBase.vue new file mode 100644 index 0000000..7d9b467 --- /dev/null +++ b/web/src/components/features/main/screen-main/parts/WidgetBase.vue @@ -0,0 +1,128 @@ + + diff --git a/web/src/components/features/main/screen-main/parts/WidgetLastAddedTasks.vue b/web/src/components/features/main/screen-main/parts/WidgetLastAddedTasks.vue new file mode 100644 index 0000000..ccfa2ce --- /dev/null +++ b/web/src/components/features/main/screen-main/parts/WidgetLastAddedTasks.vue @@ -0,0 +1,146 @@ + + diff --git a/web/src/components/features/main/screen-main/parts/WidgetToday.vue b/web/src/components/features/main/screen-main/parts/WidgetToday.vue new file mode 100644 index 0000000..0c4244c --- /dev/null +++ b/web/src/components/features/main/screen-main/parts/WidgetToday.vue @@ -0,0 +1,62 @@ + + diff --git a/web/src/components/features/main/screen-main/parts/WidgetUpcoming.vue b/web/src/components/features/main/screen-main/parts/WidgetUpcoming.vue new file mode 100644 index 0000000..bd3bde8 --- /dev/null +++ b/web/src/components/features/main/screen-main/parts/WidgetUpcoming.vue @@ -0,0 +1,59 @@ + + diff --git a/web/src/components/features/main/screen-main/useWidgetScrollContainer.ts b/web/src/components/features/main/screen-main/useWidgetScrollContainer.ts new file mode 100644 index 0000000..8d8da9f --- /dev/null +++ b/web/src/components/features/main/screen-main/useWidgetScrollContainer.ts @@ -0,0 +1,5 @@ +export const useWidgetScrollContainer = () => { + return { + class: 'flex flex-col gap-2 overflow-y-auto overflow-x-hidden h-full md:max-h-[400px] p-2 md:p-2', + } +} diff --git a/web/src/components/features/projects/ArchiveList.vue b/web/src/components/features/projects/ArchiveList.vue new file mode 100644 index 0000000..defc1fe --- /dev/null +++ b/web/src/components/features/projects/ArchiveList.vue @@ -0,0 +1,44 @@ + + + diff --git a/web/src/components/features/projects/ProjectsList.vue b/web/src/components/features/projects/ProjectsList.vue new file mode 100644 index 0000000..e60a467 --- /dev/null +++ b/web/src/components/features/projects/ProjectsList.vue @@ -0,0 +1,48 @@ + + + diff --git a/web/src/components/features/projects/ProjectsSidebar.vue b/web/src/components/features/projects/ProjectsSidebar.vue new file mode 100644 index 0000000..a2d8feb --- /dev/null +++ b/web/src/components/features/projects/ProjectsSidebar.vue @@ -0,0 +1,73 @@ + + + diff --git a/web/src/components/features/projects/parts/ProjectAddInput.vue b/web/src/components/features/projects/parts/ProjectAddInput.vue new file mode 100644 index 0000000..4238eb2 --- /dev/null +++ b/web/src/components/features/projects/parts/ProjectAddInput.vue @@ -0,0 +1,54 @@ + + + diff --git a/web/src/components/features/projects/parts/ProjectDeleteDialog.vue b/web/src/components/features/projects/parts/ProjectDeleteDialog.vue new file mode 100644 index 0000000..788ed89 --- /dev/null +++ b/web/src/components/features/projects/parts/ProjectDeleteDialog.vue @@ -0,0 +1,59 @@ + + + diff --git a/web/src/components/features/projects/parts/ProjectEditModal.vue b/web/src/components/features/projects/parts/ProjectEditModal.vue new file mode 100644 index 0000000..d406935 --- /dev/null +++ b/web/src/components/features/projects/parts/ProjectEditModal.vue @@ -0,0 +1,91 @@ + + + diff --git a/web/src/components/features/projects/parts/ProjectListBase.vue b/web/src/components/features/projects/parts/ProjectListBase.vue new file mode 100644 index 0000000..5df065d --- /dev/null +++ b/web/src/components/features/projects/parts/ProjectListBase.vue @@ -0,0 +1,255 @@ + + + diff --git a/web/src/components/features/projects/types.ts b/web/src/components/features/projects/types.ts new file mode 100644 index 0000000..7f7dc8c --- /dev/null +++ b/web/src/components/features/projects/types.ts @@ -0,0 +1,3 @@ +import type { GoalItem } from 'taskview-api' + +export type Project = GoalItem diff --git a/web/src/components/features/tasks/TaskDetailPanel.vue b/web/src/components/features/tasks/TaskDetailPanel.vue new file mode 100644 index 0000000..b836383 --- /dev/null +++ b/web/src/components/features/tasks/TaskDetailPanel.vue @@ -0,0 +1,184 @@ + + + diff --git a/web/src/components/features/tasks/TasksContainer.vue b/web/src/components/features/tasks/TasksContainer.vue new file mode 100644 index 0000000..8b862bd --- /dev/null +++ b/web/src/components/features/tasks/TasksContainer.vue @@ -0,0 +1,79 @@ + + + diff --git a/web/src/components/features/tasks/TasksPanel.vue b/web/src/components/features/tasks/TasksPanel.vue new file mode 100644 index 0000000..cb6dda1 --- /dev/null +++ b/web/src/components/features/tasks/TasksPanel.vue @@ -0,0 +1,167 @@ + + + diff --git a/web/src/components/features/tasks/TvTaskDetailOverlay.vue b/web/src/components/features/tasks/TvTaskDetailOverlay.vue new file mode 100644 index 0000000..05238b7 --- /dev/null +++ b/web/src/components/features/tasks/TvTaskDetailOverlay.vue @@ -0,0 +1,146 @@ + + + diff --git a/web/src/components/features/tasks/parts/NoteEditor.vue b/web/src/components/features/tasks/parts/NoteEditor.vue new file mode 100644 index 0000000..9906b9f --- /dev/null +++ b/web/src/components/features/tasks/parts/NoteEditor.vue @@ -0,0 +1,233 @@ + + + + + diff --git a/web/src/components/features/tasks/parts/TaskAmountEditor.vue b/web/src/components/features/tasks/parts/TaskAmountEditor.vue new file mode 100644 index 0000000..a16708a --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskAmountEditor.vue @@ -0,0 +1,151 @@ + + + + diff --git a/web/src/components/features/tasks/parts/TaskAssigneeSelect.vue b/web/src/components/features/tasks/parts/TaskAssigneeSelect.vue new file mode 100644 index 0000000..19f074a --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskAssigneeSelect.vue @@ -0,0 +1,118 @@ + + + diff --git a/web/src/components/features/tasks/parts/TaskDeadline.vue b/web/src/components/features/tasks/parts/TaskDeadline.vue new file mode 100644 index 0000000..4787097 --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskDeadline.vue @@ -0,0 +1,411 @@ + + + diff --git a/web/src/components/features/tasks/parts/TaskDeleteDialog.vue b/web/src/components/features/tasks/parts/TaskDeleteDialog.vue new file mode 100644 index 0000000..2619203 --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskDeleteDialog.vue @@ -0,0 +1,51 @@ + + + diff --git a/web/src/components/features/tasks/parts/TaskHistory.vue b/web/src/components/features/tasks/parts/TaskHistory.vue new file mode 100644 index 0000000..50f674a --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskHistory.vue @@ -0,0 +1,238 @@ + + + diff --git a/web/src/components/features/tasks/parts/TaskItem.vue b/web/src/components/features/tasks/parts/TaskItem.vue new file mode 100644 index 0000000..456ca69 --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskItem.vue @@ -0,0 +1,237 @@ + + + diff --git a/web/src/components/features/tasks/parts/TaskKanbanStatusSelect.vue b/web/src/components/features/tasks/parts/TaskKanbanStatusSelect.vue new file mode 100644 index 0000000..72b9fdb --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskKanbanStatusSelect.vue @@ -0,0 +1,79 @@ + + + diff --git a/web/src/components/features/tasks/parts/TaskListSelect.vue b/web/src/components/features/tasks/parts/TaskListSelect.vue new file mode 100644 index 0000000..3fd24e4 --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskListSelect.vue @@ -0,0 +1,105 @@ + + + diff --git a/web/src/components/features/tasks/parts/TaskPrioritySelect.vue b/web/src/components/features/tasks/parts/TaskPrioritySelect.vue new file mode 100644 index 0000000..747f6e6 --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskPrioritySelect.vue @@ -0,0 +1,85 @@ + + + diff --git a/web/src/components/features/tasks/parts/TaskSearchAdd.vue b/web/src/components/features/tasks/parts/TaskSearchAdd.vue new file mode 100644 index 0000000..caeebd0 --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskSearchAdd.vue @@ -0,0 +1,162 @@ + + + diff --git a/web/src/components/features/tasks/parts/TaskSubtaskItem.vue b/web/src/components/features/tasks/parts/TaskSubtaskItem.vue new file mode 100644 index 0000000..6db9211 --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskSubtaskItem.vue @@ -0,0 +1,90 @@ + + + diff --git a/web/src/components/features/tasks/parts/TaskSubtasks.vue b/web/src/components/features/tasks/parts/TaskSubtasks.vue new file mode 100644 index 0000000..cf50426 --- /dev/null +++ b/web/src/components/features/tasks/parts/TaskSubtasks.vue @@ -0,0 +1,107 @@ + + + diff --git a/web/src/components/features/tasks/parts/TasksFilterDrawer.vue b/web/src/components/features/tasks/parts/TasksFilterDrawer.vue new file mode 100644 index 0000000..5fed9f5 --- /dev/null +++ b/web/src/components/features/tasks/parts/TasksFilterDrawer.vue @@ -0,0 +1,215 @@ + + + diff --git a/web/src/components/features/tasks/parts/TasksToolbar.vue b/web/src/components/features/tasks/parts/TasksToolbar.vue new file mode 100644 index 0000000..c751d46 --- /dev/null +++ b/web/src/components/features/tasks/parts/TasksToolbar.vue @@ -0,0 +1,217 @@ + + + diff --git a/web/src/components/features/tasks/parts/tags/TagEditDialog.vue b/web/src/components/features/tasks/parts/tags/TagEditDialog.vue new file mode 100644 index 0000000..ea01f91 --- /dev/null +++ b/web/src/components/features/tasks/parts/tags/TagEditDialog.vue @@ -0,0 +1,143 @@ + + + diff --git a/web/src/components/features/tasks/parts/tags/TagItem.vue b/web/src/components/features/tasks/parts/tags/TagItem.vue new file mode 100644 index 0000000..65935ad --- /dev/null +++ b/web/src/components/features/tasks/parts/tags/TagItem.vue @@ -0,0 +1,83 @@ + + + diff --git a/web/src/components/features/tasks/parts/tags/TagSearchAdd.vue b/web/src/components/features/tasks/parts/tags/TagSearchAdd.vue new file mode 100644 index 0000000..1f845f4 --- /dev/null +++ b/web/src/components/features/tasks/parts/tags/TagSearchAdd.vue @@ -0,0 +1,117 @@ + + + diff --git a/web/src/components/features/tasks/parts/tags/TaskTagsManager.vue b/web/src/components/features/tasks/parts/tags/TaskTagsManager.vue new file mode 100644 index 0000000..9767b50 --- /dev/null +++ b/web/src/components/features/tasks/parts/tags/TaskTagsManager.vue @@ -0,0 +1,185 @@ + + + diff --git a/web/src/composables/useAdditionalServer.ts b/web/src/composables/useAdditionalServer.ts new file mode 100644 index 0000000..dc8fe0d --- /dev/null +++ b/web/src/composables/useAdditionalServer.ts @@ -0,0 +1,59 @@ +import { storeToRefs } from 'pinia' +import { ref } from 'vue' +import $api from '@/helpers/axios' +import { $ls, $tvApi } from '@/plugins/axios' +import { additionalUrlStore } from '@/stores/additional-url.store' + +export const LS_KEY_ADDITIONAL_SERVERS = 'additionalServers' +export const LS_KEY_MAIN_SERVER = 'mainServer' + +/** + * Use this composition to get and set additional servers and main server for API requests + * User can add additional servers and set main server from local storage + * Also user can delete additional server + * @returns + */ +export const useAdditionalServer = async () => { + const { allServers, mainServer, systemServer } = storeToRefs(additionalUrlStore()) + const serversFromLocalStorage = ref(await $ls.getValue(LS_KEY_ADDITIONAL_SERVERS)) + const mainServerFromLocalStorage = await $ls.getValue(LS_KEY_MAIN_SERVER) + + allServers.value = serversFromLocalStorage.value ? [...JSON.parse(serversFromLocalStorage.value)] : [] + + mainServer.value = + mainServerFromLocalStorage || + (process.env.NODE_ENV !== 'production' ? 'http://localhost:1401' : 'https://apitaskview.handscream.com') + + systemServer.value = + process.env.NODE_ENV !== 'production' ? 'http://localhost:1401' : 'https://apitaskview.handscream.com' + + const setMainServer = (server: string) => { + mainServer.value = server + $ls.setValue(LS_KEY_MAIN_SERVER, server) + $api.defaults.baseURL = server + $tvApi?.setBaseUrl(server) + } + + const addServer = (server: string) => { + allServers.value.push(server) + $ls.setValue(LS_KEY_ADDITIONAL_SERVERS, allServers.value) + } + + const deleteServer = (server: string) => { + allServers.value = allServers.value.filter((s) => s !== server) + $ls.setValue(LS_KEY_ADDITIONAL_SERVERS, allServers.value) + + if (mainServer.value === server) { + setMainServer(systemServer.value) + } + } + + return { + mainServer, + allServers, + setMainServer, + addServer, + systemServer, + deleteServer, + } +} diff --git a/web/src/composables/useAppRouteInfo.ts b/web/src/composables/useAppRouteInfo.ts new file mode 100644 index 0000000..d4dbeb9 --- /dev/null +++ b/web/src/composables/useAppRouteInfo.ts @@ -0,0 +1,23 @@ +import { computed } from 'vue' +import { useRoute } from 'vue-router' + +export const useAppRouteInfo = () => { + const route = useRoute() + const isUserRoute = computed(() => route.name === 'user') + const isAccountRoute = computed(() => route.name === 'account') + const projectId = computed(() => Number(route.params.projectId) || -1) + const hasProject = computed(() => projectId.value > 0) + const listId = computed(() => Number(route.params.listId) || -1) + const hasList = computed(() => !!route.params.listId) + const taskId = computed(() => Number(route.params.taskId) || -1) + + return { + isUserRoute, + isAccountRoute, + hasProject, + projectId, + listId, + hasList, + taskId, + } +} \ No newline at end of file diff --git a/web/src/composables/useColotMode.ts b/web/src/composables/useColotMode.ts new file mode 100644 index 0000000..d772258 --- /dev/null +++ b/web/src/composables/useColotMode.ts @@ -0,0 +1,11 @@ +import { computed } from 'vue' +import { useColorMode } from '@vueuse/core' + +export const useColor = () => { + const colorMode = useColorMode() + const isDark = computed(() => colorMode.value === 'dark') + return { + colorMode, + isDark, + } +} \ No newline at end of file diff --git a/web/src/composables/useDashboard.ts b/web/src/composables/useDashboard.ts new file mode 100644 index 0000000..ba6578a --- /dev/null +++ b/web/src/composables/useDashboard.ts @@ -0,0 +1,26 @@ +import { ref, watch } from 'vue' +import { useRoute } from 'vue-router' +import { createSharedComposable } from '@vueuse/core' + +const _useDashboard = () => { + const route = useRoute() + const isSidebarOpen = ref(true) + const isSidebarCollapsed = ref(false) + + watch( + () => route.fullPath, + () => { + if (window.innerWidth < 1024) { + isSidebarOpen.value = false + } + }, + { immediate: true }, + ) + + return { + isSidebarOpen, + isSidebarCollapsed, + } +} + +export const useDashboard = createSharedComposable(_useDashboard) diff --git a/web/src/composables/useGoalPermissions.ts b/web/src/composables/useGoalPermissions.ts new file mode 100644 index 0000000..6fb6d34 --- /dev/null +++ b/web/src/composables/useGoalPermissions.ts @@ -0,0 +1,112 @@ +import { computed, provide, inject, type ComputedRef, type InjectionKey, type Ref } from 'vue' +import { useGoalsStore } from '@/stores/goals.store' +import { AllGoalPermissions } from '@/types/goals.types' +import type { GoalItem } from 'taskview-api' + +type PermissionName = (keyof typeof AllGoalPermissions); + +const GOAL_CONTEXT_KEY: InjectionKey> = Symbol('goalPermissionsContext') + +export function provideGoalPermissions(goal: Ref) { + provide(GOAL_CONTEXT_KEY, goal) +} + +function hasPermission(goal: GoalItem | null | undefined, perm: PermissionName): boolean { + return !!goal?.permissions[AllGoalPermissions[perm]] +} + +export function useGoalPermissionsFor(goalRef: Ref) { + const can = (perm: PermissionName) => computed(() => hasPermission(goalRef.value, perm)) + + return { + canDeleteGoal: can('GOAL_CAN_DELETE'), + canEditGoal: can('GOAL_CAN_EDIT'), + canManageUsers: can('GOAL_CAN_MANAGE_USERS'), + canViewKanban: computed(() => hasPermission(goalRef.value, 'KANBAN_CAN_VIEW') || hasPermission(goalRef.value, 'KANBAN_CAN_MANAGE')), + canViewGraph: computed(() => hasPermission(goalRef.value, 'GRAPH_CAN_VIEW') || hasPermission(goalRef.value, 'GRAPH_CAN_MANAGE')), + } +} + +export const useGoalPermissions = () => { + const injectedGoal = inject(GOAL_CONTEXT_KEY, null) + const goalsStore = useGoalsStore() + const goal = computed(() => injectedGoal?.value ?? goalsStore.selectedGoal ?? null) + + const cache = {} as Record> + + const permissions = new Proxy({} as Record, { + get(_, prop: PermissionName) { + if (!cache[prop]) { + cache[prop] = computed(() => { + return !!goal.value?.permissions[AllGoalPermissions[prop]] + }) + } + return cache[prop].value + }, + }) + + const canDeleteGoal = computed(() => permissions.GOAL_CAN_DELETE) + const canEditGoal = computed(() => permissions.GOAL_CAN_EDIT) + const canManageUsers = computed(() => permissions.GOAL_CAN_MANAGE_USERS) + const canAddTaskList = computed(() => permissions.GOAL_CAN_ADD_TASK_LIST) + const canViewLists = computed(() => permissions.GOAL_CAN_WATCH_CONTENT) + const canDeleteList = computed(() => permissions.COMPONENT_CAN_DELETE) + const canEditList = computed(() => permissions.COMPONENT_CAN_EDIT) + const canViewTasks = computed(() => permissions.COMPONENT_CAN_WATCH_CONTENT) + const canAddTask = computed(() => permissions.COMPONENT_CAN_ADD_TASKS) + const canDeleteTask = computed(() => permissions.TASK_CAN_DELETE) + const canEditTaskDescription = computed(() => permissions.TASK_CAN_EDIT_DESCRIPTION) + const canEditTaskStatus = computed(() => permissions.TASK_CAN_EDIT_STATUS) + const canEditTaskNote = computed(() => permissions.TASK_CAN_EDIT_NOTE) + const canViewTaskNote = computed(() => permissions.TASK_CAN_WATCH_NOTE) + const canEditTaskDeadline = computed(() => permissions.TASK_CAN_EDIT_DEADLINE) + const canViewTaskDetails = computed(() => permissions.TASK_CAN_WATCH_DETAILS) + const canViewTaskSubtasks = computed(() => permissions.TASK_CAN_WATCH_SUBTASKS) + const canAddTaskSubtasks = computed(() => permissions.TASK_CAN_ADD_SUBTASKS) + const canEditTaskTags = computed(() => permissions.TASK_CAN_EDIT_TAGS) + const canViewTaskTags = computed(() => permissions.TASK_CAN_WATCH_TAGS) + const canViewTaskPriority = computed(() => permissions.TASK_CAN_WATCH_PRIORITY) + const canEditTaskPriority = computed(() => permissions.TASK_CAN_EDIT_PRIORITY) + const canAccessTaskHistory = computed(() => permissions.TASK_CAN_ACCESS_HISTORY) + const canRecoveryTaskHistory = computed(() => permissions.TASK_CAN_RECOVERY_HISTORY) + const canAssignUsersToTask = computed(() => permissions.TASK_CAN_ASSIGN_USERS) + const canViewAssignedUsersToTask = computed(() => permissions.TASK_CAN_WATCH_ASSIGNED_USERS) + const canViewKanban = computed(() => permissions.KANBAN_CAN_VIEW || permissions.KANBAN_CAN_MANAGE) + const canManageKanban = computed(() => permissions.KANBAN_CAN_MANAGE) + const canViewGraph = computed(() => permissions.GRAPH_CAN_VIEW || permissions.GRAPH_CAN_MANAGE) + const canManageGraph = computed(() => permissions.GRAPH_CAN_MANAGE) + + return { + permissions, + canDeleteGoal, + canEditGoal, + canManageUsers, + canAddTaskList, + canViewLists, + canDeleteList, + canEditList, + canViewTasks, + canAddTask, + canDeleteTask, + canEditTaskDescription, + canEditTaskStatus, + canEditTaskNote, + canViewTaskNote, + canEditTaskDeadline, + canViewTaskDetails, + canViewTaskSubtasks, + canAddTaskSubtasks, + canEditTaskTags, + canViewTaskTags, + canViewTaskPriority, + canEditTaskPriority, + canAccessTaskHistory, + canRecoveryTaskHistory, + canAssignUsersToTask, + canViewAssignedUsersToTask, + canViewKanban, + canManageKanban, + canViewGraph, + canManageGraph, + } +} diff --git a/web/src/composables/useLogout.ts b/web/src/composables/useLogout.ts new file mode 100644 index 0000000..2ee3cfa --- /dev/null +++ b/web/src/composables/useLogout.ts @@ -0,0 +1,15 @@ +import $api from '@/helpers/axios' +import { $ls } from '@/plugins/axios' + +export async function useLogout() { + const result = await $api.post<{ logout: boolean }>('/module/auth/logout').catch((err) => { + console.log(err, $api) + }) + + if (result) { + await $ls.invalidateTokens() + return true + } + + return false +} diff --git a/web/src/composables/useProjectContext.ts b/web/src/composables/useProjectContext.ts new file mode 100644 index 0000000..195cf74 --- /dev/null +++ b/web/src/composables/useProjectContext.ts @@ -0,0 +1,83 @@ +import { ref, computed, watch, provide, inject, type InjectionKey, type Ref, type ComputedRef } from 'vue' +import type { CollaborationResponseFetchAllUsers, KanbanColumnItem } from 'taskview-api' +import type { TagItem } from '@/types/tags.types' +import { DEFAULT_ID } from '@/types/app.types' +import { $tvApi } from '@/plugins/axios' +import { useTagsStore } from '@/stores/tag.store' +import { useGoalsStore } from '@/stores/goals.store' +import { useCollaborationStore } from '@/stores/collaboration.store' +import { useKanbanStore } from '@/stores/kanban.store' + +export type ProjectContext = { + tags: ComputedRef + users: Ref + statuses: Ref +} + +const PROJECT_CONTEXT_KEY: InjectionKey = Symbol('projectContext') + +export function provideProjectContext(goalId: Ref) { + const tagsStore = useTagsStore() + const goalsStore = useGoalsStore() + + const tags = computed(() => { + const id = goalId.value + if (id <= 0) return [] + return tagsStore.tags.filter( + (tag) => tag.goalId === id, + ) + }) + + const users = ref([]) + const statuses = ref([]) + let requestId = 0 + + watch(goalId, async (id) => { + if (id <= 0) { + users.value = [] + statuses.value = [] + return + } + + if (tagsStore.tags.length === 0) { + await tagsStore.fetchAllTags() + } + + // If we're inside this project already, reuse data from global stores + if (goalsStore.selectedItemId === id) { + const collaborationStore = useCollaborationStore() + const kanbanStore = useKanbanStore() + users.value = collaborationStore.users + statuses.value = kanbanStore.statuses + return + } + + // Otherwise fetch project data without touching global stores + const currentRequestId = ++requestId + + const [usersResult, columnsResult] = await Promise.all([ + $tvApi.collaboration.fetchUsersForGoal(id).catch(() => null), + $tvApi.kanban.fetchAllColumns(id).catch(() => null), + ]) + + // Stale check — goalId could have changed while requests were in flight + if (currentRequestId !== requestId) return + + if (usersResult) users.value = usersResult + if (columnsResult) { + statuses.value = [ + { id: DEFAULT_ID, name: 'msg.allTasks', goalId: id, viewOrder: 0 }, + ...columnsResult, + ] + } + }, { immediate: true }) + + const context: ProjectContext = { tags, users, statuses } + provide(PROJECT_CONTEXT_KEY, context) + + return context +} + +export function useProjectContext(): ProjectContext | null { + return inject(PROJECT_CONTEXT_KEY, null) +} diff --git a/web/src/composables/useProjectDataLoader.ts b/web/src/composables/useProjectDataLoader.ts new file mode 100644 index 0000000..da2b60c --- /dev/null +++ b/web/src/composables/useProjectDataLoader.ts @@ -0,0 +1,21 @@ +import { watch, type Ref } from 'vue' +import { useCollaborationStore } from '@/stores/collaboration.store' +import { useKanbanStore } from '@/stores/kanban.store' +import { useTagsStore } from '@/stores/tag.store' + +export function useProjectDataLoader(projectId: Ref) { + const collaborationStore = useCollaborationStore() + const kanbanStore = useKanbanStore() + const tagsStore = useTagsStore() + + tagsStore.fetchAllTags() + + watch(projectId, (id) => { + if (id > 0) { + Promise.all([ + kanbanStore.fetchStatuses(id), + collaborationStore.fetchCollaborationUsersForGoal(id), + ]) + } + }, { immediate: true }) +} diff --git a/web/src/composables/useSelectedTaskSync.ts b/web/src/composables/useSelectedTaskSync.ts new file mode 100644 index 0000000..34b62d8 --- /dev/null +++ b/web/src/composables/useSelectedTaskSync.ts @@ -0,0 +1,33 @@ +import { watch } from 'vue' +import { useTasksStore } from '@/stores/tasks.store' +import { useKanbanStore } from '@/stores/kanban.store' +import { useGraphStore } from '@/stores/graph.store' +import { useBaseScreenStore } from '@/stores/base-screen.store' + +// If you use it in more than one component, you need to use a shared composable +// using import { createSharedComposable } from '@vueuse/core' +export function useSelectedTaskSync() { + const tasksStore = useTasksStore() + const kanbanStore = useKanbanStore() + const graphStore = useGraphStore() + const baseScreenStore = useBaseScreenStore() + + watch( + () => tasksStore.selectedTask, + (task) => { + if (!task) return + kanbanStore.syncTask(task) + graphStore.syncTask(task) + baseScreenStore.syncTask(task) + }, + { deep: true }, + ) + + function broadcastRemoval(taskId: number) { + kanbanStore.removeTask(taskId) + graphStore.removeTask(taskId) + baseScreenStore.removeTask(taskId) + } + + return { broadcastRemoval } +} diff --git a/web/src/composables/useTaskDetailPanel.ts b/web/src/composables/useTaskDetailPanel.ts new file mode 100644 index 0000000..81fbaf6 --- /dev/null +++ b/web/src/composables/useTaskDetailPanel.ts @@ -0,0 +1,94 @@ +import { ref, computed, watch } from 'vue' +import { createSharedComposable } from '@vueuse/core' +import { useRoute, useRouter } from 'vue-router' +import { useTasksStore } from '@/stores/tasks.store' +import { useBaseScreenStore } from '@/stores/base-screen.store' + +const _useTaskDetailPanel = () => { + const tasksStore = useTasksStore() + const baseScreenStore = useBaseScreenStore() + const route = useRoute() + const router = useRouter() + const activeTaskId = ref(null) + const initialEndDate = ref(null) + + const isOpen = computed({ + get: () => activeTaskId.value !== null, + set: (value: boolean) => { + if (!value) { + closeTask() + } + }, + }) + + // When browser back removes taskId from URL — close panel without navigating + watch( + () => route.params.taskId, + (taskId) => { + if (!taskId && activeTaskId.value !== null && route.name === 'user') { + syncBaseScreen() + activeTaskId.value = null + tasksStore.selectedTask = null + } + }, + ) + + async function openTask(id: number) { + activeTaskId.value = id + await tasksStore.fetchTaskById(id) + initialEndDate.value = tasksStore.selectedTask?.endDate ?? null + + // Update URL with taskId only when a project is already selected + const task = tasksStore.selectedTask + if (route.name === 'user' && route.params.projectId && task) { + const projectId = String(route.params.projectId) + const listId = String(route.params.listId) + const taskId = String(task.id) + + if (route.params.taskId !== taskId) { + router.push({ + name: 'user', + params: { projectId, listId, taskId }, + }) + } + } + } + + function syncBaseScreen() { + if (!baseScreenStore.wasCalled) return + + const task = tasksStore.selectedTask + if (!task) return + + const dateChanged = (task.endDate ?? null) !== initialEndDate.value + + if (dateChanged) { + baseScreenStore.fetchAllState() + } else { + baseScreenStore.syncTask(task) + } + } + + function closeTask() { + // On user route with taskId — go back (browser history), watcher will clear state + if (route.name === 'user' && route.params.taskId) { + syncBaseScreen() + router.back() + return + } + + // On kanban/graph or no taskId in URL — just clear state + syncBaseScreen() + activeTaskId.value = null + tasksStore.selectedTask = null + } + + return { + activeTaskId, + isOpen, + openTask, + closeTask, + } +} + +export const useTaskDetailPanel = createSharedComposable(_useTaskDetailPanel) diff --git a/web/src/composables/useTaskView.ts b/web/src/composables/useTaskView.ts new file mode 100644 index 0000000..0cad678 --- /dev/null +++ b/web/src/composables/useTaskView.ts @@ -0,0 +1,17 @@ +import { computed } from 'vue' +import { useGoalsStore } from '@/stores/goals.store' +import { breakpointsTailwind, useBreakpoints } from '@vueuse/core' + + +export const useTaskView = () => { + const goalsStore = useGoalsStore() + const bp = useBreakpoints(breakpointsTailwind) + const isMobile = bp.smaller('lg') + const isDesktop = bp.greaterOrEqual('lg') + + return { + hasActiveGoals: computed(() => goalsStore.goals.length > 0), + isMobile, + isDesktop, + } +} diff --git a/web/src/composables/useTaskViewMainUrl.ts b/web/src/composables/useTaskViewMainUrl.ts new file mode 100644 index 0000000..5f9963b --- /dev/null +++ b/web/src/composables/useTaskViewMainUrl.ts @@ -0,0 +1,9 @@ +/** + * Use this composition to get OFFICIAL SERVER URL + * We allow updates only from our servers + * @returns + */ +export const useTaskViewMainUrl = () => { + // DO NOT CHANGE THIS URL WE ALLOW UPDATES ONLY FROM THIS OUR SERVERS + return process.env.NODE_ENV !== 'production' ? 'http://localhost:1401' : 'https://apitaskview.handscream.com' +} diff --git a/web/src/composables/useUpdater.ts b/web/src/composables/useUpdater.ts new file mode 100644 index 0000000..c30b92c --- /dev/null +++ b/web/src/composables/useUpdater.ts @@ -0,0 +1,81 @@ +import { Preferences } from '@capacitor/preferences' +import { SplashScreen } from '@capacitor/splash-screen' +import { type BundleInfo, CapacitorUpdater } from '@capgo/capacitor-updater' +import $api from '@/helpers/axios' +import { $ls } from '@/plugins/axios' +import type { AppResponse } from '@/types/global-app.types' +import { useTaskViewMainUrl } from './useTaskViewMainUrl' + +let version: BundleInfo + +function isNewerVersion(server: string, current: string): boolean { + const s = server.split('.').map(Number) + const c = current.split('.').map(Number) + for (let i = 0; i < Math.max(s.length, c.length); i++) { + const sv = s[i] || 0 + const cv = c[i] || 0 + if (sv > cv) return true + if (sv < cv) return false + } + return false +} + +export async function useUpdater(canUpdate: boolean = false) { + try { + const BRANCH_MODE: 'prod' | 'dev' = (await $ls.getValue('update_loading')) === 'dev' ? 'dev' : 'prod' + + const updateInfo = await $api.get>( + `${useTaskViewMainUrl()}/module/updates/info`, + ) + + const { value: currentVersion } = await Preferences.get({ key: 'app_version' }) + + const last = updateInfo.data.response.find((i) => i.branch === BRANCH_MODE) + + console.log('branch', BRANCH_MODE) + + if (!last) { + console.log('[Update] No new updates for this branch.', BRANCH_MODE) + return + } + + console.log('currentVersion', currentVersion, 'updateInfo', last) + + if (!currentVersion || isNewerVersion(last.version, currentVersion) || (version && version?.version !== last.version)) { + console.log('[Update] Downloading new version:', last.version) + + version = await CapacitorUpdater.download({ + version: last.version, + url: `${useTaskViewMainUrl()}/module/updates/download?branch=${BRANCH_MODE}`, + }) + } else { + console.log('[Update] No new updates need to download.') + } + + if (canUpdate && version) { + try { + console.log('[Update] New version available:', last.version) + + SplashScreen.show() + + console.log('[Update] Version:', version) + + await CapacitorUpdater.next(version) + + await Preferences.set({ key: 'app_version', value: last.version }) + + await CapacitorUpdater.set(version) + + console.log('[set version]', last.version) + + console.log('[Update] Update applied. Will take effect after restart.') + } catch (err: unknown) { + console.log(err) + } finally { + SplashScreen.hide() + } + } + } catch (err) { + console.warn('[Update] Error checking for update:', err) + } +} diff --git a/web/src/helpers/AppTypes.ts b/web/src/helpers/AppTypes.ts index 8e0aac5..7e3b591 100644 --- a/web/src/helpers/AppTypes.ts +++ b/web/src/helpers/AppTypes.ts @@ -1,35 +1,3 @@ -export type RestaurantContacts = { - email: string; - phone: string; - address: string; - workStart: string; - workEnd: string; -}; - -export type RestaurantItem = { - id: number; - name: string; - owner: number; - date: string; - currency: string; - contacts: RestaurantContacts; - restaurantLogin: string; -}; -export type RestaurantItems = RestaurantItem[]; - -export type UpdateRestaurantInfoResponse = { - restaurant: RestaurantItem; - update: boolean; - updateContacts: boolean; -}; -export type AppConfigResponse = { - response: { - namespace: string; - updateBlockedInterval: number; - }; - rid: string | null; -}; - export type JWTPayload = { exp: number; id: number; @@ -53,23 +21,6 @@ export type RefreshTokenResponse = { refresh: string; }; -export interface VuetifyForm { - validate(): boolean; -} - -// export type AppResponse = { -// response: S -// rid: string -// }; - -export type FormFieldRules = ((v: string) => true | string)[]; - -export type RegistrationResult = { - registration: boolean; - confirmEmail: boolean; -}; - -export type AppCredentialsFormTabs = { title: string; component: string; recovery: boolean }[]; export type RecoveryRequestResponse = { sent: boolean }; @@ -83,4 +34,4 @@ export type VuetifyHeaderItem = { text: string; value: string; sortable?: boolea export type VuetifyHeaderItems = VuetifyHeaderItem[]; -export const IMAGE_HEIGHT = 290; +export const IMAGE_HEIGHT = 290 diff --git a/web/src/helpers/Helper.ts b/web/src/helpers/Helper.ts index 5a964d9..38dce34 100644 --- a/web/src/helpers/Helper.ts +++ b/web/src/helpers/Helper.ts @@ -1,51 +1,58 @@ export function parseJwt(token: string): R | undefined { - if (token.split('.').length === 3) { - const base64Url = token.split('.')[1]; - const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); - const jsonPayload = decodeURIComponent( - atob(base64) - .split('') - .map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`) - .join('') - ); - return JSON.parse(jsonPayload); - } + if (token.split('.').length === 3) { + const base64Url = token.split('.')[1] + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/') + const jsonPayload = decodeURIComponent( + atob(base64) + .split('') + .map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`) + .join(''), + ) + return JSON.parse(jsonPayload) + } } +/** @deprecated */ export function isEmail(email: string): boolean { - const re = - /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - return re.test(String(email).toLowerCase()); + const re = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + return re.test(String(email).toLowerCase()) } +export const isNotNullable = (val: T): val is Exclude => { + return val !== null && val !== undefined +} + +export const logError = (err: any) => console.log(err) + export function validLogin(login: string) { - const re = /^[a-z0-9]{4,30}$/; - return re.test(String(login).toLowerCase()); + const re = /^[a-z0-9]{4,30}$/ + return re.test(String(login).toLowerCase()) } export function validCompanyLogin(login: string) { - const re = /^[a-z0-9-]{4,30}$/; - return re.test(String(login).toLowerCase()) && login.lastIndexOf('-') !== login.length - 1; + const re = /^[a-z0-9-]{4,30}$/ + return re.test(String(login).toLowerCase()) && login.lastIndexOf('-') !== login.length - 1 } export function getRandomColor(): string { - const letters = '0123456789ABCDEF'; - let color = '#'; - for (let i = 0; i < 6; i++) { - color += letters[Math.floor(Math.random() * 16)]; - } - return color; + const letters = '0123456789ABCDEF' + let color = '#' + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)] + } + return color } export function isValidHttpUrl(string: string) { - let url: URL; + let url: URL - try { - url = new URL(string); - //eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (_) { - return false; - } + try { + url = new URL(string) + //eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_) { + return false + } - return url.protocol === 'http:' || url.protocol === 'https:'; + return url.protocol === 'http:' || url.protocol === 'https:' } diff --git a/web/src/helpers/LocalStorage.ts b/web/src/helpers/LocalStorage.ts index 71666e1..f93fd30 100644 --- a/web/src/helpers/LocalStorage.ts +++ b/web/src/helpers/LocalStorage.ts @@ -1,114 +1,114 @@ -import { Preferences } from '@capacitor/preferences'; -import type { AxiosInstance } from 'axios'; -import type { JWTPayload } from '@/helpers/AppTypes'; -import { parseJwt } from '@/helpers/Helper'; -import { useUserStore } from '@/stores/user.store'; +import { Preferences } from '@capacitor/preferences' +import type { AxiosInstance } from 'axios' +import type { JWTPayload } from '@/helpers/AppTypes' +import { parseJwt } from '@/helpers/Helper' +import { useUserStore } from '@/stores/user.store' -export const ACCESS_TOKEN_KEY = 'access'; -export const REFRESH_TOKEN_KEY = 'refresh'; +export const ACCESS_TOKEN_KEY = 'access' +export const REFRESH_TOKEN_KEY = 'refresh' export default class LocalStorage { - protected namespace: string = ''; + protected namespace: string = '' - protected axios!: AxiosInstance; + protected axios!: AxiosInstance - public isLoggedIn: boolean = false; + public isLoggedIn: boolean = false - protected userStore!: ReturnType; + protected userStore!: ReturnType - constructor(options: { namespace: string; axios: AxiosInstance }) { - this.namespace = options.namespace; - this.axios = options.axios; - this.getToken().then(async (token) => { - this.isLoggedIn = !!token; - this.userStore = useUserStore(); - this.userStore.loading = false; - await this.updateUserStoreByToken(); - }); + constructor(options: { namespace: string; axios: AxiosInstance }) { + this.namespace = options.namespace + this.axios = options.axios + this.getToken().then(async (token) => { + this.isLoggedIn = !!token + this.userStore = useUserStore() + this.userStore.loading = false + await this.updateUserStoreByToken() + }) + } + + private key(key: string): string { + return `${this.namespace}.${key}` + } + + async getValue(key: string): Promise { + await Preferences.configure({ group: `store_${this.namespace}` }) + return (await Preferences.get({ key: this.key(key) })).value + } + + async removeKey(key: string) { + await Preferences.configure({ group: `store_${this.namespace}` }) + await Preferences.remove({ key: this.key(key) }) + } + + //eslint-disable-next-line @typescript-eslint/no-explicit-any + async setValue(key: string, value: { [key: string]: any } | string): Promise { + await Preferences.configure({ group: `store_${this.namespace}` }) + if (typeof value !== 'string') { + value = JSON.stringify(value) } + Preferences.set({ + key: this.key(key), + value: value, + }) + } - private key(key: string): string { - return `${this.namespace}.${key}`; + setToken(token: string): void { + this.setValue(ACCESS_TOKEN_KEY, token) + this.checkTokenAndSetForAxios() + this.isLoggedIn = true + } + + setRefreshToken(refreshToken: string): void { + this.setValue(REFRESH_TOKEN_KEY, refreshToken) + } + + async getToken(): Promise { + const token = await this.getValue(ACCESS_TOKEN_KEY) + if (token && token.split('.').length === 3) { + return token } + return null + } - async getValue(key: string): Promise { - await Preferences.configure({ group: `store_${this.namespace}` }); - return (await Preferences.get({ key: this.key(key) })).value; + async getRefreshToken(): Promise { + return await this.getValue(REFRESH_TOKEN_KEY) + } + + async checkTokenAndSetForAxios(): Promise { + const token = await this.getToken() + if (token) { + this.axios.defaults.headers.common.Authorization = `Bearer ${token}` } + } - async removeKey(key: string) { - await Preferences.configure({ group: `store_${this.namespace}` }); - await Preferences.remove({ key: this.key(key) }); + async invalidateTokens() { + await this.removeKey(REFRESH_TOKEN_KEY) + await this.removeKey(ACCESS_TOKEN_KEY) + delete this.axios.defaults.headers.common.Authorization + this.isLoggedIn = false + if (this.userStore) { + this.userStore.setAccessToken('') + this.userStore.setRefreshToken('') + this.userStore.setLogin('') + this.userStore.setEmail('') } + } - //eslint-disable-next-line @typescript-eslint/no-explicit-any - async setValue(key: string, value: { [key: string]: any } | string): Promise { - await Preferences.configure({ group: `store_${this.namespace}` }); - if (typeof value !== 'string') { - value = JSON.stringify(value); - } - Preferences.set({ - key: this.key(key), - value: value, - }); - } - - setToken(token: string): void { - this.setValue(ACCESS_TOKEN_KEY, token); - this.checkTokenAndSetForAxios(); - this.isLoggedIn = true; - } - - setRefreshToken(refreshToken: string): void { - this.setValue(REFRESH_TOKEN_KEY, refreshToken); - } - - async getToken(): Promise { - const token = await this.getValue(ACCESS_TOKEN_KEY); - if (token && token.split('.').length === 3) { - return token; - } - return null; - } - - async getRefreshToken(): Promise { - return await this.getValue(REFRESH_TOKEN_KEY); - } - - async checkTokenAndSetForAxios(): Promise { - const token = await this.getToken(); - if (token) { - this.axios.defaults.headers.common.Authorization = `Bearer ${token}`; - } - } - - async invalidateTokens() { - await this.removeKey(REFRESH_TOKEN_KEY); - await this.removeKey(ACCESS_TOKEN_KEY); - delete this.axios.defaults.headers.common.Authorization; - this.isLoggedIn = false; - if (this.userStore) { - this.userStore.setAccessToken(''); - this.userStore.setRefreshToken(''); - this.userStore.setLogin(''); - this.userStore.setEmail(''); - } - } - - async updateUserStoreByToken() { - const accessToken = await this.getToken(); - if (accessToken && this.userStore) { - this.userStore.setAccessToken(accessToken); - const refreshToken = await this.getRefreshToken(); - if (refreshToken) { - this.userStore.setRefreshToken(refreshToken); - } - - const payload = parseJwt(accessToken); - if (payload) { - this.userStore.setLogin(payload.userData.login); - this.userStore.setEmail(payload.userData.email); - } - } + async updateUserStoreByToken() { + const accessToken = await this.getToken() + if (accessToken && this.userStore) { + this.userStore.setAccessToken(accessToken) + const refreshToken = await this.getRefreshToken() + if (refreshToken) { + this.userStore.setRefreshToken(refreshToken) + } + + const payload = parseJwt(accessToken) + if (payload) { + this.userStore.setLogin(payload.userData.login) + this.userStore.setEmail(payload.userData.email) + } } + } } diff --git a/web/src/helpers/axios.ts b/web/src/helpers/axios.ts index 23ea5f2..ea4821d 100644 --- a/web/src/helpers/axios.ts +++ b/web/src/helpers/axios.ts @@ -1,10 +1,10 @@ -import axios from 'axios'; -import { useTaskViewMainUrl } from '@/composition/useTaskViewMainUrl'; +import axios from 'axios' +import { useTaskViewMainUrl } from '@/composables/useTaskViewMainUrl' const $api = axios.create({ - withCredentials: true, - baseURL: useTaskViewMainUrl(), - timeout: 10000, -}); + withCredentials: true, + baseURL: useTaskViewMainUrl(), + timeout: 10000, +}) -export default $api; +export default $api diff --git a/web/src/layouts/UserLayout.vue b/web/src/layouts/UserLayout.vue new file mode 100644 index 0000000..a652e1b --- /dev/null +++ b/web/src/layouts/UserLayout.vue @@ -0,0 +1,83 @@ + + + diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts new file mode 100644 index 0000000..43ca5c6 --- /dev/null +++ b/web/src/locales/en.ts @@ -0,0 +1,405 @@ +export default { + auth: { + welcome: 'Welcome back', + signInToAccount: 'Sign in to your account', + continueWithGoogle: 'Continue with Google', + continueWithGithub: 'Continue with GitHub', + continueWithApple: 'Continue with Apple', + orContinueWith: 'Or continue with', + magicLink: 'Magic link', + password: 'Password', + termsText: 'By continuing, you agree to our', + termsOfService: 'Terms of Service', + and: 'and', + privacyPolicy: 'Privacy Policy', + + // Login by code + email: 'Email', + emailPlaceholder: 'you{\'@\'}example.com', + sendCode: 'Send code', + codeSent: 'Code sent', + checkInbox: 'Check your inbox at {email}', + enterCode: 'Enter the 6-digit code sent to', + verificationCode: 'Verification code', + verifyAndSignIn: 'Verify & Sign in', + back: 'Back', + resendCode: 'Resend code', + codeResent: 'Code resent', + newCodeSent: 'New code sent to {email}', + + // Login by password + login: 'Login', + loginPlaceholder: 'Username or email', + passwordLabel: 'Password', + passwordPlaceholder: 'Your password', + forgotPassword: 'Forgot password?', + signIn: 'Sign in', + + // Forgot password + forgotPasswordTitle: 'Reset password', + forgotPasswordDescription: 'Enter your email address and we\'ll send you a link to reset your password.', + sendResetLink: 'Send reset link', + backToLogin: 'Back to login', + checkYourEmail: 'Check your email', + resetLinkSent: 'We sent a password reset link to', + emailSent: 'Email sent', + checkInboxForReset: 'Check your inbox for password reset link', + + // Reset password + setNewPassword: 'Set new password', + setNewPasswordDescription: 'Enter a new password for your account.', + newPassword: 'New password', + newPasswordPlaceholder: 'Enter new password', + confirmPassword: 'Confirm password', + confirmPasswordPlaceholder: 'Repeat new password', + resetPassword: 'Reset password', + passwordReset: 'Password reset', + passwordResetSuccess: 'Your password has been changed successfully. You can now sign in.', + invalidResetLink: 'Invalid link', + invalidResetLinkDescription: 'The password reset link is invalid or has expired.', + canNotResetPassword: 'Could not reset password. Please try again.', + passwordTooShort: 'Password must be at least 6 characters', + passwordsDoNotMatch: 'Passwords do not match', + + // Messages + success: 'Success', + error: 'Error', + loggedIn: 'You are welcome to TaskView', + invalidCode: 'Invalid code. Please try again.', + invalidCredentials: 'Invalid login or password', + loginFailed: 'Login failed. Please try again.', + failedToSendCode: 'Failed to send code. Please try again.', + failedToResendCode: 'Failed to resend code', + failedToSendResetLink: 'Failed to send reset link. Please try again.', + + // Validation + invalidEmail: 'Invalid email address', + loginRequired: 'Login is required', + passwordRequired: 'Password is required', + codeMustBe6: 'Code must be 6 characters', + }, + projects: { + title: 'Projects', + project: 'Project', + selectProject: 'Select project', + edit: 'Edit Project', + name: 'Name', + namePlaceholder: 'Enter project name', + description: 'Description', + descriptionPlaceholder: 'Enter project description', + deleteConfirm: 'Delete Project', + deleteMessage: 'Are you sure you want to delete project "{name}"? All project history will be deleted, including task and list change history. This action cannot be undone.', + addPlaceholder: 'Enter project name', + add: 'Add project', + }, + contextMenu: { + kanban: 'Kanban', + graph: 'Graph', + collaboration: 'Collaboration', + edit: 'Edit', + moveToArchive: 'Move to Archive', + restoreFromArchive: 'Restore from Archive', + delete: 'Delete', + }, + archive: { + title: 'Archive', + }, + lists: { + title: 'Lists', + selectList: 'Select a list', + selectListToViewTasks: 'Select a list to view tasks', + addPlaceholder: 'Enter list name', + add: 'Add list', + edit: 'Edit List', + name: 'Name', + namePlaceholder: 'Enter list name', + deleteConfirm: 'Delete List', + deleteMessage: 'Are you sure you want to delete list "{name}"? The list will be deleted, including its edit history, as well as all tasks in the list and their history. This action cannot be undone.', + }, + common: { + cancel: 'Cancel', + save: 'Save', + delete: 'Delete', + done: 'Done', + yes: 'Yes', + no: 'No', + loading: 'Loading...', + close: 'Close', + add: 'Add', + }, + kanban: { + title: 'Kanban', + description: 'Kanban board view coming soon', + addColumn: 'Add column', + addTask: 'Add task', + edit: 'Edit', + delete: 'Delete', + deleteColumn: 'Delete column', + deleteColumnConfirm: 'Are you sure you want to delete this column? Tasks will be moved to Backlog.', + editColumn: 'Edit column', + columnName: 'Column name', + loadMore: 'Load more', + }, + msg: { + allTasks: 'Backlog', + todayTasks: 'Today', + upcomingTasks: 'Upcoming', + lastTasks: 'Recent Tasks', + todayExplanation: 'Tasks with deadline for today or earlier', + upcomingExplanation: 'Tasks with deadline in the near future', + lastAddedTasksExplanation: 'Recently added tasks without deadline', + lastCompletedTasksExplanation: 'Recently completed tasks', + pending: 'Active', + completed: 'Completed', + noTasks: 'No tasks', + addTask: 'Add task', + addTaskToToday: 'Add task for today', + addTaskToUpcoming: 'Add upcoming task', + addFirstProject: 'Add your first project', + today: 'Today', + lastAddedTasks: 'Added', + lastCompletedTasks: 'Completed', + }, + admin: { + search: 'Search tasks...', + }, + motivationToday: [ + 'Great day for productivity!', + 'Today is your day!', + 'Start with something small!', + 'Every task is a step forward!', + ], + motivationUpcoming: [ + 'Plan ahead!', + 'The future is in your hands!', + 'Prepare for success!', + 'Exciting things ahead!', + ], + deadline: { + startDate: 'Start date', + endDate: 'End date', + selectTime: 'Select time', + today: 'Today', + tomorrow: 'Tomorrow', + yesterday: 'Yesterday', + }, + graph: { + title: 'Graph', + description: 'Graph view coming soon', + addTask: 'Add task', + selectedEdge: 'Edge selected', + }, + collaboration: { + title: 'Collaboration', + description: 'Collaboration features coming soon', + selectProject: 'Select a project to manage collaboration', + tabs: { + members: 'Members', + roles: 'Roles', + }, + members: { + title: 'Team Members', + add: 'Add Member', + invite: 'Add Member', + email: 'Email Address', + emailPlaceholder: 'colleague{\'@\'}example.com', + sendInvite: 'Send Invite', + pending: 'Pending', + owner: 'Owner', + invitedAt: 'Invited', + noRoles: 'No roles assigned', + assignRoles: 'Assign Roles', + selectRoles: 'Select roles for this member', + resendInvite: 'Resend Invite', + remove: 'Remove Member', + empty: 'No team members yet. Add someone to collaborate!', + }, + roles: { + title: 'Roles', + create: 'Create Role', + name: 'Role Name', + namePlaceholder: 'Enter role name', + permissionsCount: '{count} permissions', + system: 'System', + assignPermissions: 'Assign Permissions', + systemRoleWarning: 'System roles cannot be modified', + deleteConfirm: 'Delete Role', + deleteMessage: 'Are you sure you want to delete "{name}"? Members with this role will need to be reassigned.', + empty: 'No custom roles yet. Create one to define specific permissions.', + }, + permissions: { + searchPlaceholder: 'Search permissions...', + groups: { + tasks: 'Tasks', + lists: 'Lists', + members: 'Members', + roles: 'Roles', + }, + view_tasks: 'View Tasks', + view_tasksDesc: 'Can view tasks in the project', + create_tasks: 'Create Tasks', + create_tasksDesc: 'Can create new tasks', + edit_tasks: 'Edit Tasks', + edit_tasksDesc: 'Can edit existing tasks', + delete_tasks: 'Delete Tasks', + delete_tasksDesc: 'Can delete tasks', + view_lists: 'View Lists', + view_listsDesc: 'Can view lists in the project', + create_lists: 'Create Lists', + create_listsDesc: 'Can create new lists', + edit_lists: 'Edit Lists', + edit_listsDesc: 'Can edit existing lists', + delete_lists: 'Delete Lists', + delete_listsDesc: 'Can delete lists', + view_members: 'View Members', + view_membersDesc: 'Can view project members', + invite_members: 'Invite Members', + invite_membersDesc: 'Can invite new members', + remove_members: 'Remove Members', + remove_membersDesc: 'Can remove members from project', + view_roles: 'View Roles', + view_rolesDesc: 'Can view roles', + manage_roles: 'Manage Roles', + manage_rolesDesc: 'Can create, edit and delete roles', + }, + }, + main: 'Main', + mainScreen: { + welcome: 'Welcome', + selectProject: 'Select a project from the sidebar to get started', + }, + tasks: { + amount: 'Amount', + enterAmount: 'Enter amount', + noTransactionType: 'Type of transaction', + income: 'Income', + expense: 'Expense', + allTasks: 'All Tasks', + addPlaceholder: 'Enter task name', + searchPlaceholder: 'Search tasks...', + add: 'Add task', + details: 'Task Details', + description: 'Description', + addDescription: 'Add a description...', + note: 'Note', + addNote: 'Add a note...', + priority: 'Priority', + priorityNone: 'No priority', + priorityLow: 'Low', + priorityMedium: 'Medium', + priorityHigh: 'High', + dueDate: 'Due Date', + deadline: 'Deadline', + addDueDate: 'Add due date', + assignees: 'Assignees', + selectAssignees: 'Select assignees', + searchUsers: 'Search users...', + noUsersAvailable: 'No users available', + list: 'List', + selectList: 'Select list', + searchLists: 'Search lists...', + noList: 'No list', + status: 'Status', + selectStatus: 'Select status', + searchStatuses: 'Search statuses...', + noStatus: 'No status', + tags: 'Tags', + addTag: 'Add tag', + subtasks: 'Subtasks', + addSubtask: 'Add subtask', + notFound: 'Task not found', + noTasks: 'No tasks', + showCompleted: 'Show completed', + hideCompleted: 'Hide completed', + sortNewestFirst: 'Newest first', + sortOldestFirst: 'Oldest first', + deleteConfirm: 'Delete Task', + deleteMessage: 'Are you sure you want to delete "{name}"? This action cannot be undone.', + }, + tags: { + createTag: 'Create tag', + editTag: 'Edit tag', + name: 'Name', + namePlaceholder: 'Tag name', + color: 'Color', + preview: 'Preview', + tagName: 'Tag name', + searchPlaceholder: 'Search tags...', + noTagsFound: 'No tags found', + noTagsAssigned: 'No tags assigned', + manageTags: 'Manage', + deleteConfirm: 'Delete Tag', + deleteMessage: 'Are you sure you want to delete "{name}"? This action cannot be undone.', + }, + priority: { + low: 'Low', + medium: 'Medium', + high: 'High', + }, + history: { + title: 'Change History', + show: 'Show history', + hide: 'Hide history', + empty: 'No change history', + changed: 'Changed', + restore: 'Restore', + details: 'Change Details', + completeTrue: 'Completed', + completeFalse: 'Not completed', + fields: { + note: 'Note', + description: 'Description', + complete: 'Completion status', + priority: 'Priority', + list: 'List', + endDate: 'Due date', + startDate: 'Start date', + }, + }, + filters: { + title: 'Filters', + assignee: 'Assignee', + selectAssignee: 'Select assignee', + allUsers: 'All users', + reset: 'Reset', + apply: 'Apply', + }, + userMenu: { + profile: 'Profile', + appearance: 'Appearance', + light: 'Light', + dark: 'Dark', + language: 'Language', + taskDetailView: 'Task detail view', + taskDetailSlideover: 'Side panel', + taskDetailModal: 'Dialog', + site: 'Site', + documentation: 'Documentation', + github: 'GitHub repository', + docker: 'Docker Images', + accountSettings: 'Account settings', + logout: 'Log out', + logoutFailed: 'Logout failed', + logoutFailedDescription: 'Try clearing browser cache to remove the authorization token', + }, + account: { + title: 'Account settings', + management: 'Account management', + deleteAccount: 'Delete account', + deleteConfirm: 'Are you sure you want to delete the account and all associated data?', + deleteWarning: 'All associated data with this account will be deleted! This action cannot be undone!', + enterCode: 'Enter code from email', + confirm: 'Confirm', + cancel: 'Cancel', + deleted: 'Account deleted', + codeSendError: 'Unable to send the code. Please contact support to delete your account.', + }, + server: { + selectServer: 'Server', + addServer: 'Add server', + addNewServer: 'Add new server', + serverUrl: 'Server URL', + serverUrlHint: 'Enter the full API server URL, e.g. https://api.example.com', + defaultServer: 'Default server', + }, +} diff --git a/web/src/locales/index.ts b/web/src/locales/index.ts new file mode 100644 index 0000000..8d5ff67 --- /dev/null +++ b/web/src/locales/index.ts @@ -0,0 +1,11 @@ +import en from './en' +import ru from './ru' + +export const messages = { + en, + ru, +} + +export type Locale = keyof typeof messages +export const locales: Locale[] = ['en', 'ru'] +export const defaultLocale: Locale = 'ru' diff --git a/web/src/locales/ru.ts b/web/src/locales/ru.ts new file mode 100644 index 0000000..eea96e3 --- /dev/null +++ b/web/src/locales/ru.ts @@ -0,0 +1,405 @@ +export default { + auth: { + welcome: 'С возвращением', + signInToAccount: 'Войдите в свой аккаунт', + continueWithGoogle: 'Продолжить с Google', + continueWithGithub: 'Продолжить с GitHub', + continueWithApple: 'Продолжить с Apple', + orContinueWith: 'Или продолжить с', + magicLink: 'Код из почты', + password: 'Пароль', + termsText: 'Продолжая, вы соглашаетесь с', + termsOfService: 'Условиями использования', + and: 'и', + privacyPolicy: 'Политикой конфиденциальности', + + // Login by code + email: 'Email', + emailPlaceholder: 'you{\'@\'}example.com', + sendCode: 'Отправить код', + codeSent: 'Код отправлен', + checkInbox: 'Проверьте почту {email}', + enterCode: 'Введите 6-значный код, отправленный на', + verificationCode: 'Код подтверждения', + verifyAndSignIn: 'Подтвердить и войти', + back: 'Назад', + resendCode: 'Отправить повторно', + codeResent: 'Код отправлен повторно', + newCodeSent: 'Новый код отправлен на {email}', + + // Login by password + login: 'Логин', + loginPlaceholder: 'Имя пользователя или email', + passwordLabel: 'Пароль', + passwordPlaceholder: 'Ваш пароль', + forgotPassword: 'Забыли пароль?', + signIn: 'Войти', + + // Forgot password + forgotPasswordTitle: 'Сброс пароля', + forgotPasswordDescription: 'Введите email и мы отправим вам ссылку для сброса пароля.', + sendResetLink: 'Отправить ссылку', + backToLogin: 'Вернуться к входу', + checkYourEmail: 'Проверьте почту', + resetLinkSent: 'Мы отправили ссылку для сброса пароля на', + emailSent: 'Письмо отправлено', + checkInboxForReset: 'Проверьте почту для сброса пароля', + + // Reset password + setNewPassword: 'Установите новый пароль', + setNewPasswordDescription: 'Введите новый пароль для вашего аккаунта.', + newPassword: 'Новый пароль', + newPasswordPlaceholder: 'Введите новый пароль', + confirmPassword: 'Подтвердите пароль', + confirmPasswordPlaceholder: 'Повторите новый пароль', + resetPassword: 'Сбросить пароль', + passwordReset: 'Пароль сброшен', + passwordResetSuccess: 'Ваш пароль успешно изменён. Теперь вы можете войти.', + invalidResetLink: 'Недействительная ссылка', + invalidResetLinkDescription: 'Ссылка для сброса пароля недействительна или истекла.', + canNotResetPassword: 'Не удалось сбросить пароль. Попробуйте снова.', + passwordTooShort: 'Пароль должен содержать минимум 6 символов', + passwordsDoNotMatch: 'Пароли не совпадают', + + // Messages + success: 'Успешно', + error: 'Ошибка', + loggedIn: 'Добро пожаловать в TaskView', + invalidCode: 'Неверный код. Попробуйте снова.', + invalidCredentials: 'Неверный логин или пароль', + loginFailed: 'Не удалось войти. Попробуйте снова.', + failedToSendCode: 'Не удалось отправить код. Попробуйте снова.', + failedToResendCode: 'Не удалось отправить код повторно', + failedToSendResetLink: 'Не удалось отправить ссылку. Попробуйте снова.', + + // Validation + invalidEmail: 'Неверный email адрес', + loginRequired: 'Введите логин', + passwordRequired: 'Введите пароль', + codeMustBe6: 'Код должен содержать 6 символов', + }, + projects: { + title: 'Проекты', + project: 'Проект', + selectProject: 'Выберите проект', + edit: 'Редактировать проект', + name: 'Название', + namePlaceholder: 'Введите название проекта', + description: 'Описание', + descriptionPlaceholder: 'Введите описание проекта', + deleteConfirm: 'Удалить проект', + deleteMessage: 'Вы уверены, что хотите удалить проект "{name}"? Будет удалена вся история проекта, включая историю изменений задач и списков. Это действие нельзя отменить.', + addPlaceholder: 'Введите название проекта', + add: 'Добавить проект', + }, + contextMenu: { + kanban: 'Канбан', + graph: 'Граф', + collaboration: 'Совместная работа', + edit: 'Редактировать', + moveToArchive: 'В архив', + restoreFromArchive: 'Восстановить из архива', + delete: 'Удалить', + }, + archive: { + title: 'Архив', + }, + lists: { + title: 'Списки', + selectList: 'Выберите список', + selectListToViewTasks: 'Выберите список для просмотра задач', + addPlaceholder: 'Введите название списка', + add: 'Добавить список', + edit: 'Редактировать список', + name: 'Название', + namePlaceholder: 'Введите название списка', + deleteConfirm: 'Удалить список', + deleteMessage: 'Вы уверены, что хотите удалить список "{name}"? Будет удалён список, включая историю его редактирования, а также все задачи в списке и их история. Это действие нельзя отменить.', + }, + common: { + cancel: 'Отмена', + save: 'Сохранить', + delete: 'Удалить', + done: 'Готово', + yes: 'Да', + no: 'Нет', + loading: 'Загрузка...', + close: 'Закрыть', + add: 'Добавить', + }, + kanban: { + title: 'Канбан', + description: 'Канбан-доска скоро будет доступна', + addColumn: 'Добавить колонку', + addTask: 'Добавить задачу', + edit: 'Редактировать', + delete: 'Удалить', + deleteColumn: 'Удалить колонку', + deleteColumnConfirm: 'Вы уверены, что хотите удалить эту колонку? Задачи будут перемещены в Backlog.', + editColumn: 'Редактировать колонку', + columnName: 'Название колонки', + loadMore: 'Загрузить ещё', + }, + msg: { + allTasks: 'Backlog', + todayTasks: 'Сегодня', + upcomingTasks: 'Предстоящие', + lastTasks: 'Последние задачи', + todayExplanation: 'Задачи с дедлайном на сегодня или ранее', + upcomingExplanation: 'Задачи с дедлайном на ближайшее будущее', + lastAddedTasksExplanation: 'Недавно добавленные задачи без дедлайна', + lastCompletedTasksExplanation: 'Недавно выполненные задачи', + pending: 'Активные', + completed: 'Выполненные', + noTasks: 'Нет задач', + addTask: 'Добавить задачу', + addTaskToToday: 'Добавить задачу на сегодня', + addTaskToUpcoming: 'Добавить предстоящую задачу', + addFirstProject: 'Добавьте свой первый проект', + today: 'Сегодня', + lastAddedTasks: 'Добавленные', + lastCompletedTasks: 'Выполненные', + }, + admin: { + search: 'Поиск задач...', + }, + motivationToday: [ + 'Отличный день для продуктивности!', + 'Сегодня всё получится!', + 'Начни с малого!', + 'Каждая задача - шаг к цели!', + ], + motivationUpcoming: [ + 'Планируй заранее!', + 'Будущее в твоих руках!', + 'Готовься к успеху!', + 'Впереди много интересного!', + ], + deadline: { + startDate: 'Дата начала', + endDate: 'Дата окончания', + selectTime: 'Выбрать время', + today: 'Сегодня', + tomorrow: 'Завтра', + yesterday: 'Вчера', + }, + graph: { + title: 'Граф', + description: 'Вид графа скоро будет доступен', + addTask: 'Добавить задачу', + selectedEdge: 'Выбрана связь', + }, + collaboration: { + title: 'Совместная работа', + description: 'Функции совместной работы скоро будут доступны', + selectProject: 'Выберите проект для управления совместной работой', + tabs: { + members: 'Участники', + roles: 'Роли', + }, + members: { + title: 'Участники команды', + add: 'Добавить', + invite: 'Добавить', + email: 'Email адрес', + emailPlaceholder: 'colleague{\'@\'}example.com', + sendInvite: 'Отправить приглашение', + pending: 'Ожидает', + owner: 'Владелец', + invitedAt: 'Приглашен', + noRoles: 'Роли не назначены', + assignRoles: 'Назначить роли', + selectRoles: 'Выберите роли для участника', + resendInvite: 'Отправить повторно', + remove: 'Удалить участника', + empty: 'Пока нет участников. Добавьте кого-нибудь для совместной работы!', + }, + roles: { + title: 'Роли', + create: 'Создать роль', + name: 'Название роли', + namePlaceholder: 'Введите название роли', + permissionsCount: '{count} разрешений', + system: 'Системная', + assignPermissions: 'Назначить разрешения', + systemRoleWarning: 'Системные роли нельзя изменять', + deleteConfirm: 'Удалить роль', + deleteMessage: 'Вы уверены, что хотите удалить "{name}"? Участникам с этой ролью нужно будет назначить другую роль.', + empty: 'Пока нет пользовательских ролей. Создайте роль для настройки разрешений.', + }, + permissions: { + searchPlaceholder: 'Поиск разрешений...', + groups: { + tasks: 'Задачи', + lists: 'Списки', + members: 'Участники', + roles: 'Роли', + }, + view_tasks: 'Просмотр задач', + view_tasksDesc: 'Может просматривать задачи в проекте', + create_tasks: 'Создание задач', + create_tasksDesc: 'Может создавать новые задачи', + edit_tasks: 'Редактирование задач', + edit_tasksDesc: 'Может редактировать существующие задачи', + delete_tasks: 'Удаление задач', + delete_tasksDesc: 'Может удалять задачи', + view_lists: 'Просмотр списков', + view_listsDesc: 'Может просматривать списки в проекте', + create_lists: 'Создание списков', + create_listsDesc: 'Может создавать новые списки', + edit_lists: 'Редактирование списков', + edit_listsDesc: 'Может редактировать существующие списки', + delete_lists: 'Удаление списков', + delete_listsDesc: 'Может удалять списки', + view_members: 'Просмотр участников', + view_membersDesc: 'Может просматривать участников проекта', + invite_members: 'Приглашение участников', + invite_membersDesc: 'Может приглашать новых участников', + remove_members: 'Удаление участников', + remove_membersDesc: 'Может удалять участников из проекта', + view_roles: 'Просмотр ролей', + view_rolesDesc: 'Может просматривать роли', + manage_roles: 'Управление ролями', + manage_rolesDesc: 'Может создавать, редактировать и удалять роли', + }, + }, + main: 'Главная', + mainScreen: { + welcome: 'Добро пожаловать', + selectProject: 'Выберите проект в боковой панели для начала работы', + }, + tasks: { + amount: 'Стоимость', + enterAmount: 'Введите стоимость', + noTransactionType: 'Тип транзакции', + income: 'Доход', + expense: 'Расход', + allTasks: 'Все задачи', + addPlaceholder: 'Введите название задачи', + searchPlaceholder: 'Поиск задач...', + add: 'Добавить задачу', + details: 'Детали задачи', + description: 'Описание', + addDescription: 'Добавить описание...', + note: 'Заметка', + addNote: 'Добавить заметку...', + priority: 'Приоритет', + priorityNone: 'Без приоритета', + priorityLow: 'Низкий', + priorityMedium: 'Средний', + priorityHigh: 'Высокий', + dueDate: 'Срок выполнения', + deadline: 'Дедлайн', + addDueDate: 'Добавить срок', + assignees: 'Исполнители', + selectAssignees: 'Выбрать исполнителей', + searchUsers: 'Поиск пользователей...', + noUsersAvailable: 'Нет доступных пользователей', + list: 'Список', + selectList: 'Выбрать список', + searchLists: 'Поиск списков...', + noList: 'Без списка', + status: 'Статус', + selectStatus: 'Выбрать статус', + searchStatuses: 'Поиск статусов...', + noStatus: 'Без статуса', + tags: 'Теги', + addTag: 'Добавить тег', + subtasks: 'Подзадачи', + addSubtask: 'Добавить подзадачу', + notFound: 'Задача не найдена', + noTasks: 'Нет задач', + showCompleted: 'Показать выполненные', + hideCompleted: 'Скрыть выполненные', + sortNewestFirst: 'Сначала новые', + sortOldestFirst: 'Сначала старые', + deleteConfirm: 'Удалить задачу', + deleteMessage: 'Вы уверены, что хотите удалить "{name}"? Это действие нельзя отменить.', + }, + tags: { + createTag: 'Создать тег', + editTag: 'Редактировать тег', + name: 'Название', + namePlaceholder: 'Название тега', + color: 'Цвет', + preview: 'Предпросмотр', + tagName: 'Название тега', + searchPlaceholder: 'Поиск тегов...', + noTagsFound: 'Теги не найдены', + noTagsAssigned: 'Нет назначенных тегов', + manageTags: 'Управление', + deleteConfirm: 'Удалить тег', + deleteMessage: 'Вы уверены, что хотите удалить "{name}"? Это действие нельзя отменить.', + }, + priority: { + low: 'Низкий', + medium: 'Средний', + high: 'Высокий', + }, + history: { + title: 'История изменений', + show: 'Показать историю', + hide: 'Скрыть историю', + empty: 'Нет истории изменений', + changed: 'Изменено', + restore: 'Восстановить', + details: 'Детали изменения', + completeTrue: 'Выполнено', + completeFalse: 'Не выполнено', + fields: { + note: 'Заметка', + description: 'Описание', + complete: 'Статус выполнения', + priority: 'Приоритет', + list: 'Список', + endDate: 'Срок выполнения', + startDate: 'Дата начала', + }, + }, + filters: { + title: 'Фильтры', + assignee: 'Исполнитель', + selectAssignee: 'Выбрать исполнителя', + allUsers: 'Все пользователи', + reset: 'Сбросить', + apply: 'Применить', + }, + userMenu: { + profile: 'Профиль', + appearance: 'Оформление', + light: 'Светлая', + dark: 'Тёмная', + language: 'Язык', + taskDetailView: 'Вид деталей задачи', + taskDetailSlideover: 'Боковая панель', + taskDetailModal: 'Диалог', + site: 'Сайт', + documentation: 'Документация', + github: 'GitHub репозиторий', + docker: 'Docker образы', + accountSettings: 'Настройки аккаунта', + logout: 'Выйти', + logoutFailed: 'Не удалось выйти', + logoutFailedDescription: 'Попробуйте очистить кэш браузера, чтобы удалить токен авторизации', + }, + account: { + title: 'Настройки аккаунта', + management: 'Управление аккаунтом', + deleteAccount: 'Удалить аккаунт', + deleteConfirm: 'Вы действительно хотите удалить аккаунт и все связанные данные?', + deleteWarning: 'Все связанные данные с данным аккаунтом будут удалены! Данное действие отменить невозможно!', + enterCode: 'Введите код из письма', + confirm: 'Подтвердить', + cancel: 'Отмена', + deleted: 'Аккаунт удалён', + codeSendError: 'Не удалось отправить код. Обратитесь в поддержку для удаления аккаунта.', + }, + server: { + selectServer: 'Сервер', + addServer: 'Добавить сервер', + addNewServer: 'Добавить новый сервер', + serverUrl: 'URL сервера', + serverUrlHint: 'Введите полный URL API сервера, например https://api.example.com', + defaultServer: 'Основной сервер', + }, +} diff --git a/web/src/main.ts b/web/src/main.ts index 3a0391d..eb89829 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -1,24 +1,82 @@ -import { createPinia } from 'pinia'; -import { createApp } from 'vue'; +import './assets/css/main.css' +import { addCollection } from '@iconify/vue' +import lucide from '@iconify-json/lucide/icons.json' +import mdi from '@iconify-json/mdi/icons.json' +import carbon from '@iconify-json/carbon/icons.json' +import { createPinia } from 'pinia' +import { createApp } from 'vue' +import { createRouter, createWebHistory } from 'vue-router' +import ui from '@nuxt/ui/vue-plugin' +import { i18n } from './plugins/i18n' -import App from './App.vue'; -import router from './router'; -import '@/helpers/at'; -import api from '@/plugins/axios'; -import i18n from '@/plugins/i18n'; -import vuetify from '@/plugins/vuetify'; +addCollection(lucide) +addCollection(mdi) +addCollection(carbon) -import './assets/base.css'; -import './assets/app.scss'; +import App from './App.vue' +import authenticated from './middleware/authenticated' +import syncSelectedProject from './middleware/syncSelectedProject' +import api from './plugins/axios' +import LoginPage from './pages/login.vue' -const app = createApp(App); +const app = createApp(App) -app.use(createPinia()); -app.use(router); -app.use(api); -app.use(i18n); -app.use(vuetify); +app.use(createPinia()) +app.use(api) +app.use(i18n) -app.mount('#app'); +const router = createRouter({ + routes: [ + { + path: '/', + name: 'login', + component: LoginPage, + }, + { + path: '/reset-password', + name: 'reset-password', + component: () => import('./pages/reset-password.vue'), + }, + { + path: '/:user', + component: () => import('./layouts/UserLayout.vue'), + beforeEnter: [authenticated], + children: [ + { + path: ':projectId/kanban', + name: 'kanban', + component: () => import('./pages/user/kanban.vue'), + }, + { + path: ':projectId/graph', + name: 'graph', + component: () => import('./pages/user/graph.vue'), + }, + { + path: ':projectId/collaboration', + name: 'collaboration', + component: () => import('./pages/user/collaboration.vue'), + }, + { + path: 'account', + name: 'account', + component: () => import('./pages/user/account.vue'), + }, + { + path: ':projectId?/:listId?/:taskId?', + name: 'user', + component: () => import('./pages/user/index.vue'), + }, + ], + }, + ], + history: createWebHistory(), +}) -export { app }; + +router.beforeEach(syncSelectedProject) + +app.use(router) +app.use(ui) + +app.mount('#app') diff --git a/web/src/middleware/authenticated.ts b/web/src/middleware/authenticated.ts index 90974dd..e7912de 100644 --- a/web/src/middleware/authenticated.ts +++ b/web/src/middleware/authenticated.ts @@ -1,10 +1,9 @@ -import { $ls } from '@/plugins/axios'; +import { $ls } from '@/plugins/axios' export default async function authenticated() { - await $ls.updateUserStoreByToken(); - if (!(await $ls.getToken())) { - console.warn('authenticated'); - return { name: 'login' }; - } - return true; + await $ls.updateUserStoreByToken() + if (!(await $ls.getToken())) { + return { name: 'login' } + } + return true } diff --git a/web/src/middleware/syncSelectedProject.ts b/web/src/middleware/syncSelectedProject.ts new file mode 100644 index 0000000..0435186 --- /dev/null +++ b/web/src/middleware/syncSelectedProject.ts @@ -0,0 +1,12 @@ +import type { NavigationGuardWithThis } from 'vue-router' +import { useGoalsStore } from '@/stores/goals.store' + +const syncSelectedProject: NavigationGuardWithThis = (to) => { + const projectId = to.params.projectId + if (projectId) { + const goalsStore = useGoalsStore() + goalsStore.selectedItemId = +projectId + } +} + +export default syncSelectedProject diff --git a/web/src/pages/login.vue b/web/src/pages/login.vue new file mode 100644 index 0000000..39715fc --- /dev/null +++ b/web/src/pages/login.vue @@ -0,0 +1,86 @@ + + + diff --git a/web/src/pages/reset-password.vue b/web/src/pages/reset-password.vue new file mode 100644 index 0000000..ff3e82d --- /dev/null +++ b/web/src/pages/reset-password.vue @@ -0,0 +1,11 @@ + + + diff --git a/web/src/pages/user/account.vue b/web/src/pages/user/account.vue new file mode 100644 index 0000000..f272394 --- /dev/null +++ b/web/src/pages/user/account.vue @@ -0,0 +1,7 @@ + + + diff --git a/web/src/pages/user/collaboration.vue b/web/src/pages/user/collaboration.vue new file mode 100644 index 0000000..123c8b8 --- /dev/null +++ b/web/src/pages/user/collaboration.vue @@ -0,0 +1,22 @@ + + + diff --git a/web/src/pages/user/graph.vue b/web/src/pages/user/graph.vue new file mode 100644 index 0000000..20477f9 --- /dev/null +++ b/web/src/pages/user/graph.vue @@ -0,0 +1,32 @@ + + + diff --git a/web/src/pages/user/index.vue b/web/src/pages/user/index.vue new file mode 100644 index 0000000..0af7bca --- /dev/null +++ b/web/src/pages/user/index.vue @@ -0,0 +1,35 @@ + + + diff --git a/web/src/pages/user/kanban.vue b/web/src/pages/user/kanban.vue new file mode 100644 index 0000000..78914da --- /dev/null +++ b/web/src/pages/user/kanban.vue @@ -0,0 +1,33 @@ + + + diff --git a/web/src/plugins/axios.ts b/web/src/plugins/axios.ts index 6bcfda8..b58ba57 100644 --- a/web/src/plugins/axios.ts +++ b/web/src/plugins/axios.ts @@ -1,108 +1,110 @@ -import qs from 'qs'; -import { TvApi } from 'taskview-api'; -import type { App } from 'vue'; -import type { RefreshTokenResponse } from '@/helpers/AppTypes'; -import $api from '@/helpers/axios'; -import LocalStorage from '@/helpers/LocalStorage'; +import qs from 'qs' +import { TvApi } from 'taskview-api' +import type { App } from 'vue' +import $api from '@/helpers/axios' +import LocalStorage from '@/helpers/LocalStorage' -let $ls: LocalStorage; -const $tvApi: TvApi = new TvApi($api); +let $ls: LocalStorage +const $tvApi: TvApi = new TvApi($api) const api = { - async install(app: App) { - console.log('Install axios ------------'); - app.config.globalProperties.$ls = new LocalStorage({ - namespace: 'task_view', - axios: $api, - }); - app.config.globalProperties.$axios = $api; + async install(app: App) { + console.log('Install axios ------------') + app.config.globalProperties.$ls = new LocalStorage({ + namespace: 'task_view', + axios: $api, + }) + app.config.globalProperties.$axios = $api - $ls = app.config.globalProperties.$ls; + $ls = app.config.globalProperties.$ls - // const apiToken = await $ls.getToken(); - // if (apiToken) { - // $tvApi = new TvApi($api); - // } + // const apiToken = await $ls.getToken(); + // if (apiToken) { + // $tvApi = new TvApi($api); + // } - console.debug('Install axios'); - await $ls.checkTokenAndSetForAxios(); + console.debug('Install axios') + await $ls.checkTokenAndSetForAxios() - // TODO move to helpers/axios.ts - const $axios = $api; - let isRefreshing = false; - let failedQueue: { resolve: (value: unknown) => void; reject: (value: unknown) => void }[] = []; + // TODO move to helpers/axios.ts + const $axios = $api + let isRefreshing = false + let failedQueue: { resolve: (value: unknown) => void; reject: (value: unknown) => void }[] = [] - //eslint-disable-next-line @typescript-eslint/no-explicit-any - const processQueue = (error: any, token: string | null = null) => { - failedQueue.forEach((prom) => { - if (error) { - prom.reject(error); - } else { - prom.resolve(token); - } - }); - failedQueue = []; - }; + //eslint-disable-next-line @typescript-eslint/no-explicit-any + const processQueue = (error: any, token: string | null = null) => { + failedQueue.forEach((prom) => { + if (error) { + prom.reject(error) + } else { + prom.resolve(token) + } + }) + failedQueue = [] + } - $axios.interceptors.response.use( - (response) => response, - async (error) => { - const originalRequest = error.config; - if (error.response.status === 401 && !originalRequest._retry) { - console.debug('We have 401'); + $axios.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config + if (error.response.status === 401 && !originalRequest._retry) { + console.debug('We have 401') - if (isRefreshing) { - console.debug('isRefreshing token '); - return new Promise((resolve, reject) => { - failedQueue.push({ resolve, reject }); - }) - .then((token) => { - console.debug('We got new token', token); - originalRequest.headers.Authorization = `Bearer ${token}`; - return $axios(originalRequest); - }) - .catch((err) => { - return Promise.reject(err); - }); - } - originalRequest._retry = true; - isRefreshing = true; - const refreshToken = await $ls.getRefreshToken(); - console.log('We get refresh token'); - if (refreshToken) { - return new Promise((resolve, reject) => { - $axios - .post( - '/module/auth/refresh/token', - qs.stringify({ refreshToken }) + if (isRefreshing) { + console.debug('isRefreshing token ') + return new Promise((resolve, reject) => { + failedQueue.push({ resolve, reject }) + }) + .then((token) => { + console.debug('We got new token', token) + originalRequest.headers.Authorization = `Bearer ${token}` + return $axios(originalRequest) + }) + .catch((err) => { + return Promise.reject(err) + }) + } + originalRequest._retry = true + isRefreshing = true + const refreshToken = await $ls.getRefreshToken() + console.log('We get refresh token') + if (refreshToken) { + return new Promise((resolve, reject) => { + $axios + .post<{ + access: string; + refresh: string; + }>( + '/module/auth/refresh/token', + qs.stringify({ refreshToken }), ) - .then((data) => { - console.log('We got refresh data tokens', data); - $ls.setToken(data.data.access); - $ls.setRefreshToken(data.data.refresh); - $ls.checkTokenAndSetForAxios(); - $ls.updateUserStoreByToken(); - originalRequest.headers.Authorization = `Bearer ${data.data.access}`; - processQueue(null, data.data.access); - resolve($axios(originalRequest)); - }) - .catch((err) => { - processQueue(err, null); - $ls.invalidateTokens(); - app.config.globalProperties.$router.push('/'); - reject(err); - }) - .finally(() => { - isRefreshing = false; - }); - }); - } - } - return Promise.reject(error); - } - ); - }, -}; + .then((data) => { + console.log('We got refresh data tokens', data) + $ls.setToken(data.data.access) + $ls.setRefreshToken(data.data.refresh) + $ls.checkTokenAndSetForAxios() + $ls.updateUserStoreByToken() + originalRequest.headers.Authorization = `Bearer ${data.data.access}` + processQueue(null, data.data.access) + resolve($axios(originalRequest)) + }) + .catch((err) => { + processQueue(err, null) + $ls.invalidateTokens() + app.config.globalProperties.$router.push('/') + reject(err) + }) + .finally(() => { + isRefreshing = false + }) + }) + } + } + return Promise.reject(error) + }, + ) + }, +} -export { $ls, $tvApi }; -export default api; +export { $ls, $tvApi } +export default api diff --git a/web/src/plugins/i18n.ts b/web/src/plugins/i18n.ts index 7c6fd8b..eda320e 100644 --- a/web/src/plugins/i18n.ts +++ b/web/src/plugins/i18n.ts @@ -1,20 +1,13 @@ -import { createI18n } from 'vue-i18n'; -import en from '@/lang/en.json'; -import ru from '@/lang/ru.json'; -import type { TI18nLocalesInApp } from '@/types/app.types'; +import { createI18n } from 'vue-i18n' +import { messages, defaultLocale } from '@/locales' -const messages: TI18nLocalesInApp = { - en: en, - ru: ru, -}; -const i18n = createI18n({ - locale: 'ru', - fallbackLocale: 'en', - missingWarn: false, // Отключает предупреждения о пропущенных ключах - fallbackWarn: false, // Отключает предупреждения о fallback - messages, - legacy: false, -}); -export default i18n; -const $i18n = i18n.global; -export { $i18n }; +export const i18n = createI18n({ + legacy: false, + locale: defaultLocale, + fallbackLocale: 'en', + messages, +}) + +export function useI18n() { + return i18n.global +} diff --git a/web/src/stores/additional-url.store.ts b/web/src/stores/additional-url.store.ts index 091a1c8..fa50cef 100644 --- a/web/src/stores/additional-url.store.ts +++ b/web/src/stores/additional-url.store.ts @@ -1,10 +1,10 @@ -import { defineStore } from 'pinia'; -import { ref } from 'vue'; +import { defineStore } from 'pinia' +import { ref } from 'vue' export const additionalUrlStore = defineStore('additionalUrlStore', () => { - const allServers = ref([]); - const mainServer = ref(''); - const systemServer = ref(''); + const allServers = ref([]) + const mainServer = ref('') + const systemServer = ref('') - return { allServers, mainServer, systemServer }; -}); + return { allServers, mainServer, systemServer } +}) diff --git a/web/src/stores/app.store.ts b/web/src/stores/app.store.ts index b61c795..44cdf55 100644 --- a/web/src/stores/app.store.ts +++ b/web/src/stores/app.store.ts @@ -1,15 +1,29 @@ -import { defineStore } from 'pinia'; -import type { AppStoreState } from '@/types/global-app.types'; +import { defineStore } from 'pinia' +import type { AppStoreState, TaskDetailDisplayMode } from '@/types/global-app.types' +import { $ls } from '@/plugins/axios' + +const TASK_DETAIL_MODE_KEY = 'taskview:taskDetailDisplayMode' export const useAppStore = defineStore('app', { - state(): AppStoreState { - return { - drawer: false, - }; + state(): AppStoreState { + return { + drawer: false, + taskDetailDisplayMode: 'modal', + } + }, + actions: { + async initTaskDetailDisplayMode() { + const stored = await $ls.getValue(TASK_DETAIL_MODE_KEY) + if (stored === 'slideover' || stored === 'modal') { + this.taskDetailDisplayMode = stored + } }, - actions: { - setDrawer(state: boolean) { - this.drawer = state; - }, + setDrawer(state: boolean) { + this.drawer = state }, -}); + setTaskDetailDisplayMode(mode: TaskDetailDisplayMode) { + this.taskDetailDisplayMode = mode + $ls.setValue(TASK_DETAIL_MODE_KEY, mode) + }, + }, +}) diff --git a/web/src/stores/base-screen.store.ts b/web/src/stores/base-screen.store.ts index 827f170..1a7d6c5 100644 --- a/web/src/stores/base-screen.store.ts +++ b/web/src/stores/base-screen.store.ts @@ -1,198 +1,220 @@ -import { useDateFormat } from '@vueuse/core'; -import { defineStore } from 'pinia'; -import { logError } from '@/helpers/app-helper'; -import $api from '@/helpers/axios'; -import { $tvApi } from '@/plugins/axios'; -import { useTagsStore } from '@/stores/tag.store'; +import { useDateFormat } from '@vueuse/core' +import { defineStore } from 'pinia' +import { logError } from '@/helpers/Helper' +import $api from '@/helpers/axios' +import { $tvApi } from '@/plugins/axios' +import { useTagsStore } from '@/stores/tag.store' import { - type BaseScreenSearchResponse, - type BaseScreenState, - FILTER_DEFAULT, - type GoalAndListsResp, - type MainScreenAllStateResponse, -} from '@/types/base-screen.types'; -import type { AppResponse } from '@/types/global-app.types'; -import type { TaskItem } from '@/types/tasks.types'; + type BaseScreenSearchResponse, + type BaseScreenState, + FILTER_DEFAULT, + type GoalAndListsResp, + type MainScreenAllStateResponse, +} from '@/types/base-screen.types' +import type { AppResponse } from '@/types/global-app.types' +import type { TaskItem } from '@/types/tasks.types' export const useBaseScreenStore = defineStore('base-screen-store', { - state(): BaseScreenState { - return { - activeWidgetInMobile: 'today', - wasCalled: false, - tasks: [], //all tasks in the app - tasksToday: [], - tasksUpcoming: [], - tasksLastCompleted: [], - //todo rename "users" to "usersByProject" - users: [], //show users by project - searchTask: '', - // addTaskDialog: false,//shoud we offer add goals - lists: [], - // listToGoal: {}, - loading: false, - //todo rename "assignees" to "assigneesByTask" - assignees: [], - filterAndSorting: { - sort: 'new', - filters: { - ...FILTER_DEFAULT, - }, - }, - taskIdToUser: {}, - }; + state(): BaseScreenState { + return { + activeWidgetInMobile: 'today', + wasCalled: false, + tasks: [], //all tasks in the app + tasksToday: [], + tasksUpcoming: [], + tasksLastCompleted: [], + //todo rename "users" to "usersByProject" + users: [], //show users by project + searchTask: '', + // addTaskDialog: false,//shoud we offer add goals + lists: [], + // listToGoal: {}, + loading: false, + //todo rename "assignees" to "assigneesByTask" + assignees: [], + filterAndSorting: { + sort: 'new', + filters: { + ...FILTER_DEFAULT, + }, + }, + taskIdToUser: {}, + } + }, + actions: { + async fetchAllState() { + this.taskIdToUser = {} + this.loading = true + const result = await $api + .get>( + `/module/about/fetchallstate?tz=${encodeURIComponent(Intl.DateTimeFormat().resolvedOptions().timeZone)}`, + ) + .catch((err) => console.log(err)) + + const tagsStore = useTagsStore() + tagsStore.fetchAllTags() + this.wasCalled = true + if (result) { + this.tasks = result.data.response.tasks + this.users = result.data.response.users + this.tasksToday = result.data.response.tasksToday + this.tasksUpcoming = result.data.response.tasksUpcoming + this.tasksLastCompleted = result.data.response.tasksLastCompleted + // this.listToGoal = result.data.response.listToGoal; + this.assignees = result.data.response.assignees + + this.assignees.forEach((assignee) => { + if (!this.taskIdToUser[assignee.taskId]) { + this.taskIdToUser[assignee.taskId] = [assignee] + } else { + this.taskIdToUser[assignee.taskId].push(assignee) + } + }) + } + setTimeout(() => { + this.loading = false + }, 100) }, - actions: { - async fetchAllState() { - this.taskIdToUser = {}; - this.loading = true; - const result = await $api - .get>( - `/module/about/fetchallstate?tz=${encodeURIComponent(Intl.DateTimeFormat().resolvedOptions().timeZone)}` - ) - .catch((err) => console.log(err)); - const tagsStore = useTagsStore(); - tagsStore.fetchAllTags(); - this.wasCalled = true; - if (result) { - this.tasks = result.data.response.tasks; - this.users = result.data.response.users; - this.tasksToday = result.data.response.tasksToday; - this.tasksUpcoming = result.data.response.tasksUpcoming; - this.tasksLastCompleted = result.data.response.tasksLastCompleted; - // this.listToGoal = result.data.response.listToGoal; - this.assignees = result.data.response.assignees; - - this.assignees.forEach((assignee) => { - if (!this.taskIdToUser[assignee.taskId]) { - this.taskIdToUser[assignee.taskId] = [assignee]; - } else { - this.taskIdToUser[assignee.taskId].push(assignee); - } - }); - } - setTimeout(() => { - this.loading = false; - }, 100); - }, - - async fetchAllAvailableLists() { - const result = await $api - .get>('/module/about/fetch/lists') - .catch((err) => console.log(err)); - if (result) { - this.lists = result.data.response; - } - }, - - async updateTaskStatusNew( - data: { status: boolean; taskId: TaskItem['id'] }, - prop: 'tasksToday' | 'tasksUpcoming' | 'tasksLastCompleted' | 'tasks' - ) { - const task = await $tvApi.tasks.updateTask({ - id: data.taskId, - complete: data.status, - }); - - if (!task) return; - - const localTask = this[prop].find((tsk) => tsk.id === data.taskId); - if (localTask) { - localTask.complete = task.complete; - - switch (prop) { - case 'tasks': - this.processLastAdded(task); - break; - case 'tasksToday': - this.processToday(task); - break; - case 'tasksUpcoming': - this.processUpcoming(task); - break; - case 'tasksLastCompleted': - this.processLastCompleted(task); - break; - } - } - }, - - processLastAdded(task: TaskItem) { - if (task.complete) { - this.tasksLastCompleted.unshift(task); - this.tasks = this.tasks.filter((t) => t.id !== task.id); - } else { - this.processLastCompleted(task); - } - }, - - processUpcoming(task: TaskItem) { - if (task.complete) { - this.tasksLastCompleted.unshift(task); - } else { - this.processLastCompleted(task); - } - }, - - processToday(task: TaskItem) { - if (task.complete) { - this.tasksLastCompleted.unshift(task); - } else { - this.processLastCompleted(task); - } - }, - - processLastCompleted(task: TaskItem) { - if (task.complete) return; - - const deadline = new Date(useDateFormat(new Date(task.endDate || ''), 'YYYY-MM-DD').value); - const today = new Date(useDateFormat(new Date(), 'YYYY-MM-DD').value); - - const removeTask = () => { - this.tasksLastCompleted = this.tasksLastCompleted.filter((t) => t.id !== task.id); - }; - - const key = !task.endDate ? 'tasks' : deadline <= today ? 'tasksToday' : 'tasksUpcoming'; - - const existingTask = this[key].find((tsk) => tsk.id === task.id); - - if (existingTask) { - existingTask.complete = task.complete; - } else { - this[key].unshift(task); - } - - removeTask(); - }, - - async updateTaskStatus(taskId: TaskItem['id']) { - const task = await $tvApi.tasks.updateTask({ - id: taskId, - complete: true, - }); - - if (!task) return; - - const index = this.tasks.findIndex((tsk) => tsk.id === taskId); - if (index !== -1) { - this.tasks.splice(index, 1); - } - }, - - async searchTaskRequest(taskDescription: string): Promise { - const result = await $api - .get>(`/module/about/search-task?description=${taskDescription}`) - .catch(logError); - if (!result) return []; - return result.data.response; - }, - - async removeTasksByGoalId(goalId: number) { - const filterFunc = (task: TaskItem) => task.goalId !== goalId; - this.tasks = this.tasks.filter(filterFunc); - this.tasksToday = this.tasksToday.filter(filterFunc); - this.tasksUpcoming = this.tasksUpcoming.filter(filterFunc); - this.tasksLastCompleted = this.tasksLastCompleted.filter(filterFunc); - }, + async fetchAllAvailableLists() { + const result = await $api + .get>('/module/about/fetch/lists') + .catch((err) => console.log(err)) + if (result) { + this.lists = result.data.response + } }, -}); + + async updateTaskStatusNew( + data: { status: boolean; taskId: TaskItem['id'] }, + prop: 'tasksToday' | 'tasksUpcoming' | 'tasksLastCompleted' | 'tasks', + ) { + const task = await $tvApi.tasks.updateTask({ + id: data.taskId, + complete: data.status, + }) + + if (!task) return + + const localTask = this[prop].find((tsk) => tsk.id === data.taskId) + if (localTask) { + localTask.complete = task.complete + + switch (prop) { + case 'tasks': + this.processLastAdded(task) + break + case 'tasksToday': + this.processToday(task) + break + case 'tasksUpcoming': + this.processUpcoming(task) + break + case 'tasksLastCompleted': + this.processLastCompleted(task) + break + } + } + }, + + processLastAdded(task: TaskItem) { + if (task.complete) { + this.tasksLastCompleted.unshift(task) + this.tasks = this.tasks.filter((t) => t.id !== task.id) + } else { + this.processLastCompleted(task) + } + }, + + processUpcoming(task: TaskItem) { + if (task.complete) { + this.tasksLastCompleted.unshift(task) + } else { + this.processLastCompleted(task) + } + }, + + processToday(task: TaskItem) { + if (task.complete) { + this.tasksLastCompleted.unshift(task) + } else { + this.processLastCompleted(task) + } + }, + + processLastCompleted(task: TaskItem) { + if (task.complete) return + + const deadline = new Date(useDateFormat(new Date(task.endDate || ''), 'YYYY-MM-DD').value) + const today = new Date(useDateFormat(new Date(), 'YYYY-MM-DD').value) + + const removeTask = () => { + this.tasksLastCompleted = this.tasksLastCompleted.filter((t) => t.id !== task.id) + } + + const key = !task.endDate ? 'tasks' : deadline <= today ? 'tasksToday' : 'tasksUpcoming' + + const existingTask = this[key].find((tsk) => tsk.id === task.id) + + if (existingTask) { + existingTask.complete = task.complete + } else { + this[key].unshift(task) + } + + removeTask() + }, + + async updateTaskStatus(taskId: TaskItem['id']) { + const task = await $tvApi.tasks.updateTask({ + id: taskId, + complete: true, + }) + + if (!task) return + + const index = this.tasks.findIndex((tsk) => tsk.id === taskId) + if (index !== -1) { + this.tasks.splice(index, 1) + } + }, + + async searchTaskRequest(taskDescription: string): Promise { + const result = await $api + .get>(`/module/about/search-task?description=${taskDescription}`) + .catch(logError) + if (!result) return [] + return result.data.response + }, + + syncTask(task: TaskItem) { + if (!this.wasCalled) return + + const lists = ['tasks', 'tasksToday', 'tasksUpcoming', 'tasksLastCompleted'] as const + for (const list of lists) { + const index = this[list].findIndex((t) => t.id === task.id) + if (index !== -1) { + Object.assign(this[list][index], task) + } + } + }, + + removeTask(taskId: number) { + const lists = ['tasks', 'tasksToday', 'tasksUpcoming', 'tasksLastCompleted'] as const + for (const list of lists) { + const index = this[list].findIndex((t) => t.id === taskId) + if (index !== -1) { + this[list].splice(index, 1) + } + } + }, + + async removeTasksByGoalId(goalId: number) { + const filterFunc = (task: TaskItem) => task.goalId !== goalId + this.tasks = this.tasks.filter(filterFunc) + this.tasksToday = this.tasksToday.filter(filterFunc) + this.tasksUpcoming = this.tasksUpcoming.filter(filterFunc) + this.tasksLastCompleted = this.tasksLastCompleted.filter(filterFunc) + }, + }, +}) diff --git a/web/src/stores/collaboration-permissions.store.ts b/web/src/stores/collaboration-permissions.store.ts index 8dc8142..8fc7ddc 100644 --- a/web/src/stores/collaboration-permissions.store.ts +++ b/web/src/stores/collaboration-permissions.store.ts @@ -1,36 +1,36 @@ -import { defineStore } from 'pinia'; -import { $tvApi } from '@/plugins/axios'; -import type { CollaborationPermissionsState, PermissioinGroupsIds } from '@/types/collaboration-permissions.types'; +import { defineStore } from 'pinia' +import { $tvApi } from '@/plugins/axios' +import type { CollaborationPermissionsState, PermissioinGroupsIds } from '@/types/collaboration-permissions.types' export const useCollaborationPermissionsStore = defineStore('collaboration-permissions', { - state(): CollaborationPermissionsState { - return { - permissions: [], - }; - }, - getters: { - groupedPermissions(state): Record { - const map = new Map(); - state.permissions.forEach((perm) => { - const arr = map.get(perm.permission_group); - if (!arr) { - map.set(perm.permission_group, [perm]); - } else { - arr.push(perm); - } - }); + state(): CollaborationPermissionsState { + return { + permissions: [], + } + }, + getters: { + groupedPermissions(state): Record { + const map = new Map() + state.permissions.forEach((perm) => { + const arr = map.get(perm.permission_group) + if (!arr) { + map.set(perm.permission_group, [perm]) + } else { + arr.push(perm) + } + }) - return Object.fromEntries(map) as Record< + return Object.fromEntries(map) as Record< PermissioinGroupsIds, CollaborationPermissionsState['permissions'] - >; - }, + > }, - actions: { - async fetchAllPermissions(): Promise { - const result = await $tvApi.collaboration.fetchAllPermissions().catch((err) => console.log(err)); - if (!result) return; - this.permissions = result; - }, + }, + actions: { + async fetchAllPermissions(): Promise { + const result = await $tvApi.collaboration.fetchAllPermissions().catch((err) => console.log(err)) + if (!result) return + this.permissions = result }, -}); + }, +}) diff --git a/web/src/stores/collaboration-roles.store.ts b/web/src/stores/collaboration-roles.store.ts index 4698d18..a9aa334 100644 --- a/web/src/stores/collaboration-roles.store.ts +++ b/web/src/stores/collaboration-roles.store.ts @@ -1,75 +1,75 @@ -import { defineStore } from 'pinia'; +import { defineStore } from 'pinia' import type { - CollaborationArgCreateRoleForGoal, - CollaborationArgDeleteRoleFromGoal, - CollaborationArgToggleRolePermission, - GoalItem, -} from 'taskview-api'; -import { $tvApi } from '@/plugins/axios'; -import type { CollaborationRolesStore } from '@/types/collaboration-roles.types'; + CollaborationArgCreateRoleForGoal, + CollaborationArgDeleteRoleFromGoal, + CollaborationArgToggleRolePermission, + GoalItem, +} from 'taskview-api' +import { $tvApi } from '@/plugins/axios' +import type { CollaborationRolesStore } from '@/types/collaboration-roles.types' export const useCollaborationRolesStore = defineStore('collaborationRoles', { - state(): CollaborationRolesStore { - return { - roles: [], - rolesPermissions: {}, - }; + state(): CollaborationRolesStore { + return { + roles: [], + rolesPermissions: {}, + } + }, + + actions: { + async fetchCollaborationRolesForGoal(goalId: GoalItem['id']): Promise { + const roles = await $tvApi.collaboration.fetchRolesForGoal(goalId) + if (!roles) return + this.roles = roles }, - actions: { - async fetchCollaborationRolesForGoal(goalId: GoalItem['id']): Promise { - const roles = await $tvApi.collaboration.fetchRolesForGoal(goalId); - if (!roles) return; - this.roles = roles; - }, + async addCollaborationRole(data: CollaborationArgCreateRoleForGoal): Promise { + const role = await $tvApi.collaboration.createRoleForGoal(data) + if (!role) return - async addCollaborationRole(data: CollaborationArgCreateRoleForGoal): Promise { - const role = await $tvApi.collaboration.createRoleForGoal(data); - if (!role) return; + this.roles.push(role) - this.roles.push(role); - - if (!this.rolesPermissions[role.id]) { - this.rolesPermissions[role.id] = {}; - } - }, - - async deleteCollaborationRole(data: CollaborationArgDeleteRoleFromGoal): Promise { - const result = await $tvApi.collaboration.deleteRoleFromGoal(data); - if (!result) return; - - const index = this.roles.findIndex((role) => role.id === data.id); - if (index !== -1) { - this.roles.splice(index, 1); - } - }, - - async fetchAllRolePermissionsForGoal(goalId: GoalItem['id']): Promise { - const result = await $tvApi.collaboration.fetchRoleToPermissionsForGoal(goalId); - if (!result) return; - - result.forEach((item) => { - if (!item.role_id || !item.permission_id) return; - - if (!this.rolesPermissions[item.role_id]) { - this.rolesPermissions[item.role_id] = {}; - } - this.rolesPermissions[item.role_id][item.permission_id] = true; - }); - }, - - async togglePermissionForRole(data: CollaborationArgToggleRolePermission) { - const result = await $tvApi.collaboration.toggleRolePermission(data); - if (!result) return; - - if (result.add) { - if (!this.rolesPermissions[data.roleId]) { - this.rolesPermissions[data.roleId] = {}; - } - this.rolesPermissions[data.roleId][data.permissionId] = true; - } else { - delete this.rolesPermissions[data.roleId][data.permissionId]; - } - }, + if (!this.rolesPermissions[role.id]) { + this.rolesPermissions[role.id] = {} + } }, -}); + + async deleteCollaborationRole(data: CollaborationArgDeleteRoleFromGoal): Promise { + const result = await $tvApi.collaboration.deleteRoleFromGoal(data) + if (!result) return + + const index = this.roles.findIndex((role) => role.id === data.id) + if (index !== -1) { + this.roles.splice(index, 1) + } + }, + + async fetchAllRolePermissionsForGoal(goalId: GoalItem['id']): Promise { + const result = await $tvApi.collaboration.fetchRoleToPermissionsForGoal(goalId) + if (!result) return + + result.forEach((item) => { + if (!item.role_id || !item.permission_id) return + + if (!this.rolesPermissions[item.role_id]) { + this.rolesPermissions[item.role_id] = {} + } + this.rolesPermissions[item.role_id][item.permission_id] = true + }) + }, + + async togglePermissionForRole(data: CollaborationArgToggleRolePermission) { + const result = await $tvApi.collaboration.toggleRolePermission(data) + if (!result) return + + if (result.add) { + if (!this.rolesPermissions[data.roleId]) { + this.rolesPermissions[data.roleId] = {} + } + this.rolesPermissions[data.roleId][data.permissionId] = true + } else { + delete this.rolesPermissions[data.roleId][data.permissionId] + } + }, + }, +}) diff --git a/web/src/stores/collaboration.store.ts b/web/src/stores/collaboration.store.ts index e14359d..f6c53bc 100644 --- a/web/src/stores/collaboration.store.ts +++ b/web/src/stores/collaboration.store.ts @@ -1,70 +1,70 @@ -import { defineStore } from 'pinia'; +import { defineStore } from 'pinia' import type { - CollaborationArgAddUser, - CollaborationArgDeleteUser, - CollaborationArgToggleUserRoles, - GoalItem, -} from 'taskview-api'; -import { $tvApi } from '@/plugins/axios'; -import type { CollaborationStore } from '@/types/collaboration.types'; + CollaborationArgAddUser, + CollaborationArgDeleteUser, + CollaborationArgToggleUserRoles, + GoalItem, +} from 'taskview-api' +import { $tvApi } from '@/plugins/axios' +import type { CollaborationStore } from '@/types/collaboration.types' export const useCollaborationStore = defineStore('collaboration', { - state(): CollaborationStore { - return { - users: [], - }; - }, + state(): CollaborationStore { + return { + users: [], + } + }, - getters: { - userMap: (state) => { - return new Map(state.users.map((user) => [user.id, user])); - }, + getters: { + userMap: (state) => { + return new Map(state.users.map((user) => [user.id, user])) }, + }, - actions: { - /** + actions: { + /** * Fetch all users for collaboration * we use it in the main screen to show the users * in the assignees section for tasks */ - async fetchAllCollaborationUsers(): Promise { - const users = await $tvApi.collaboration.fetchAllUsers(); - if (!users) return; - this.users = users; - }, + async fetchAllCollaborationUsers(): Promise { + const users = await $tvApi.collaboration.fetchAllUsers() + if (!users) return + this.users = users + }, - /** + /** * Fetch users for a specific goal for collaboration section */ - async fetchCollaborationUsersForGoal(goalId: GoalItem['id']): Promise { - const users = await $tvApi.collaboration.fetchUsersForGoal(goalId); - if (!users) return; - this.users = users; - }, - - async addCollaborationUser(data: CollaborationArgAddUser): Promise { - const result = await $tvApi.collaboration.inviteUserToGoal(data); - if (!result) return false; - this.users.push(result); - return true; - }, - - async deleteUserFromCollaboration(data: CollaborationArgDeleteUser): Promise { - const result = await $tvApi.collaboration.deleteUserFromGoal(data); - if (!result) return; - const index = this.users.findIndex((usr) => usr.id === data.id); - if (index !== -1) { - this.users.splice(index, 1); - } - }, - - async toggleUserRole(data: CollaborationArgToggleUserRoles) { - const roles = await $tvApi.collaboration.toggleUserRoles(data); - if (!roles) return; - const user = this.users.find((us) => us.id === data.userId); - if (user && roles) { - user.roles = roles; - } - }, + async fetchCollaborationUsersForGoal(goalId: GoalItem['id']): Promise { + const users = await $tvApi.collaboration.fetchUsersForGoal(goalId) + if (!users) return + this.users = users }, -}); + + async addCollaborationUser(data: CollaborationArgAddUser): Promise { + const result = await $tvApi.collaboration.inviteUserToGoal(data) + if (!result) return false + this.users.push(result) + return true + }, + + async deleteUserFromCollaboration(data: CollaborationArgDeleteUser): Promise { + const result = await $tvApi.collaboration.deleteUserFromGoal(data) + if (!result) return + const index = this.users.findIndex((usr) => usr.id === data.id) + if (index !== -1) { + this.users.splice(index, 1) + } + }, + + async toggleUserRole(data: CollaborationArgToggleUserRoles) { + const roles = await $tvApi.collaboration.toggleUserRoles(data) + if (!roles) return + const user = this.users.find((us) => us.id === data.userId) + if (user && roles) { + user.roles = roles + } + }, + }, +}) diff --git a/web/src/stores/goal-lists.store.ts b/web/src/stores/goal-lists.store.ts index 48128c8..85673b0 100644 --- a/web/src/stores/goal-lists.store.ts +++ b/web/src/stores/goal-lists.store.ts @@ -1,73 +1,69 @@ -import { defineStore } from 'pinia'; +import { defineStore } from 'pinia' import type { - GoalListItemArgAdd, - GoalListItemArgDelete, - GoalListItemArgFetch, - GoalListItemArgUpdate, -} from 'taskview-api'; -import { $tvApi } from '@/plugins/axios'; -import type { GoalListsStoreState } from '@/types/goal-lists.types'; -import { useGoalsStore } from './goals.store'; + GoalListItem, + GoalListItemArgAdd, + GoalListItemArgDelete, + GoalListItemArgFetch, + GoalListItemArgUpdate, +} from 'taskview-api' +import { $tvApi } from '@/plugins/axios' +import type { GoalListsStoreState } from '@/types/goal-lists.types' export const useGoalListsStore = defineStore('goal-lists', { - state: (): GoalListsStoreState => ({ - currentGoalId: -1, - lists: [], - loading: false, - }), - getters: { - listMap: (state) => { - return new Map(state.lists.map((list) => [list.id, list])); - }, + state: (): GoalListsStoreState => ({ + lists: [], + loading: false, + }), + getters: { + listMap: (state) => { + return new Map(state.lists.map((list) => [list.id, list])) }, - actions: { - async addList(listItem: GoalListItemArgAdd) { - this.loading = true; - const list = await $tvApi.goalLists.createList(listItem).catch(console.error); - this.loading = false; - if (list) { - this.lists.unshift(list); - return true; - } - return false; - }, - - async fetchLists(goalId: GoalListItemArgFetch['goalId']) { - this.loading = true; - const goalsStore = useGoalsStore(); - goalsStore.selectedItemId = goalId; - this.currentGoalId = goalId; - this.lists = []; - const lists = await $tvApi.goalLists.fetchLists({ goalId }).catch(console.error); - this.loading = false; - this.lists = lists ?? []; - }, - - async deleteList(listId: GoalListItemArgDelete) { - this.loading = true; - const result = await $tvApi.goalLists.deleteList(listId).catch(console.error); - this.loading = false; - if (!result) return false; - const index = this.lists.findIndex((item) => +item.id === +listId); - if (index !== -1) { - this.lists.splice(index, 1); - } - }, - - async updateList(listData: GoalListItemArgUpdate) { - this.loading = true; - const list = await $tvApi.goalLists.updateList(listData).catch(console.error); - this.loading = false; - - if (!list) return false; - - const index = this.lists.findIndex((item) => +item.id === +listData.id); - if (index !== -1) { - this.lists[index] = list; - return true; - } - - return false; - }, + }, + actions: { + async addList(listItem: GoalListItemArgAdd): Promise { + this.loading = true + const list = await $tvApi.goalLists.createList(listItem).catch(console.error) + this.loading = false + if (list) { + this.lists.unshift(list) + return list + } + return null }, -}); + + async fetchLists(goalId: GoalListItemArgFetch['goalId']) { + this.loading = true + this.lists = [] + const lists = await $tvApi.goalLists.fetchLists({ goalId }).catch(console.error) + this.loading = false + this.lists = lists ?? [] + }, + + async deleteList(listId: GoalListItemArgDelete) { + this.loading = true + const result = await $tvApi.goalLists.deleteList(listId).catch(console.error) + this.loading = false + if (!result) return false + const index = this.lists.findIndex((item) => +item.id === +listId) + if (index !== -1) { + this.lists.splice(index, 1) + } + }, + + async updateList(listData: GoalListItemArgUpdate) { + this.loading = true + const list = await $tvApi.goalLists.updateList(listData).catch(console.error) + this.loading = false + + if (!list) return false + + const index = this.lists.findIndex((item) => +item.id === +listData.id) + if (index !== -1) { + this.lists[index] = list + return true + } + + return false + }, + }, +}) diff --git a/web/src/stores/goals.store.ts b/web/src/stores/goals.store.ts index 39f1b96..07d0edf 100644 --- a/web/src/stores/goals.store.ts +++ b/web/src/stores/goals.store.ts @@ -1,69 +1,73 @@ -import { defineStore } from 'pinia'; -import type { GoalArgItemAdd, GoalArgItemUpdate, GoalItem } from 'taskview-api'; -import { $tvApi } from '@/plugins/axios'; -import type { GoalsStoreState } from '@/types/goals.types'; -import { useUserStore } from './user.store'; +import { defineStore } from 'pinia' +import type { GoalArgItemAdd, GoalArgItemUpdate, GoalItem } from 'taskview-api' +import { $tvApi } from '@/plugins/axios' +import type { GoalsStoreState } from '@/types/goals.types' +import { useUserStore } from './user.store' export const useGoalsStore = defineStore('goals', { - state: (): GoalsStoreState => ({ - loading: false, - selectedItemId: -1, - goals: [], - }), - getters: { - goalMap: (state) => { - return new Map(state.goals.map((goal) => [goal.id, goal])); - }, - - selectedGoal: (state) => state.goals.find((goal) => goal.id === state.selectedItemId), - - amIOwner(): boolean { - const userStore = useUserStore(); - return this.selectedGoal?.owner === userStore.payloadData?.id; - }, - - goalIdToName(): Record { - const map: Record = {}; - this.goals.forEach((g) => { - map[g.id] = g.name; - }); - return map; - }, + state: (): GoalsStoreState => ({ + loading: false, + selectedItemId: -1, + goals: [], + }), + getters: { + goalMap: (state) => { + return new Map(state.goals.map((goal) => [goal.id, goal])) }, - actions: { - async fetchGoals(): Promise { - this.loading = true; - const result = await $tvApi.goals.fetchGoals(); - if (!result) return; - this.goals = result || []; - this.loading = false; - }, - async addGoal(goal: GoalArgItemAdd): Promise { - const goalWithPermissions = await $tvApi.goals.createGoal(goal); - if (!goalWithPermissions) return false; - this.goals.unshift(goalWithPermissions); - return true; - }, + selectedGoal: (state) => state.goals.find((goal) => goal.id === state.selectedItemId), - async updateGoal(goal: GoalArgItemUpdate): Promise { - const goalWithPermissions = await $tvApi.goals.updateGoal(goal); - if (!goalWithPermissions) return false; - const index = this.goals.findIndex((g) => +g.id === +goal.id); - if (index !== -1) { - this.goals[index] = goalWithPermissions; - } - return true; - }, - - async deleteGoal(goalId: GoalItem['id']): Promise { - const result = await $tvApi.goals.deleteGoal(goalId); - if (!result) return; - const index = this.goals.findIndex((g) => +g.id === +goalId); - if (index !== -1) { - this.goals.splice(index, 1); - } - return; - }, + amIOwner(): boolean { + const userStore = useUserStore() + return this.selectedGoal?.owner === userStore.payloadData?.id }, -}); + + goalIdToName(): Record { + const map: Record = {} + this.goals.forEach((g) => { + map[g.id] = g.name + }) + return map + }, + }, + actions: { + async fetchGoals(): Promise { + if (this.loading) return + this.loading = true + const result = await $tvApi.goals.fetchGoals() + if (!result) { + this.loading = false + return + } + this.goals = result || [] + this.loading = false + }, + + async addGoal(goal: GoalArgItemAdd): Promise { + const goalWithPermissions = await $tvApi.goals.createGoal(goal) + if (!goalWithPermissions) return null + this.goals.unshift(goalWithPermissions) + return goalWithPermissions + }, + + async updateGoal(goal: GoalArgItemUpdate): Promise { + const goalWithPermissions = await $tvApi.goals.updateGoal(goal) + if (!goalWithPermissions) return false + const index = this.goals.findIndex((g) => +g.id === +goal.id) + if (index !== -1) { + this.goals[index] = goalWithPermissions + } + return true + }, + + async deleteGoal(goalId: GoalItem['id']): Promise { + const result = await $tvApi.goals.deleteGoal(goalId) + if (!result) return + const index = this.goals.findIndex((g) => +g.id === +goalId) + if (index !== -1) { + this.goals.splice(index, 1) + } + return + }, + }, +}) diff --git a/web/src/stores/graph.store.ts b/web/src/stores/graph.store.ts index 4dca2a8..c9b5a80 100644 --- a/web/src/stores/graph.store.ts +++ b/web/src/stores/graph.store.ts @@ -1,96 +1,113 @@ -import { defineStore } from 'pinia'; -import type { GoalItem, GraphArgAddEdge, TaskArgUpdate } from 'taskview-api'; -import { logError } from '@/helpers/app-helper'; -import { $tvApi } from '@/plugins/axios'; -import type { GraphStore } from '@/types/graph.types'; -import type { TaskItem } from '@/types/tasks.types'; -import { useGoalListsStore } from './goal-lists.store'; -import { useTasksStore } from './tasks.store'; +import { defineStore } from 'pinia' +import type { GoalItem, GraphArgAddEdge, TaskArgUpdate } from 'taskview-api' +import { $tvApi } from '@/plugins/axios' +import type { GraphStore } from '@/types/graph.types' +import type { TaskItem } from '@/types/tasks.types' +import { useGoalListsStore } from './goal-lists.store' +import { useTasksStore } from './tasks.store' +import { logError } from '@/helpers/Helper' export const useGraphStore = defineStore('use-graph-store', { - state: (): GraphStore => { - return { - nodes: [], - edges: [], - }; + state: (): GraphStore => { + return { + nodes: [], + edges: [], + } + }, + actions: { + getStores() { + return { + tasksStore: useTasksStore(), + goalListsStore: useGoalListsStore(), + } }, - actions: { - getStores() { - return { - tasksStore: useTasksStore(), - goalListsStore: useGoalListsStore(), - }; - }, - async fetchAllTasksAndLists(goalId: GoalItem['id']): Promise { - const { tasksStore, goalListsStore } = this.getStores(); + async fetchAllTasksAndLists(goalId: GoalItem['id']): Promise { + const { tasksStore, goalListsStore } = this.getStores() - goalListsStore.currentGoalId = goalId; - await Promise.all([ - goalListsStore.fetchLists(goalListsStore.currentGoalId), - tasksStore.fetchAllTasks(goalListsStore.currentGoalId), - ]); + await Promise.all([ + goalListsStore.fetchLists(goalId), + tasksStore.fetchAllTasks(goalId, 1, 1, true), + ]) - this.nodes = tasksStore.tasks.map((task) => ({ - id: task.id.toString(), - data: { task, label: `${task.description} == ${task.id}` }, - position: task.nodeGraphPosition ?? { x: 0, y: 0 }, - type: 'task', - style: { resize: 'none' }, - })); - await this.fetchAllEdges(goalId); - }, - - async addNode(task: TaskItem) { - this.nodes.push({ - id: task.id.toString(), - data: { task, label: `${task.description} == ${task.id}` }, - position: task.nodeGraphPosition ?? { x: 0, y: 0 }, - type: 'task', - style: { resize: 'none' }, - }); - return task.id.toString(); - }, - - async addEdge(data: GraphArgAddEdge) { - const result = await $tvApi.graph.addEdge(data).catch((err: unknown) => logError(err)); - if (!result) return; - - const edge = result; - - const newEdge = { - id: edge.id.toString(), - source: edge.fromTaskId.toString(), - target: edge.toTaskId.toString(), - }; - - this.edges.push(newEdge); - - return newEdge; - }, - - async fetchAllEdges(goalId: number) { - const result = await $tvApi.graph.fetchAllEdges(goalId).catch((err: unknown) => logError(err)); - if (!result) return; - this.edges = result.map((edge) => ({ - id: edge.id.toString(), - source: edge.fromTaskId.toString(), - target: edge.toTaskId.toString(), - })); - }, - - async deleteEdge(id: number) { - const result = await $tvApi.graph.deleteEdge(id).catch((err: unknown) => logError(err)); - if (!result) return; - const index = this.edges.findIndex((edge) => edge.id === id.toString()); - if (index !== -1) { - this.edges.splice(index, 1); - } - }, - - async updateTaskPosition(data: TaskArgUpdate) { - const result = await $tvApi.tasks.updateTask(data).catch((err: unknown) => logError(err)); - if (!result) return; - }, + this.nodes = tasksStore.tasks.map((task) => ({ + id: task.id.toString(), + data: { task, label: `${task.description} == ${task.id}` }, + position: task.nodeGraphPosition ?? { x: 0, y: 0 }, + type: 'task', + style: { resize: 'none' }, + })) + await this.fetchAllEdges(goalId) }, -}); + + async addNode(task: TaskItem) { + this.nodes.push({ + id: task.id.toString(), + data: { task, label: `${task.description} == ${task.id}` }, + position: task.nodeGraphPosition ?? { x: 0, y: 0 }, + type: 'task', + style: { resize: 'none' }, + }) + return task.id.toString() + }, + + async addEdge(data: GraphArgAddEdge) { + const result = await $tvApi.graph.addEdge(data).catch((err: unknown) => logError(err)) + if (!result) return + + const edge = result + + const newEdge = { + id: edge.id.toString(), + source: edge.fromTaskId.toString(), + target: edge.toTaskId.toString(), + } + + this.edges.push(newEdge) + + return newEdge + }, + + async fetchAllEdges(goalId: number) { + const result = await $tvApi.graph.fetchAllEdges(goalId).catch((err: unknown) => logError(err)) + if (!result) return + this.edges = result.map((edge) => ({ + id: edge.id.toString(), + source: edge.fromTaskId.toString(), + target: edge.toTaskId.toString(), + })) + }, + + removeTask(taskId: number) { + const nodeId = taskId.toString() + const nodeIndex = this.nodes.findIndex((n) => n.id === nodeId) + if (nodeIndex !== -1) { + this.nodes.splice(nodeIndex, 1) + } + this.edges = this.edges.filter((e) => e.source !== nodeId && e.target !== nodeId) + }, + + async deleteEdge(id: number) { + const result = await $tvApi.graph.deleteEdge(id).catch((err: unknown) => logError(err)) + if (!result) return + const index = this.edges.findIndex((edge) => edge.id === id.toString()) + if (index !== -1) { + this.edges.splice(index, 1) + } + }, + + syncTask(task: { id: number; description?: string }) { + const node = this.nodes.find((n) => n.id === task.id.toString()) + if (!node) return + Object.assign(node.data.task, task) + if (task.description !== undefined) { + node.data.label = `${task.description} == ${task.id}` + } + }, + + async updateTaskPosition(data: TaskArgUpdate) { + const result = await $tvApi.tasks.updateTask(data).catch((err: unknown) => logError(err)) + if (!result) return + }, + }, +}) diff --git a/web/src/stores/kanban.store.ts b/web/src/stores/kanban.store.ts index 3663ec7..1895424 100644 --- a/web/src/stores/kanban.store.ts +++ b/web/src/stores/kanban.store.ts @@ -1,155 +1,180 @@ -import { defineStore } from 'pinia'; -import type { GoalItem, KanbanArgDeleteColumn, KanbanArgUpdateColumn, KanbanArgUpdateTasksOrder, KanbanColumnItem, Task, TaskArgAdd } from 'taskview-api'; -import { DEFAULT_ID } from '@/types/app.types'; -import type { KanbanStoreState } from '@/types/kanban.types'; -import { useTasksStore } from './tasks.store'; -import { $tvApi } from '@/plugins/axios'; -import type { KanbanArgAddColumn } from 'taskview-api'; - -const tasksStore = useTasksStore(); +import { defineStore } from 'pinia' +import type { GoalItem, KanbanArgDeleteColumn, KanbanArgUpdateColumn, KanbanArgUpdateTasksOrder, KanbanColumnItem, TaskArgAdd, KanbanArgAddColumn } from 'taskview-api' +import { DEFAULT_ID } from '@/types/app.types' +import type { KanbanStoreState } from '@/types/kanban.types' +import { $tvApi } from '@/plugins/axios' +import { TaskItem } from '@/types/tasks.types' export const useKanbanStore = defineStore('kanban', { - state(): KanbanStoreState { - return { - tasksData: {}, - statuses: [], - goalId: DEFAULT_ID, - }; + state(): KanbanStoreState { + return { + tasksData: {}, + statuses: [], + goalId: DEFAULT_ID, + } + }, + actions: { + async fetchTasksForColumn(goalId: GoalItem['id'], columnId: KanbanColumnItem['id'] | null, cursor: string | number | null) { + const result = await $tvApi.kanban.fetchTasksForColumn(goalId, columnId, cursor).catch(console.error) + + if (!result) { + if (!this.tasksData[columnId ?? DEFAULT_ID]) { + this.tasksData[columnId ?? DEFAULT_ID] = { + tasks: [], + nextCursor: null, + columnVersion: null, + } + } + return + } + + if (!this.tasksData[columnId ?? DEFAULT_ID]) { + this.tasksData[columnId ?? DEFAULT_ID] = { + tasks: [], + nextCursor: null, + columnVersion: null, + } + } + + // if (cursor === null) { + // this.tasksData[columnId ?? DEFAULT_ID]!.tasks = result.tasks + // } else { + this.tasksData[columnId ?? DEFAULT_ID]!.tasks.push(...result.tasks) + // } + this.tasksData[columnId ?? DEFAULT_ID]!.nextCursor = result.nextCursor + this.tasksData[columnId ?? DEFAULT_ID]!.columnVersion = result.columnVersion }, - actions: { - async fetchTasksForColumn(goalId: GoalItem['id'], columnId: KanbanColumnItem['id'] | null, cursor: string | number | null) { - const result = await $tvApi.kanban.fetchTasksForColumn(goalId, columnId, cursor).catch(console.error); - if (!result) { - if (!this.tasksData[columnId ?? DEFAULT_ID]) { - this.tasksData[columnId ?? DEFAULT_ID] = { - tasks: [], - nextCursor: null, - columnVersion: null, - }; - } - return; - } + // async fetchTasksOrderForColumnAndCursor(goalId: GoalItem['id'], columnId: Task['statusId'], cursor: string | number | null) { + // const result = await $tvApi.kanban.getTasksOrderForColumnAndCursor(goalId, columnId, cursor).catch(console.error); + // if (!result) { + // return; + // } - if (!this.tasksData[columnId ?? DEFAULT_ID]) { - this.tasksData[columnId ?? DEFAULT_ID] = { - tasks: [], - nextCursor: null, - columnVersion: null, - }; - } + // result.forEach((task) => { + // const localTask = this.tasksData[columnId ?? DEFAULT_ID]?.tasks.find((t) => t.id === task.id); + // if (!localTask) return; + // localTask.kanbanOrder = task.kanbanOrder; + // }); + // }, - this.tasksData[columnId ?? DEFAULT_ID]!.tasks.push(...result.tasks); - this.tasksData[columnId ?? DEFAULT_ID]!.nextCursor = result.nextCursor; - this.tasksData[columnId ?? DEFAULT_ID]!.columnVersion = result.columnVersion; - }, + async updateTasksOrderAndColumn(data: KanbanArgUpdateTasksOrder) { + const result = await $tvApi.kanban.updateTasksOrderAndColumn(data).catch(console.error) - // async fetchTasksOrderForColumnAndCursor(goalId: GoalItem['id'], columnId: Task['statusId'], cursor: string | number | null) { - // const result = await $tvApi.kanban.getTasksOrderForColumnAndCursor(goalId, columnId, cursor).catch(console.error); - // if (!result) { - // return; - // } + if (!result) { + return + } - // result.forEach((task) => { - // const localTask = this.tasksData[columnId ?? DEFAULT_ID]?.tasks.find((t) => t.id === task.id); - // if (!localTask) return; - // localTask.kanbanOrder = task.kanbanOrder; - // }); - // }, + result.tasks.forEach((task) => { + const localTask = this.tasksData[data.columnId ?? DEFAULT_ID]?.tasks.find((t) => t.id === task.id) + if (!localTask) return + localTask.kanbanOrder = task.kanbanOrder + }) - async updateTasksOrderAndColumn(data: KanbanArgUpdateTasksOrder) { - const result = await $tvApi.kanban.updateTasksOrderAndColumn(data).catch(console.error); + // if the column version is different, fetch the tasks again + if (this.tasksData[data.columnId ?? DEFAULT_ID]!.columnVersion !== result.columnVersion) { + this.tasksData[data.columnId ?? DEFAULT_ID]!.tasks = [] + this.tasksData[data.columnId ?? DEFAULT_ID]!.nextCursor = null + this.tasksData[data.columnId ?? DEFAULT_ID]!.columnVersion = null + await this.fetchTasksForColumn(this.goalId, data.columnId ?? null, null) + } - if (!result) { - return; - } - - result.tasks.forEach((task) => { - const localTask = this.tasksData[data.columnId ?? DEFAULT_ID]?.tasks.find((t) => t.id === task.id); - if (!localTask) return; - localTask.kanbanOrder = task.kanbanOrder; - }); - - // if the column version is different, fetch the tasks again - if (this.tasksData[data.columnId ?? DEFAULT_ID]!.columnVersion !== result.columnVersion) { - this.tasksData[data.columnId ?? DEFAULT_ID]!.tasks = []; - this.tasksData[data.columnId ?? DEFAULT_ID]!.nextCursor = null; - this.tasksData[data.columnId ?? DEFAULT_ID]!.columnVersion = null; - await this.fetchTasksForColumn(this.goalId, data.columnId ?? null, null); - } - - this.tasksData[data.columnId ?? DEFAULT_ID]!.columnVersion = result.columnVersion; - }, - - async addTask(taskArg: TaskArgAdd) { - const result = await $tvApi.tasks.createTask(taskArg).catch(console.error); - - if (!result) { - return; - } - - this.tasksData[taskArg.statusId ?? DEFAULT_ID]?.tasks.unshift(result); - }, - - async fetchStatuses(goalId: GoalItem['id']) { - const result = await $tvApi.kanban.fetchAllColumns(goalId); - if (!result) { - return; - } - this.statuses = result; - this.statuses.unshift({ - id: DEFAULT_ID, - name: 'msg.allTasks', - goalId: this.goalId, - viewOrder: 0, - }); - }, - - async addStatus(data: KanbanArgAddColumn) { - const result = await $tvApi.kanban.addColumn(data); - if (!result) { - return; - } - this.statuses.push(result); - - this.tasksData[result.id] = { - tasks: [], - nextCursor: null, - columnVersion: null, - }; - }, - - async updateStatus(data: KanbanArgUpdateColumn) { - const result = await $tvApi.kanban.updateColumn(data); - if (!result) { - return; - } - - const index = this.statuses.findIndex((stat) => stat.id === data.id); - - if (index !== -1) { - this.statuses[index] = { ...this.statuses[index], ...data }; - } - }, - - async deleteStatus(data: KanbanArgDeleteColumn) { - const result = await $tvApi.kanban.deleteColumn(data); - if (!result) { - return; - } - const index = this.statuses.findIndex((stat) => stat.id === data.id); - - if (index !== -1) { - this.statuses.splice(index, 1); - } - - this.tasksData[data.id]?.tasks.forEach((t) => { - if (t.statusId === data.id) { - t.statusId = null; - } - }); - - delete this.tasksData[data.id]; - }, + this.tasksData[data.columnId ?? DEFAULT_ID]!.columnVersion = result.columnVersion }, -}); + + async addTask(taskArg: TaskArgAdd) { + const result = await $tvApi.tasks.createTask(taskArg).catch(console.error) + + if (!result) { + return + } + + this.tasksData[taskArg.statusId ?? DEFAULT_ID]?.tasks.unshift(result) + }, + + async fetchStatuses(goalId: GoalItem['id']) { + const result = await $tvApi.kanban.fetchAllColumns(goalId) + if (!result) { + return + } + this.statuses = result + this.statuses.unshift({ + id: DEFAULT_ID, + name: 'msg.allTasks', + goalId: this.goalId, + viewOrder: 0, + }) + }, + + async addStatus(data: KanbanArgAddColumn) { + const result = await $tvApi.kanban.addColumn(data) + if (!result) { + return + } + this.statuses.push(result) + + this.tasksData[result.id] = { + tasks: [], + nextCursor: null, + columnVersion: null, + } + }, + + async updateStatus(data: KanbanArgUpdateColumn) { + const result = await $tvApi.kanban.updateColumn(data) + if (!result) { + return + } + + const index = this.statuses.findIndex((stat) => stat.id === data.id) + + if (index !== -1) { + this.statuses[index] = { ...this.statuses[index], ...data } + } + }, + + syncTask(task: TaskItem) { + for (const columnId of Object.keys(this.tasksData)) { + const column = this.tasksData[Number(columnId)] + if (!column) continue + const localTask = column.tasks.find((t) => t.id === task.id) + if (localTask) { + Object.assign(localTask, task) + return + } + } + }, + + removeTask(taskId: number) { + for (const columnId of Object.keys(this.tasksData)) { + const column = this.tasksData[Number(columnId)] + if (!column) continue + const index = column.tasks.findIndex((t: { id: number }) => t.id === taskId) + if (index !== -1) { + column.tasks.splice(index, 1) + return + } + } + }, + + async deleteStatus(data: KanbanArgDeleteColumn) { + const result = await $tvApi.kanban.deleteColumn(data) + if (!result) { + return + } + const index = this.statuses.findIndex((stat) => stat.id === data.id) + + if (index !== -1) { + this.statuses.splice(index, 1) + } + + this.tasksData[data.id]?.tasks.forEach((t) => { + if (t.statusId === data.id) { + t.statusId = null + } + }) + + delete this.tasksData[data.id] + }, + }, +}) diff --git a/web/src/stores/tag.store.ts b/web/src/stores/tag.store.ts index 9cf1748..ecf40b6 100644 --- a/web/src/stores/tag.store.ts +++ b/web/src/stores/tag.store.ts @@ -1,109 +1,106 @@ -import { defineStore } from 'pinia'; -import type { TagItemArgAdd, TagItemArgToggle, TagItemArgUpdate, TagItemResponseToggle } from 'taskview-api'; -import { $tvApi } from '@/plugins/axios'; -import type { TagItem, TagsState } from '@/types/tags.types'; -import { useGoalListsStore } from './goal-lists.store'; -import { useGoalsStore } from './goals.store'; +import { defineStore } from 'pinia' +import type { TagItemArgAdd, TagItemArgToggle, TagItemArgUpdate, TagItemResponseToggle } from 'taskview-api' +import { $tvApi } from '@/plugins/axios' +import type { TagItem, TagsState } from '@/types/tags.types' +import { useGoalsStore } from './goals.store' export const useTagsStore = defineStore('tags', { - state(): TagsState { - return { - tags: [], - tagsMap: new Map(), - loading: false, - }; + state(): TagsState { + return { + tags: [], + tagsMap: new Map(), + loading: false, + } + }, + + getters: { + filteredTags(state) { + const goalsStore = useGoalsStore() + const goalId = goalsStore.selectedGoal?.id + return state.tags.filter( + (tag) => tag.goalId === goalId, + ) + }, + }, + actions: { + async fetchAllTags(): Promise { + this.loading = true + + const tags = await $tvApi.tags + .fetchAllTagsForUser() + .catch((err) => console.log(err)) + .finally(() => { + this.loading = false + }) + + if (!tags) { + return + } + this.tags = tags + + this.tags.forEach((tag) => { + this.tagsMap.set(tag.id, tag) + }) }, - getters: { - filteredTags(state) { - const goalListStore = useGoalListsStore(); - const goalsStore = useGoalsStore(); - return state.tags.filter( - (tag) => - (tag.goalId === null || tag.goalId === goalListStore.currentGoalId) && - tag.owner === goalsStore.selectedGoal?.owner - ); - }, + async addTag(tagData: TagItemArgAdd): Promise { + this.loading = true + const result = await $tvApi.tags + .createTag(tagData) + .catch((err) => console.log(err)) + .finally(() => { + this.loading = false + }) + if (!result) return false + this.tags.push(result) + this.tagsMap.set(result.id, result) + return true }, - actions: { - async fetchAllTags(): Promise { - this.loading = true; - const tags = await $tvApi.tags - .fetchAllTagsForUser() - .catch((err) => console.log(err)) - .finally(() => { - this.loading = false; - }); + async deleteTag(tagId: TagItem['id']): Promise { + this.loading = true - if (!tags) { - return; - } - this.tags = tags; + const deleteResult = await $tvApi.tags + .deleteTag({ tagId }) + .catch((err) => console.log(err)) + .finally(() => { + this.loading = false + }) - this.tags.forEach((tag) => { - this.tagsMap.set(tag.id, tag); - }); - }, + if (!deleteResult) return - async addTag(tagData: TagItemArgAdd): Promise { - this.loading = true; - const result = await $tvApi.tags - .createTag(tagData) - .catch((err) => console.log(err)) - .finally(() => { - this.loading = false; - }); - if (!result) return false; - this.tags.push(result); - this.tagsMap.set(result.id, result); - return true; - }, + const index = this.tags.findIndex((tag) => +tag.id === +tagId) + if (index === -1) return - async deleteTag(tagId: TagItem['id']): Promise { - this.loading = true; - - const deleteResult = await $tvApi.tags - .deleteTag({ tagId }) - .catch((err) => console.log(err)) - .finally(() => { - this.loading = false; - }); - - if (!deleteResult) return; - - const index = this.tags.findIndex((tag) => +tag.id === +tagId); - if (index === -1) return; - - this.tagsMap.delete(this.tags[index].id); - this.tags.splice(index, 1); - }, - - async toggleTag(data: TagItemArgToggle): Promise { - const result = await $tvApi.tags - .toggleTag(data) - .catch(console.error) - .finally(() => { - this.loading = false; - }); - - if (!result) return undefined; - - return result.action; - }, - - async updateTag(newTag: TagItemArgUpdate) { - this.loading = true; - const updateResult = await $tvApi.tags - .updateTag(newTag) - .catch((err) => console.log(err)) - .finally(() => { - this.loading = false; - }); - if (!updateResult) return; - const tag = this.tags.find((tag) => tag.id === newTag.id); - if (!tag) return; - Object.assign(tag, newTag); - }, + this.tagsMap.delete(this.tags[index].id) + this.tags.splice(index, 1) }, -}); + + async toggleTag(data: TagItemArgToggle): Promise { + const result = await $tvApi.tags + .toggleTag(data) + .catch(console.error) + .finally(() => { + this.loading = false + }) + + if (!result) return undefined + + return result.action + }, + + async updateTag(newTag: TagItemArgUpdate) { + this.loading = true + const updateResult = await $tvApi.tags + .updateTag(newTag) + .catch((err) => console.log(err)) + .finally(() => { + this.loading = false + }) + if (!updateResult) return + const tag = this.tags.find((tag) => tag.id === newTag.id) + if (!tag) return + Object.assign(tag, newTag) + }, + }, +}) diff --git a/web/src/stores/task-history.store.ts b/web/src/stores/task-history.store.ts index 3993281..4cb264e 100644 --- a/web/src/stores/task-history.store.ts +++ b/web/src/stores/task-history.store.ts @@ -1,26 +1,26 @@ -import { defineStore } from 'pinia'; -import { $tvApi } from '@/plugins/axios'; -import type { TaskHistoryState } from '@/types/task-history.types'; -import type { TaskItem } from '@/types/tasks.types'; +import { defineStore } from 'pinia' +import { $tvApi } from '@/plugins/axios' +import type { TaskHistoryState } from '@/types/task-history.types' +import type { TaskItem } from '@/types/tasks.types' export const useTaskHistory = defineStore('taskHistory', { - state(): TaskHistoryState { - return { - history: [], - }; + state(): TaskHistoryState { + return { + history: [], + } + }, + + actions: { + async fetchHistoryForTask(taskId: TaskItem['id']): Promise { + const result = await $tvApi.tasks.fetchTaskHistory(taskId) + if (!result) return + this.history = result.history }, - actions: { - async fetchHistoryForTask(taskId: TaskItem['id']): Promise { - const result = await $tvApi.tasks.fetchTaskHistory(taskId); - if (!result) return; - this.history = result.history; - }, - - async recoverState(historyId: number, taskId: TaskItem['id']): Promise { - const result = await $tvApi.tasks.recoveryTaskHistory(historyId, taskId); - if (!result) return false; - return result.recovery; - }, + async recoverState(historyId: number, taskId: TaskItem['id']): Promise { + const result = await $tvApi.tasks.recoveryTaskHistory(historyId, taskId) + if (!result) return false + return result.recovery }, -}); + }, +}) diff --git a/web/src/stores/tasks.store.ts b/web/src/stores/tasks.store.ts index 86f462a..1d7a585 100644 --- a/web/src/stores/tasks.store.ts +++ b/web/src/stores/tasks.store.ts @@ -1,521 +1,544 @@ -import { defineStore } from 'pinia'; -import type { - TagItemArgToggle, - Task, - TaskArgAdd, - TaskArgDelete, - TaskArgToggleAssignee, - TaskArgUpdate, - TaskBase, -} from 'taskview-api'; -import { delay, isNotNullable, logError } from '@/helpers/app-helper'; -import { $tvApi } from '@/plugins/axios'; -import { DEFAULT_ID } from '@/types/app.types'; -import { ALL_TASKS_LIST_ID, type TasksStoreState } from '@/types/tasks.types'; -import { useTagsStore } from './tag.store'; +import { defineStore } from 'pinia' +import { + ALL_TASKS_LIST_ID, + type TagItemArgToggle, + type Task, + type TaskArgAdd, + type TaskArgDelete, + type TaskArgToggleAssignee, + type TaskArgUpdate, + type TaskBase, +} from 'taskview-api' +import { $tvApi } from '@/plugins/axios' +import { DEFAULT_ID } from '@/types/app.types' +import { type TasksStoreState } from '@/types/tasks.types' +import { useTagsStore } from './tag.store' +import { isNotNullable, logError } from '@/helpers/Helper' export const useTasksStore = defineStore('tasks', { - state(): TasksStoreState { - return { - tasks: [], - selectedTask: null, - fetchRules: { - showCompleted: 0, - currentListId: DEFAULT_ID, - endOfTasks: false, - currentPage: 0, - searchText: '', - firstNew: 1, - filters: {}, - goalId: -1, - }, - loading: false, - }; + state(): TasksStoreState { + return { + tasks: [], + selectedTask: null, + fetchRules: { + showCompleted: 0, + currentListId: DEFAULT_ID, + endOfTasks: false, + currentPage: 0, + searchText: '', + firstNew: 1, + filters: {}, + goalId: -1, + }, + loading: false, + } + }, + actions: { + updateFetchRules(data: Partial) { + this.fetchRules = { ...this.fetchRules, ...data } }, - actions: { - updateFetchRules(data: Partial) { - this.fetchRules = { ...this.fetchRules, ...data }; - }, - resetTasks() { - this.tasks = []; - }, - - updateSelectedTask(task: Task) { - if (this.selectedTask && this.selectedTask.id === task.id) { - Object.assign(this.selectedTask, task); - } - }, - - async fetchTaskById(taskId: TaskBase['id']) { - if (taskId === this.selectedTask?.id) return; - - const task = await $tvApi.tasks.fetchTaskById(taskId).catch(logError); - - if (!task) return; - - this.selectedTask = task; - }, - - /** @deprecated */ - setTaskOrder(data: Partial) { - const task = this.tasks.find((t) => data.id === t.id); - if (!task) return; - - if (isNotNullable(data.taskOrder)) { - task.taskOrder = data.taskOrder; - } - if (isNotNullable(data.kanbanOrder)) { - task.kanbanOrder = data.kanbanOrder; - } - }, - - // setTaskStatusId(data: TaskUpdateStatusIdArg) { - - // if (data.taskId === this.selectedTask?.id) { - // this.selectedTask.statusId = data.statusId; - // } - - // const task = this.tasks.find((tsk) => tsk.id === data.taskId); - - // if (!task) return; - - // task.statusId = data.statusId; - // this.updateSelectedTask(task); - // }, - - /** @deprecated */ - async updateOrders(data: TaskArgUpdate): Promise { - const task = await $tvApi.tasks.updateTask(data).catch(logError); - if (!task) return null; - - this.setTaskOrder(task); - return task; - }, - - async addTask(taskArg: TaskArgAdd): Promise { - if (taskArg.goalListId === ALL_TASKS_LIST_ID) { - taskArg.goalListId = null; - } - - const task = await $tvApi.tasks.createTask(taskArg).catch(logError); - if (!task) return false; - - if (task.parentId) { - let mainTask = this.tasks.find((t) => t.id === task.parentId); - - if (!mainTask && this.selectedTask?.id === task.parentId) { - mainTask = this.selectedTask - } - - if (!mainTask) { - throw new Error('Main task not found for adding subtask'); - } - - if (mainTask) { - mainTask.subtasks.push(task); - } - - if (this.selectedTask?.id === task.parentId) { - this.selectedTask.subtasks.push(task); - } - } else { - this.tasks.unshift(task); - } - return task; - }, - - appendTasks(items: Task[]) { - this.tasks = [...this.tasks, ...items]; - }, - - async fetchAllTasks(goalId: number, showCompleted: 0 | 1 = 0, firstNew: 0 | 1 = 1) { - this.loading = true; - const searchParams = { - showCompleted: +showCompleted as 0 | 1, - goalId: +goalId, - firstNew: +firstNew as 0 | 1, - componentId: ALL_TASKS_LIST_ID, - page: +this.fetchRules.currentPage, - searchText: this.fetchRules.searchText, - filters: this.fetchRules.filters, - unlimited: true, - }; - - const tasks = await $tvApi.tasks - .fetch(searchParams) - .catch(logError) - .finally(() => { - this.loading = false; - }); - if (!tasks) return false; - this.tasks = tasks; - - return false; - }, - - async fetchTasks(): Promise { - this.loading = true; - - const searchParams = { - showCompleted: +this.fetchRules.showCompleted as 0 | 1, - goalId: +this.fetchRules.goalId, - firstNew: +this.fetchRules.firstNew as 0 | 1, - componentId: +this.fetchRules.currentListId, - page: +this.fetchRules.currentPage, - searchText: this.fetchRules.searchText, - filters: this.fetchRules.filters, - }; - - const tasks = await $tvApi.tasks - .fetch(searchParams) - .catch(logError) - .finally(() => { - this.loading = false; - }); - if (!tasks) return false; - - this.fetchRules.endOfTasks = tasks.length < 1; - if (!this.fetchRules.endOfTasks) { - this.fetchRules.currentPage++; - } - this.appendTasks(tasks); - return true; - }, - - /**@todo rename to updateTaskComplete */ - async updateTaskCompleteStatus( - data: TaskArgUpdate, - deleteCompleted: boolean = true - ): Promise { - this.loading = true; - - const taskResult = await $tvApi.tasks - .updateTask(data) - .catch(logError) - .finally(() => { - this.loading = false; - }); - - if (!taskResult) return undefined; - - // this.setTaskStatusId({ taskId: taskResult.id, statusId: taskResult.statusId }); - - const searchId = taskResult.parentId || taskResult.id; - - let parentTask: Task | undefined; - - parentTask = this.tasks.find((tsk: Task) => tsk.id === searchId); - - if (!parentTask && searchId === this.selectedTask?.id) { - parentTask = this.selectedTask; - } - - if (!parentTask) { - return undefined; - } - - //we updating subtasks - if (taskResult.parentId) { - const subTask = parentTask.subtasks.find((sub) => sub.id === +taskResult.id); - if (subTask) { - subTask.complete = taskResult.complete; - } - } else { - //we updating main task - parentTask.complete = taskResult.complete; - //we deleting task from list if deleteCompleted is true - // and task complete is different from showCompleted rule for fetching - if (deleteCompleted && !!this.fetchRules.showCompleted !== data.complete) { - //handle condition with showing tasks in diff mode (showCompleted) - await delay(500); - const taskIndex = this.tasks.findIndex((task) => task.id === taskResult.id); - if (taskIndex !== -1) { - this.tasks.splice(taskIndex, 1); - } - } - } - - this.updateSelectedTask(parentTask); - - return taskResult.complete; - }, - - async updateTaskDescription(data: TaskArgUpdate): Promise { - this.loading = true; - const task = await $tvApi.tasks - .updateTask(data) - .catch(logError) - .finally(() => { - this.loading = false; - }); - if (!task) return false; - - const localTask = this.tasks.find(({ id }) => id === task.id); - if (localTask) { - Object.assign(localTask, task); - } - this.updateSelectedTask(task); - return true; - }, - - async deleteTask(id: TaskArgDelete): Promise { - this.loading = true; - - const result = await $tvApi.tasks - .deleteTask(id) - .catch(logError) - .finally(() => { - this.loading = false; - }); - if (!result) return false; - - const taskIndex = this.tasks.findIndex((task) => task.id === id); - - if (taskIndex !== -1) { - this.tasks.splice(taskIndex, 1); - } - - if (this.selectedTask?.id === id) { - this.selectedTask = null; - } - - return true; - }, - - async updateTaskNote(data: TaskArgUpdate): Promise { - this.loading = true; - - const task = await $tvApi.tasks - .updateTask(data) - .catch(logError) - .finally(() => { - this.loading = false; - }); - - if (!task) return false; - - const localTask = this.tasks.find(({ id }) => id === task.id); - if (localTask) { - Object.assign(localTask, task); - } - this.updateSelectedTask(task); - - return true; - }, - - //we do not fetch subtasks from server separately, we fetch all tasks with subtasks - // async fetchSubtasks(taskId: TaskItem['id']): Promise { - // this.loading = true; - // const subtasks = await $tvApi.tasks.fetch(taskId).catch(logError).finally(() => this.loading = false); - // // const result = await $api - // // .get>(`${this.urls.fetchSubtasks}?taskId=${taskId}`) - // // .catch((err) => console.log(err)); - // // this.loading = false; - // // if (result) { - // // if (result.data.response.length > 0) { - // // const task = this.tasks.find(({ id }) => id === +taskId); - // // if (task) { - // // task.subtasks = result.data.response; - // // this.updateSelectedTask(task); - // // } else { - // // if (this.selectedTask && +this.selectedTask.id === +taskId) { - // // this.selectedTask.subtasks = result.data.response; - // // } - // // } - // // } - // // } - // }, - - async toggleTagForTask(data: TagItemArgToggle) { - const tagsStore = useTagsStore(); - const action = await tagsStore.toggleTag(data); - - if (!action) { - return; - } - - const task = this.tasks.find((tsk) => +tsk.id === +data.taskId); - - const toggleTag = (task: Task) => { - if (action === 'delete') { - const tagIndex = task.tags.indexOf(data.tagId); - if (tagIndex !== -1) { - task.tags.splice(tagIndex, 1); - } - } else { - task.tags.push(data.tagId); - } - }; - - if (task) { - toggleTag(task); - this.updateSelectedTask(task); - } else if (this.selectedTask?.id === data.taskId) { - toggleTag(this.selectedTask); - } - }, - - async udpatePriority(data: TaskArgUpdate): Promise { - this.loading = true; - - const task = await $tvApi.tasks - .updateTask(data) - .catch(logError) - .finally(() => { - this.loading = false; - }); - - if (!task) return; - - const localTask = this.tasks.find(({ id }) => id === task.id); - - if (localTask) { - Object.assign(localTask, task); - } - - this.updateSelectedTask(task); - }, - - //TODO fixme consider refactoring to make one method for all updates - async moveTaskToAnotherList(data: TaskArgUpdate) { - this.loading = true; - - const task = await $tvApi.tasks - .updateTask(data) - .catch(logError) - .finally(() => { - this.loading = false; - }); - - if (!task) return; - - const localTask = this.tasks.find(({ id }) => id === task.id); - - if (localTask) { - Object.assign(localTask, task); - } - - this.updateSelectedTask(task); - }, - - async saveDateForTask(data: TaskArgUpdate) { - this.loading = true; - - const task = await $tvApi.tasks - .updateTask(data) - .catch(logError) - .finally(() => { - this.loading = false; - }); - - if (!task) return; - - const localTask = this.tasks.find(({ id }) => id === task.id); - - if (localTask) { - Object.assign(localTask, task); - } - - this.updateSelectedTask(task); - }, - - async updateTaskAssignee(data: TaskArgToggleAssignee) { - this.loading = true; - - const updateAssignee = await $tvApi.tasks - .toggleTasksAssignee(data) - .catch(logError) - .finally(() => { - this.loading = false; - }); - - if (!updateAssignee) return; - - const task = this.tasks.find((tsk) => tsk.id === data.taskId); - - if (!task) return; - - task.assignedUsers = updateAssignee.userIds; - this.updateSelectedTask(task); - }, - - // we do not fetch assigned users from server separately, we fetch all tasks with assigned users - // async fetchTaskAssignedUsers(data: Pick) { - // this.loading = true; - // const result = await $api - // .post>(this.urls.taskFetchAssignedUsers, data) - // .catch((err) => console.log(err)); - // this.loading = false; - - // if (result) { - // const task = this.tasks.find((tsk) => tsk.id === data.taskId); - // if (task) { - // task.assignedUsers = result.data.response.usersIds; - // this.updateSelectedTask(task); - // } else if (this.selectedTask?.id === data.taskId) { - // this.selectedTask.assignedUsers = result.data.response.usersIds; - // } - // } - // }, - - async updateTaskStatusId(data: TaskArgUpdate) { - const task = await $tvApi.tasks - .updateTask(data) - .catch(logError) - .finally(() => { - this.loading = false; - }); - - if (!task) return; - - const localTask = this.tasks.find(({ id }) => id === task.id); - - if (localTask) { - Object.assign(localTask, task); - } - - this.updateSelectedTask(task); - }, - - async updateTaskAmount(data: TaskArgUpdate) { - this.loading = true; - const task = await $tvApi.tasks - .updateTask(data) - .catch(logError) - .finally(() => { - this.loading = false; - }); - - if (!task) return; - - const localTask = this.tasks.find(({ id }) => id === task.id); - - if (localTask) { - Object.assign(localTask, task); - } - - this.updateSelectedTask(task); - }, - - async updateTaskTransactionType(data: TaskArgUpdate) { - this.loading = true; - const task = await $tvApi.tasks - .updateTask(data) - .catch(logError) - .finally(() => { - this.loading = false; - }); - - if (!task) return; - - const localTask = this.tasks.find(({ id }) => id === task.id); - - if (localTask) { - Object.assign(localTask, task); - } - - this.updateSelectedTask(task); - }, + resetTasks() { + this.tasks = [] }, -}); + + updateSelectedTask(task: Task) { + if (this.selectedTask && this.selectedTask.id === task.id) { + Object.assign(this.selectedTask, task) + } + }, + + async fetchTaskById(taskId: TaskBase['id']) { + if (taskId === this.selectedTask?.id) return + + const task = await $tvApi.tasks.fetchTaskById(taskId).catch(logError) + + if (!task) return + + this.selectedTask = task + }, + + /** @deprecated */ + setTaskOrder(data: Partial) { + const task = this.tasks.find((t) => data.id === t.id) + if (!task) return + + if (isNotNullable(data.taskOrder)) { + task.taskOrder = data.taskOrder + } + if (isNotNullable(data.kanbanOrder)) { + task.kanbanOrder = data.kanbanOrder + } + }, + + // setTaskStatusId(data: TaskUpdateStatusIdArg) { + + // if (data.taskId === this.selectedTask?.id) { + // this.selectedTask.statusId = data.statusId; + // } + + // const task = this.tasks.find((tsk) => tsk.id === data.taskId); + + // if (!task) return; + + // task.statusId = data.statusId; + // this.updateSelectedTask(task); + // }, + + /** @deprecated */ + async updateOrders(data: TaskArgUpdate): Promise { + const task = await $tvApi.tasks.updateTask(data).catch(logError) + if (!task) return null + + this.setTaskOrder(task) + return task + }, + + async addTask(taskArg: TaskArgAdd): Promise { + if (taskArg.goalListId === ALL_TASKS_LIST_ID) { + taskArg.goalListId = null + } + + const task = await $tvApi.tasks.createTask(taskArg).catch(logError) + if (!task) return false + + if (task.parentId) { + let mainTask = this.tasks.find((t) => t.id === task.parentId) + + if (!mainTask && this.selectedTask?.id === task.parentId) { + mainTask = this.selectedTask + } + + if (!mainTask) { + throw new Error('Main task not found for adding subtask') + } + + if (mainTask) { + mainTask.subtasks.push(task) + } + + if (this.selectedTask?.id === task.parentId) { + this.selectedTask.subtasks.push(task) + } + } else { + this.tasks.unshift(task) + } + return task + }, + + appendTasks(items: Task[]) { + this.tasks = [...this.tasks, ...items] + }, + + async fetchAllTasks(goalId: number, showCompleted: 0 | 1 = 1, firstNew: 0 | 1 = 1, ignoreCompleted: boolean = false) { + this.loading = true + const searchParams = { + showCompleted: +showCompleted as 0 | 1, + goalId: +goalId, + firstNew: +firstNew as 0 | 1, + componentId: ALL_TASKS_LIST_ID, + page: +this.fetchRules.currentPage, + searchText: this.fetchRules.searchText, + filters: this.fetchRules.filters, + unlimited: true, + ignoreCompleted: ignoreCompleted, + } + + const tasks = await $tvApi.tasks + .fetch(searchParams) + .catch(logError) + .finally(() => { + this.loading = false + }) + if (!tasks) return false + this.tasks = tasks + + return false + }, + + async fetchTasks(): Promise { + this.loading = true + + const searchParams = { + showCompleted: +this.fetchRules.showCompleted as 0 | 1, + goalId: +this.fetchRules.goalId, + firstNew: +this.fetchRules.firstNew as 0 | 1, + componentId: +this.fetchRules.currentListId, + page: +this.fetchRules.currentPage, + searchText: this.fetchRules.searchText, + filters: this.fetchRules.filters, + } + + const tasks = await $tvApi.tasks + .fetch(searchParams) + .catch(logError) + .finally(() => { + this.loading = false + }) + if (!tasks) return false + + this.fetchRules.endOfTasks = tasks.length < 1 + if (!this.fetchRules.endOfTasks) { + this.fetchRules.currentPage++ + } + this.appendTasks(tasks) + return true + }, + + /**@todo rename to updateTaskComplete */ + async updateTaskCompleteStatus( + data: TaskArgUpdate, + deleteCompleted: boolean = true, + ): Promise { + this.loading = true + + const taskResult = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false + }) + + if (!taskResult) return undefined + + // this.setTaskStatusId({ taskId: taskResult.id, statusId: taskResult.statusId }); + + const searchId = taskResult.parentId || taskResult.id + + let parentTask: Task | undefined + + parentTask = this.tasks.find((tsk: Task) => tsk.id === searchId) + + if (!parentTask && searchId === this.selectedTask?.id) { + parentTask = this.selectedTask + } + + if (!parentTask) { + return undefined + } + + //we updating subtasks + if (taskResult.parentId) { + const subTask = parentTask.subtasks.find((sub) => sub.id === +taskResult.id) + if (subTask) { + subTask.complete = taskResult.complete + } + } else { + //we updating main task + parentTask.complete = taskResult.complete + //we deleting task from list if deleteCompleted is true + // and task complete is different from showCompleted rule for fetching + if (deleteCompleted && !!this.fetchRules.showCompleted !== data.complete) { + //handle condition with showing tasks in diff mode (showCompleted) + await new Promise((resolve) => setTimeout(resolve, 500)) + const taskIndex = this.tasks.findIndex((task) => task.id === taskResult.id) + if (taskIndex !== -1) { + this.tasks.splice(taskIndex, 1) + } + } + } + + this.updateSelectedTask(parentTask) + + return taskResult.complete + }, + + async updateTaskDescription(data: TaskArgUpdate): Promise { + this.loading = true + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false + }) + if (!task) return false + + const localTask = this.tasks.find(({ id }) => id === task.id) + if (localTask) { + Object.assign(localTask, task) + } + this.updateSelectedTask(task) + return true + }, + + async deleteTask(id: TaskArgDelete): Promise { + this.loading = true + + const result = await $tvApi.tasks + .deleteTask(id) + .catch(logError) + .finally(() => { + this.loading = false + }) + + if (!result) return false + + const taskIndex = this.tasks.findIndex((task) => task.id === id) + + if (taskIndex !== -1) { + this.tasks.splice(taskIndex, 1) + } else { + // Check if it's a subtask + for (const task of this.tasks) { + const subIndex = task.subtasks.findIndex((s) => s.id === id) + if (subIndex !== -1) { + task.subtasks.splice(subIndex, 1) + break + } + } + } + + if (this.selectedTask?.id === id) { + this.selectedTask = null + } else if (this.selectedTask) { + const subIndex = this.selectedTask.subtasks.findIndex((s) => s.id === id) + if (subIndex !== -1) { + this.selectedTask.subtasks.splice(subIndex, 1) + } + } + + return true + }, + + async updateTaskNote(data: TaskArgUpdate): Promise { + this.loading = true + + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false + }) + + if (!task) return false + + const localTask = this.tasks.find(({ id }) => id === task.id) + if (localTask) { + Object.assign(localTask, task) + } + this.updateSelectedTask(task) + + return true + }, + + //we do not fetch subtasks from server separately, we fetch all tasks with subtasks + // async fetchSubtasks(taskId: TaskItem['id']): Promise { + // this.loading = true; + // const subtasks = await $tvApi.tasks.fetch(taskId).catch(logError).finally(() => this.loading = false); + // // const result = await $api + // // .get>(`${this.urls.fetchSubtasks}?taskId=${taskId}`) + // // .catch((err) => console.log(err)); + // // this.loading = false; + // // if (result) { + // // if (result.data.response.length > 0) { + // // const task = this.tasks.find(({ id }) => id === +taskId); + // // if (task) { + // // task.subtasks = result.data.response; + // // this.updateSelectedTask(task); + // // } else { + // // if (this.selectedTask && +this.selectedTask.id === +taskId) { + // // this.selectedTask.subtasks = result.data.response; + // // } + // // } + // // } + // // } + // }, + + async toggleTagForTask(data: TagItemArgToggle) { + const tagsStore = useTagsStore() + const action = await tagsStore.toggleTag(data) + + if (!action) { + return + } + + const task = this.tasks.find((tsk) => +tsk.id === +data.taskId) + + const toggleTag = (task: Task) => { + if (action === 'delete') { + const tagIndex = task.tags.indexOf(data.tagId) + if (tagIndex !== -1) { + task.tags.splice(tagIndex, 1) + } + } else { + task.tags.push(data.tagId) + } + } + + if (task) { + toggleTag(task) + this.updateSelectedTask(task) + } else if (this.selectedTask?.id === data.taskId) { + toggleTag(this.selectedTask) + } + + }, + + async udpatePriority(data: TaskArgUpdate): Promise { + this.loading = true + + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false + }) + + if (!task) return + + const localTask = this.tasks.find(({ id }) => id === task.id) + + if (localTask) { + Object.assign(localTask, task) + } + + this.updateSelectedTask(task) + }, + + //TODO fixme consider refactoring to make one method for all updates + async moveTaskToAnotherList(data: TaskArgUpdate) { + this.loading = true + + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false + }) + + if (!task) return + + const localTask = this.tasks.find(({ id }) => id === task.id) + + if (localTask) { + Object.assign(localTask, task) + } + + this.updateSelectedTask(task) + }, + + async saveDateForTask(data: TaskArgUpdate) { + this.loading = true + + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false + }) + + if (!task) return + + const localTask = this.tasks.find(({ id }) => id === task.id) + + if (localTask) { + Object.assign(localTask, task) + } + + this.updateSelectedTask(task) + }, + + async updateTaskAssignee(data: TaskArgToggleAssignee) { + this.loading = true + + const updateAssignee = await $tvApi.tasks + .toggleTasksAssignee(data) + .catch(logError) + .finally(() => { + this.loading = false + }) + + if (!updateAssignee) return + + const task = this.tasks.find((tsk) => tsk.id === data.taskId) + + if (task) { + task.assignedUsers = updateAssignee.userIds + this.updateSelectedTask(task) + } else if (this.selectedTask?.id === data.taskId) { + this.selectedTask.assignedUsers = updateAssignee.userIds + } + }, + + // we do not fetch assigned users from server separately, we fetch all tasks with assigned users + // async fetchTaskAssignedUsers(data: Pick) { + // this.loading = true; + // const result = await $api + // .post>(this.urls.taskFetchAssignedUsers, data) + // .catch((err) => console.log(err)); + // this.loading = false; + + // if (result) { + // const task = this.tasks.find((tsk) => tsk.id === data.taskId); + // if (task) { + // task.assignedUsers = result.data.response.usersIds; + // this.updateSelectedTask(task); + // } else if (this.selectedTask?.id === data.taskId) { + // this.selectedTask.assignedUsers = result.data.response.usersIds; + // } + // } + // }, + + async updateTaskStatusId(data: TaskArgUpdate) { + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false + }) + + if (!task) return + + const localTask = this.tasks.find(({ id }) => id === task.id) + + if (localTask) { + Object.assign(localTask, task) + this.updateSelectedTask(task) + } else if (this.selectedTask?.id === task.id) { + Object.assign(this.selectedTask, task) + } + }, + + async updateTaskAmount(data: TaskArgUpdate) { + this.loading = true + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false + }) + + if (!task) return + + const localTask = this.tasks.find(({ id }) => id === task.id) + + if (localTask) { + Object.assign(localTask, task) + this.updateSelectedTask(task) + } else if (this.selectedTask?.id === task.id) { + Object.assign(this.selectedTask, task) + } + }, + + async updateTaskTransactionType(data: TaskArgUpdate) { + this.loading = true + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false + }) + + if (!task) return + + const localTask = this.tasks.find(({ id }) => id === task.id) + + if (localTask) { + Object.assign(localTask, task) + this.updateSelectedTask(task) + } else if (this.selectedTask?.id === task.id) { + Object.assign(this.selectedTask, task) + } + }, + }, +}) diff --git a/web/src/stores/user.store.ts b/web/src/stores/user.store.ts index 61b0edc..fa841ef 100644 --- a/web/src/stores/user.store.ts +++ b/web/src/stores/user.store.ts @@ -1,42 +1,42 @@ -import { defineStore } from 'pinia'; -import type { JWTPayload } from '@/helpers/AppTypes'; -import { parseJwt } from '@/helpers/Helper'; +import { defineStore } from 'pinia' +import type { JWTPayload } from '@/helpers/AppTypes' +import { parseJwt } from '@/helpers/Helper' export const useUserStore = defineStore('user', { - state: () => ({ - loading: true, - login: '', - email: '', - accessToken: '', - refreshToken: '', - authType: 'jwt', - }), - getters: { - isLoggedIn(state): boolean { - return state.login.trim() !== '' && state.accessToken.trim() !== '' && state.refreshToken.trim() !== ''; - }, - payloadData(state) { - const tokenData = parseJwt(state.accessToken); - return tokenData?.userData; - }, + state: () => ({ + loading: true, + login: '', + email: '', + accessToken: '', + refreshToken: '', + authType: 'jwt', + }), + getters: { + isLoggedIn(state): boolean { + return state.login.trim() !== '' && state.accessToken.trim() !== '' && state.refreshToken.trim() !== '' }, - actions: { - setAccessToken(token: string) { - this.accessToken = token; - }, - setRefreshToken(token: string) { - this.refreshToken = token; - }, - setLogin(login: string) { - this.login = login; - }, - - setEmail(email: string) { - this.email = email; - }, - - setAuthType(type: JWTPayload['type']) { - this.authType = type; - }, + payloadData(state) { + const tokenData = parseJwt(state.accessToken) + return tokenData?.userData }, -}); + }, + actions: { + setAccessToken(token: string) { + this.accessToken = token + }, + setRefreshToken(token: string) { + this.refreshToken = token + }, + setLogin(login: string) { + this.login = login + }, + + setEmail(email: string) { + this.email = email + }, + + setAuthType(type: JWTPayload['type']) { + this.authType = type + }, + }, +}) diff --git a/web/src/types/analytics.types.ts b/web/src/types/analytics.types.ts index 9b73976..e16dc49 100644 --- a/web/src/types/analytics.types.ts +++ b/web/src/types/analytics.types.ts @@ -1,7 +1,7 @@ -import type { GoalItem } from 'taskview-api'; -import type { CollaborationUsers } from './collaboration.types'; -import type { AppResponse } from './global-app.types'; -import type { TaskItem } from './tasks.types'; +import type { GoalItem } from 'taskview-api' +import type { CollaborationUsers } from './collaboration.types' +import type { AppResponse } from './global-app.types' +import type { TaskItem } from './tasks.types' export type AnalyticsStoreState = { loading: boolean; diff --git a/web/src/types/app.types.ts b/web/src/types/app.types.ts index 8ceb829..74aca53 100644 --- a/web/src/types/app.types.ts +++ b/web/src/types/app.types.ts @@ -5,4 +5,4 @@ export type TI18nLocalesInApp = { ru: Record; }; -export const DEFAULT_ID = -1; +export const DEFAULT_ID = -1 diff --git a/web/src/types/base-screen.types.ts b/web/src/types/base-screen.types.ts index 6af03c8..0a50ecd 100644 --- a/web/src/types/base-screen.types.ts +++ b/web/src/types/base-screen.types.ts @@ -1,7 +1,6 @@ -import { mdiAccountBoxMultipleOutline, mdiClipboardEditOutline, mdiLightbulbOn10 } from '@mdi/js'; -import type { TagItem } from './tags.types'; -import type { Task } from 'taskview-api'; -import type { TaskItem } from './tasks.types'; +import type { TagItem } from './tags.types' +import type { Task } from 'taskview-api' +import type { TaskItem } from './tasks.types' type RespTasks = TaskItem[]; type RespUsers = { id: number; name: string; users: { id: number; email: string }[] }[]; @@ -56,20 +55,20 @@ export type FiltersTasks = { }; export const FILTER_DEFAULT: FiltersTasks = { - projects: [], - priorities: [], - tags: [], - assignees: [], -}; + projects: [], + priorities: [], + tags: [], + assignees: [], +} // export type SortTasks = { // byId: typeof SORT_ASC | typeof SORT_DESC | typeof SORT_DEFAULT; // byName: typeof SORT_ASC | typeof SORT_DESC | typeof SORT_DEFAULT; // }; -export const ICON_PROJECT = { icon: mdiLightbulbOn10, color: '#FFB800' }; -export const ICON_TASKS = { icon: mdiClipboardEditOutline, color: '#38D681' }; -export const ICON_USERS = { icon: mdiAccountBoxMultipleOutline, color: '#BE8CFF' }; -export const DIALOG_WIDTH = 600; -export const DIALOG_DESKTOP_HEIGHT = '50%'; +export const ICON_PROJECT = { icon: 'i-lucide-lightbulb', color: '#FFB800' } +export const ICON_TASKS = { icon: 'i-lucide-clipboard-edit', color: '#38D681' } +export const ICON_USERS = { icon: 'i-lucide-users', color: '#BE8CFF' } +export const DIALOG_WIDTH = 600 +export const DIALOG_DESKTOP_HEIGHT = '50%' export type BaseScreenSearchResponse = TaskItem[]; diff --git a/web/src/types/collaboration-permissions.types.ts b/web/src/types/collaboration-permissions.types.ts index a3d628a..413e9da 100644 --- a/web/src/types/collaboration-permissions.types.ts +++ b/web/src/types/collaboration-permissions.types.ts @@ -1,14 +1,14 @@ -import type { CollaborationPermission } from 'taskview-api'; -import type { TI18nLocalesInApp } from './app.types'; +import type { CollaborationPermission } from 'taskview-api' +import type { TI18nLocalesInApp } from './app.types' export type PermissioinGroupsIds = 1 | 2 | 3 | 4; export const PermissionGroups: Record> = { - 1: { en: 'Applicarion level', ru: 'Уровня приложения' }, - 2: { en: 'Project', ru: 'Проект' }, - 3: { en: 'Task list', ru: 'Список задач' }, - 4: { en: 'Tasks', ru: 'Задачи' }, -}; + 1: { en: 'Applicarion level', ru: 'Уровня приложения' }, + 2: { en: 'Project', ru: 'Проект' }, + 3: { en: 'Task list', ru: 'Список задач' }, + 4: { en: 'Tasks', ru: 'Задачи' }, +} export type CollaborationPermissionsState = { permissions: CollaborationPermission[]; diff --git a/web/src/types/collaboration-roles.types.ts b/web/src/types/collaboration-roles.types.ts index 8cfc1a4..88ab8bf 100644 --- a/web/src/types/collaboration-roles.types.ts +++ b/web/src/types/collaboration-roles.types.ts @@ -1,4 +1,4 @@ -import type { CollaborationPermission } from 'taskview-api'; +import type { CollaborationPermission } from 'taskview-api' export type CollaborationRole = { id: number; diff --git a/web/src/types/collaboration.types.ts b/web/src/types/collaboration.types.ts index 986d826..98e2bdf 100644 --- a/web/src/types/collaboration.types.ts +++ b/web/src/types/collaboration.types.ts @@ -1,4 +1,4 @@ -import type { CollaborationResponseFetchAllUsers } from 'taskview-api'; +import type { CollaborationResponseFetchAllUsers } from 'taskview-api' export type CollaborationUsers = CollaborationResponseFetchAllUsers[]; diff --git a/web/src/types/global-app.types.ts b/web/src/types/global-app.types.ts index 48ddfe9..95c3d52 100644 --- a/web/src/types/global-app.types.ts +++ b/web/src/types/global-app.types.ts @@ -3,6 +3,9 @@ export type AppResponse = { rid: string; }; +export type TaskDetailDisplayMode = 'slideover' | 'modal'; + export type AppStoreState = { drawer: boolean; + taskDetailDisplayMode: TaskDetailDisplayMode; }; diff --git a/web/src/types/goal-lists.types.ts b/web/src/types/goal-lists.types.ts index 8de6d93..c5c2946 100644 --- a/web/src/types/goal-lists.types.ts +++ b/web/src/types/goal-lists.types.ts @@ -1,27 +1,13 @@ -import type { GoalItem, GoalListItem } from 'taskview-api'; -// import type { GoalItem } from './goals.types'; +import type { GoalListItem } from 'taskview-api' /** @deprecated use GoalListItem[] from taskview-api instead */ export type GoalListItems = GoalListItem[]; export type GoalListsStoreState = { - currentGoalId: GoalItem['id']; lists: GoalListItem[]; loading: boolean; }; -// /** @deprecated */ -// export type GoalListAddArg = { -// goalId: number; -// name: string; -// }; - -// /** @deprecated */ -// export type GoalListAddResponse = { -// add: boolean; -// component?: GoalListItem; -// }; - export type GoalListEventMoreMenu = { activator: HTMLElement; list: GoalListItem; @@ -35,21 +21,3 @@ export type GoalListActionsItems = { eventName: GoalListActionsItemsEvents; icon?: string; }[]; - -// export type GoalListDeleteResponse = { -// delete: boolean; -// }; - -// export type GoalListUpdateArg = { -// id: number; -// name: string; -// }; - -// export type GoalListUpdateResponse = { -// update: boolean; -// component?: GoalListItem; -// }; - -// export type ArchiveListArg = { goalListId: GoalListItem['id']; archive: GoalListItem['archive'] }; -// export type ArchiveListResp = { archive: 0 | 1 }; -export type GoalSectionsTabs = 'tasks' | 'lists'; diff --git a/web/src/types/goals.types.ts b/web/src/types/goals.types.ts index d971753..0feefb7 100644 --- a/web/src/types/goals.types.ts +++ b/web/src/types/goals.types.ts @@ -1,18 +1,18 @@ -import type { GoalItem } from 'taskview-api'; -import { TvPermissions } from 'taskview-api'; +import type { GoalItem } from 'taskview-api' +import { TvPermissions } from 'taskview-api' export const DEFAULT_GOAL_ITEM: GoalItem = { - id: -1, - name: '', - description: '', - color: '', - owner: -1, - permissions: {}, - archive: 0, - dateCreation: null, -}; + id: -1, + name: '', + description: '', + color: '', + owner: -1, + permissions: {}, + archive: 0, + dateCreation: null, +} -export const AllGoalPermissions = TvPermissions; +export const AllGoalPermissions = TvPermissions export type GoalsStoreState = { loading: boolean; diff --git a/web/src/types/graph.types.ts b/web/src/types/graph.types.ts index 1fd91dd..22c7dcf 100644 --- a/web/src/types/graph.types.ts +++ b/web/src/types/graph.types.ts @@ -1,4 +1,4 @@ -import type { Edge, Node } from '@vue-flow/core'; +import type { Edge, Node } from '@vue-flow/core' export type GraphStore = { nodes: Node[]; diff --git a/web/src/types/kanban.types.ts b/web/src/types/kanban.types.ts index c41c0c0..cf69bd4 100644 --- a/web/src/types/kanban.types.ts +++ b/web/src/types/kanban.types.ts @@ -1,4 +1,4 @@ -import type { GoalItem, KanbanColumnItem, KanbanResponseTasksForColumn } from 'taskview-api'; +import type { GoalItem, KanbanColumnItem, KanbanResponseTasksForColumn } from 'taskview-api' export type KanbanStoreState = { tasksData: { diff --git a/web/src/types/layout.types.ts b/web/src/types/layout.types.ts index 6f20f85..5763580 100644 --- a/web/src/types/layout.types.ts +++ b/web/src/types/layout.types.ts @@ -1,5 +1,5 @@ -import type { Component } from 'vue'; -import type { RouteLocationNamedRaw } from 'vue-router'; +import type { Component } from 'vue' +import type { RouteLocationNamedRaw } from 'vue-router' export type TvNavItemT = { title: string; diff --git a/web/src/types/router.d.ts b/web/src/types/router.d.ts index 2c77c87..57999c7 100644 --- a/web/src/types/router.d.ts +++ b/web/src/types/router.d.ts @@ -1,6 +1,6 @@ -import type { MiddlewareHandlers } from '@/router'; +import type { MiddlewareHandlers } from '@/router' -import 'vue-router'; +import 'vue-router' declare module 'vue-router' { interface RouteMeta { diff --git a/web/src/types/tags.types.ts b/web/src/types/tags.types.ts index 67c2804..4f8b332 100644 --- a/web/src/types/tags.types.ts +++ b/web/src/types/tags.types.ts @@ -1,4 +1,4 @@ -import type { TagItem as TagItemFromApi } from 'taskview-api'; +import type { TagItem as TagItemFromApi } from 'taskview-api' export type TagItem = TagItemFromApi; diff --git a/web/src/types/task-history.types.ts b/web/src/types/task-history.types.ts index 32a43f0..8c99fa9 100644 --- a/web/src/types/task-history.types.ts +++ b/web/src/types/task-history.types.ts @@ -1,14 +1,14 @@ -import type { Task } from 'taskview-api'; -import type { TaskItem } from '@/types/tasks.types'; +import type { Task } from 'taskview-api' +import type { TaskItem } from '@/types/tasks.types' export type PriorityValues = 'low' | 'medium' | 'high'; export type PriorityItem = { id: Task['priorityId']; code: PriorityValues }; export const PriorityIdToLocaleString: Record = { - 1: 'priority.low', - 2: 'priority.medium', - 3: 'priority.high', -}; + 1: 'priority.low', + 2: 'priority.medium', + 3: 'priority.high', +} export type TaskHistoryItem = TaskItem; export type TaskHistoryState = { diff --git a/web/src/types/tasks.types.ts b/web/src/types/tasks.types.ts index 4d619c7..7845fd0 100644 --- a/web/src/types/tasks.types.ts +++ b/web/src/types/tasks.types.ts @@ -1,4 +1,4 @@ -import type { GoalItem, GoalListItem, Task, TaskFilters } from 'taskview-api'; +import type { GoalItem, GoalListItem, Task, TaskFilters } from 'taskview-api' export type TaskItem = Task; @@ -25,9 +25,10 @@ export type TasksStoreState = { }; export const PRIORITY_COLORS = { - '1': { color: '#38D681', t: 'priority.low' }, - '2': { color: '#FF9100', t: 'priority.medium' }, - '3': { color: '#FF1744', t: 'priority.high' }, -}; + '1': { color: '#38D681', t: 'priority.low' }, + '2': { color: '#FF9100', t: 'priority.medium' }, + '3': { color: '#FF1744', t: 'priority.high' }, +} -export const ALL_TASKS_LIST_ID = -1401; +/**@deprecated use ALL_TASKS_LIST_ID from taskview-api */ +export const ALL_TASKS_LIST_ID = -1401 diff --git a/web/src/vite-env.d.ts b/web/src/vite-env.d.ts new file mode 100644 index 0000000..634736b --- /dev/null +++ b/web/src/vite-env.d.ts @@ -0,0 +1,3 @@ +/// + +declare const APP_VERSION: string diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json index a601717..702b58d 100644 --- a/web/tsconfig.app.json +++ b/web/tsconfig.app.json @@ -1,36 +1,40 @@ { - "extends": "@vue/tsconfig/tsconfig.dom.json", - "include": [ - "env.d.ts", - "src/**/*", - "src/**/*.vue", - "src/**/*.d.ts", - "src/types/**/*.d.ts" - ], - "exclude": [ - "src/**/__tests__/*" - ], "compilerOptions": { - "types": [ - "node" + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" ], - "module": "esnext", - "composite": true, - // "esModuleInterop": true, - // "skipLibCheck": true, + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "preserve", + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + /* Paths */ "baseUrl": ".", "paths": { - "@/*": [ - "./src/*", - ], - "~/*": [ - "src/*" + "@/*": ["./src/*"], + "#build/ui/*": [ + "./node_modules/.nuxt-ui/ui/*" ] - }, - "lib": [ - "ESNext", - "DOM", - "esnext.promise" - ] - } + } + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "src/**/*.vue", + "auto-imports.d.ts", + "components.d.ts" + ] } \ No newline at end of file diff --git a/web/tsconfig.json b/web/tsconfig.json index 2c5e1b9..1ffef60 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -1,3 +1,7 @@ { - "extends": "./tsconfig.app.json", -} \ No newline at end of file + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json new file mode 100644 index 0000000..bac01b7 --- /dev/null +++ b/web/tsconfig.node.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": [ + "ES2023" + ], + "module": "ESNext", + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + /* Paths - use local vite types instead of root node_modules */ + "paths": { + "#build/ui": [ + "./node_modules/.nuxt-ui/ui" + ], + "vite": [ + "./node_modules/vite" + ] + }, + "typeRoots": [ + "./node_modules/@types", + "./node_modules" + ] + }, + "include": [ + "vite.config.ts" + ] +} \ No newline at end of file diff --git a/web/vercel.json b/web/vercel.json new file mode 100644 index 0000000..072cd7d --- /dev/null +++ b/web/vercel.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "rewrites": [ + { + "source": "/(.*)", + "destination": "/index.html" + } + ] +} diff --git a/web/vite.config.ts b/web/vite.config.ts index 8717d49..b81dcae 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,25 +1,156 @@ -import { fileURLToPath, URL } from 'node:url'; +import { fileURLToPath, URL } from 'node:url' +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import ui from '@nuxt/ui/vite' +// import { transformWithEsbuild } from 'vite' +// import type { Plugin } from 'vite' -import { defineConfig } from 'vite'; -import vue from '@vitejs/plugin-vue'; -import vueJsx from '@vitejs/plugin-vue-jsx'; +// function transpileArktype(): Plugin { +// return { +// name: 'transpile-arktype', +// apply: 'build', +// async transform(code, id) { +// if (id.includes('@ark/') || id.includes('arktype')) { +// const result = await transformWithEsbuild(code, id, { +// target: 'es2020', +// format: 'esm', +// }) +// return { code: result.code, map: result.map } +// } +// }, +// } +// } // https://vitejs.dev/config/ export default defineConfig({ - define: { - APP_VERSION: JSON.stringify(process.env.npm_package_version), - }, - build: { - target: 'ESNext', - }, - server: { - host: 'localhost', - port: 3000, - }, - plugins: [vue(), vueJsx()], - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)), + define: { + APP_VERSION: JSON.stringify(process.env.npm_package_version), + }, + plugins: [ + vue(), + ui({ + ui: { + drawer: { + slots: { + container: 'pb-[calc(var(--tv-safe-area-inset-bottom)+16px)]' + } }, + slideover: { + slots: { + content: 'py-safe', + }, + }, + dashboardGroup: { + base: 'py-safe', + }, + dashboardPanel: { + slots: { + body: 'p-2! pb-[calc(var(--tv-safe-area-inset-bottom)+var(--tv-safe-area-inset-top)+64px)]! lg:pb-0!' + }, + }, + modal: { + variants: { + fullscreen: { + true: { + content: 'py-safe', + } + } + } + }, + dashboardSearchButton: { + slots: { + base: 'rounded-lg', + }, + }, + dashboardSearch: { + variants: { + fullscreen: { + true: { + // was too much close to the top with safe area + input: 'mt-4' + } + } + } + }, + dashboardNavbar: { + slots: { + title: 'text-lg!', + }, + }, + button: { + slots: { + base: 'cursor-pointer', + }, + defaultVariants: { + size: 'xl', + }, + }, + input: { + slots: { + base: 'rounded-lg', + }, + defaultVariants: { + size: 'xl', + }, + }, + textarea: { + defaultVariants: { + size: 'xl', + }, + }, + colors: { + primary: 'green', + neutral: 'zinc', + }, + collapsible: { + slots: { + content: 'p-2', + }, + }, + pageCard: { + variants: { + variant: { + taskview: { + root: 'cursor-pointer shadow-sm bg-tv-ui-bg-elevated', + container: 'sm:p-2 sm:px-4', + }, + }, + + }, + }, + checkbox: { + slots: { + base: 'ring-2 cursor-pointer', + }, + variants: { + size: { + xl: { + base: 'size-5', + container: 'h-5', + }, + }, + }, + defaultVariants: { + size: 'xl', + color: 'primary', + }, + }, + }, + }), + // transpileArktype(), + ], + // build: { + // target: ['es2020', 'safari15'], + // }, + // optimizeDeps: { + // include: ['arktype', '@ark/schema', '@ark/util'], + // esbuildOptions: { + // target: 'es2020', + // }, + // }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), }, -}); + }, +}) diff --git a/web__old/.gitignore b/web__old/.gitignore new file mode 100644 index 0000000..468bac4 --- /dev/null +++ b/web__old/.gitignore @@ -0,0 +1,2 @@ +!build-upload.sh +tsconfig.tsbuildinfo \ No newline at end of file diff --git a/web/README.md b/web__old/README.md similarity index 100% rename from web/README.md rename to web__old/README.md diff --git a/web__old/android/.gitignore b/web__old/android/.gitignore new file mode 100644 index 0000000..48354a3 --- /dev/null +++ b/web__old/android/.gitignore @@ -0,0 +1,101 @@ +# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore + +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + +# Android Profiling +*.hprof + +# Cordova plugins for Capacitor +capacitor-cordova-android-plugins + +# Copied web assets +app/src/main/assets/public + +# Generated Config files +app/src/main/assets/capacitor.config.json +app/src/main/assets/capacitor.plugins.json +app/src/main/res/xml/config.xml diff --git a/web__old/android/app/.gitignore b/web__old/android/app/.gitignore new file mode 100644 index 0000000..043df80 --- /dev/null +++ b/web__old/android/app/.gitignore @@ -0,0 +1,2 @@ +/build/* +!/build/.npmkeep diff --git a/web__old/android/app/build.gradle b/web__old/android/app/build.gradle new file mode 100644 index 0000000..ab71d00 --- /dev/null +++ b/web__old/android/app/build.gradle @@ -0,0 +1,57 @@ +apply plugin: 'com.android.application' + +android { + namespace "com.handscream.taskview.app" + compileSdk rootProject.ext.compileSdkVersion + defaultConfig { + applicationId "com.handscreamgnl.taskview.app" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1196 + versionName "1.19.6" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + aaptOptions { + // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. + // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 + ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' + } + setProperty("archivesBaseName", "TaskView-$versionName") + + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.debug + } + } +} + +repositories { + flatDir{ + dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" + implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" + implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" + implementation project(':capacitor-android') + testImplementation "junit:junit:$junitVersion" + androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" + androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" + implementation project(':capacitor-cordova-android-plugins') +} + +apply from: 'capacitor.build.gradle' + +try { + def servicesJSON = file('google-services.json') + if (servicesJSON.text) { + apply plugin: 'com.google.gms.google-services' + } +} catch(Exception e) { + logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") +} diff --git a/web__old/android/app/capacitor.build.gradle b/web__old/android/app/capacitor.build.gradle new file mode 100644 index 0000000..89c30ab --- /dev/null +++ b/web__old/android/app/capacitor.build.gradle @@ -0,0 +1,24 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN + +android { + compileOptions { + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 + } +} + +apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" +dependencies { + implementation project(':capacitor-app') + implementation project(':capacitor-browser') + implementation project(':capacitor-device') + implementation project(':capacitor-preferences') + implementation project(':capacitor-splash-screen') + implementation project(':capgo-capacitor-updater') + +} + + +if (hasProperty('postBuildExtras')) { + postBuildExtras() +} diff --git a/web/android/app/debug/output-metadata.json b/web__old/android/app/debug/output-metadata.json similarity index 100% rename from web/android/app/debug/output-metadata.json rename to web__old/android/app/debug/output-metadata.json diff --git a/web__old/android/app/proguard-rules.pro b/web__old/android/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/web__old/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/web__old/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java b/web__old/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java new file mode 100644 index 0000000..f2c2217 --- /dev/null +++ b/web__old/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.getcapacitor.myapp; + +import static org.junit.Assert.*; + +import android.content.Context; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("com.getcapacitor.app", appContext.getPackageName()); + } +} diff --git a/web__old/android/app/src/main/AndroidManifest.xml b/web__old/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d41af82 --- /dev/null +++ b/web__old/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web__old/android/app/src/main/java/com/handscream/taskview/app/MainActivity.java b/web__old/android/app/src/main/java/com/handscream/taskview/app/MainActivity.java new file mode 100644 index 0000000..34955e4 --- /dev/null +++ b/web__old/android/app/src/main/java/com/handscream/taskview/app/MainActivity.java @@ -0,0 +1,5 @@ +package com.handscream.taskview.app; + +import com.getcapacitor.BridgeActivity; + +public class MainActivity extends BridgeActivity {} diff --git a/web__old/android/app/src/main/res/drawable-hdpi/___splash.png b/web__old/android/app/src/main/res/drawable-hdpi/___splash.png new file mode 100644 index 0000000..666d433 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-hdpi/___splash.png differ diff --git a/web__old/android/app/src/main/res/drawable-hdpi/splash.png b/web__old/android/app/src/main/res/drawable-hdpi/splash.png new file mode 100644 index 0000000..a6a6f21 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-hdpi/splash.png differ diff --git a/web__old/android/app/src/main/res/drawable-ldpi/___splash.png b/web__old/android/app/src/main/res/drawable-ldpi/___splash.png new file mode 100644 index 0000000..88b0e36 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-ldpi/___splash.png differ diff --git a/web__old/android/app/src/main/res/drawable-ldpi/splash.png b/web__old/android/app/src/main/res/drawable-ldpi/splash.png new file mode 100644 index 0000000..a6a6f21 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-ldpi/splash.png differ diff --git a/web__old/android/app/src/main/res/drawable-mdpi/___splash.png b/web__old/android/app/src/main/res/drawable-mdpi/___splash.png new file mode 100644 index 0000000..8388bc9 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-mdpi/___splash.png differ diff --git a/web__old/android/app/src/main/res/drawable-mdpi/splash.png b/web__old/android/app/src/main/res/drawable-mdpi/splash.png new file mode 100644 index 0000000..a6a6f21 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-mdpi/splash.png differ diff --git a/web__old/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/web__old/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/web__old/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/web__old/android/app/src/main/res/drawable-xhdpi/___splash.png b/web__old/android/app/src/main/res/drawable-xhdpi/___splash.png new file mode 100644 index 0000000..0b1024d Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-xhdpi/___splash.png differ diff --git a/web__old/android/app/src/main/res/drawable-xhdpi/splash.png b/web__old/android/app/src/main/res/drawable-xhdpi/splash.png new file mode 100644 index 0000000..a6a6f21 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-xhdpi/splash.png differ diff --git a/web__old/android/app/src/main/res/drawable-xxhdpi/___splash.png b/web__old/android/app/src/main/res/drawable-xxhdpi/___splash.png new file mode 100644 index 0000000..b6cf1d6 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-xxhdpi/___splash.png differ diff --git a/web__old/android/app/src/main/res/drawable-xxhdpi/splash.png b/web__old/android/app/src/main/res/drawable-xxhdpi/splash.png new file mode 100644 index 0000000..a6a6f21 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-xxhdpi/splash.png differ diff --git a/web__old/android/app/src/main/res/drawable-xxxhdpi/___splash.png b/web__old/android/app/src/main/res/drawable-xxxhdpi/___splash.png new file mode 100644 index 0000000..5e37270 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-xxxhdpi/___splash.png differ diff --git a/web__old/android/app/src/main/res/drawable-xxxhdpi/splash.png b/web__old/android/app/src/main/res/drawable-xxxhdpi/splash.png new file mode 100644 index 0000000..a6a6f21 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable-xxxhdpi/splash.png differ diff --git a/web__old/android/app/src/main/res/drawable/ic_launcher_background.xml b/web__old/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/web__old/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web__old/android/app/src/main/res/drawable/splash.png b/web__old/android/app/src/main/res/drawable/splash.png new file mode 100644 index 0000000..666d433 Binary files /dev/null and b/web__old/android/app/src/main/res/drawable/splash.png differ diff --git a/web__old/android/app/src/main/res/layout/activity_main.xml b/web__old/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..b5ad138 --- /dev/null +++ b/web__old/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/web__old/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/web__old/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/web__old/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/web__old/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/web__old/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/web__old/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/web__old/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/web__old/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..9363281 Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/web__old/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/web__old/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..4f9348c Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/web__old/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/web__old/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..e4ebaaa Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/web__old/android/app/src/main/res/mipmap-ldpi/ic_launcher.png b/web__old/android/app/src/main/res/mipmap-ldpi/ic_launcher.png new file mode 100644 index 0000000..31fd18e Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-ldpi/ic_launcher.png differ diff --git a/web__old/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/web__old/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..1108b0c Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/web__old/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/web__old/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..840f47e Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/web__old/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/web__old/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..1928529 Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/web__old/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/web__old/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..f1e0840 Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/web__old/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/web__old/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..f34b874 Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/web__old/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/web__old/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..56176b5 Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/web__old/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/web__old/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..50b5876 Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/web__old/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/web__old/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..510b4fd Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/web__old/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/web__old/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..a8d71a5 Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/web__old/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/web__old/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..3b8620b Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/web__old/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/web__old/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..25b9e51 Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/web__old/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/web__old/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..77650da Binary files /dev/null and b/web__old/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/web__old/android/app/src/main/res/values/ic_launcher_background.xml b/web__old/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..beab31f --- /dev/null +++ b/web__old/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #000000 + \ No newline at end of file diff --git a/web__old/android/app/src/main/res/values/strings.xml b/web__old/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..612e621 --- /dev/null +++ b/web__old/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + TaskView + TaskView + com.handscream.taskview.app + com.handscream.taskview.app + diff --git a/web__old/android/app/src/main/res/values/styles.xml b/web__old/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..be874e5 --- /dev/null +++ b/web__old/android/app/src/main/res/values/styles.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/web__old/android/app/src/main/res/xml/file_paths.xml b/web__old/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..bd0c4d8 --- /dev/null +++ b/web__old/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/web__old/android/app/src/main/res/xml/network_security_config.xml b/web__old/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..7448506 --- /dev/null +++ b/web__old/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/web__old/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java b/web__old/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java new file mode 100644 index 0000000..0297327 --- /dev/null +++ b/web__old/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java @@ -0,0 +1,18 @@ +package com.getcapacitor.myapp; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} diff --git a/web__old/android/build.gradle b/web__old/android/build.gradle new file mode 100644 index 0000000..e6d4bd3 --- /dev/null +++ b/web__old/android/build.gradle @@ -0,0 +1,29 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.7.3' + classpath 'com.google.gms:google-services:4.4.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +apply from: "variables.gradle" + +allprojects { + repositories { + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/web__old/android/capacitor.settings.gradle b/web__old/android/capacitor.settings.gradle new file mode 100644 index 0000000..56ae2d1 --- /dev/null +++ b/web__old/android/capacitor.settings.gradle @@ -0,0 +1,21 @@ +// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN +include ':capacitor-android' +project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@7.4.5_@capacitor+core@7.4.5/node_modules/@capacitor/android/capacitor') + +include ':capacitor-app' +project(':capacitor-app').projectDir = new File('../../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/app/android') + +include ':capacitor-browser' +project(':capacitor-browser').projectDir = new File('../../../node_modules/.pnpm/@capacitor+browser@7.0.3_@capacitor+core@7.4.5/node_modules/@capacitor/browser/android') + +include ':capacitor-device' +project(':capacitor-device').projectDir = new File('../../../node_modules/.pnpm/@capacitor+device@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/device/android') + +include ':capacitor-preferences' +project(':capacitor-preferences').projectDir = new File('../../../node_modules/.pnpm/@capacitor+preferences@7.0.1_@capacitor+core@7.4.5/node_modules/@capacitor/preferences/android') + +include ':capacitor-splash-screen' +project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/.pnpm/@capacitor+splash-screen@7.0.4_@capacitor+core@7.4.5/node_modules/@capacitor/splash-screen/android') + +include ':capgo-capacitor-updater' +project(':capgo-capacitor-updater').projectDir = new File('../../../node_modules/.pnpm/@capgo+capacitor-updater@7.41.1_@capacitor+core@7.4.5/node_modules/@capgo/capacitor-updater/android') diff --git a/web__old/android/gradle.properties b/web__old/android/gradle.properties new file mode 100644 index 0000000..2e87c52 --- /dev/null +++ b/web__old/android/gradle.properties @@ -0,0 +1,22 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true diff --git a/web__old/android/gradle/wrapper/gradle-wrapper.jar b/web__old/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..033e24c Binary files /dev/null and b/web__old/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/web__old/android/gradle/wrapper/gradle-wrapper.properties b/web__old/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c1d5e01 --- /dev/null +++ b/web__old/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/web__old/android/gradlew b/web__old/android/gradlew new file mode 100755 index 0000000..fcb6fca --- /dev/null +++ b/web__old/android/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/web__old/android/gradlew.bat b/web__old/android/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/web__old/android/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/web__old/android/settings.gradle b/web__old/android/settings.gradle new file mode 100644 index 0000000..3b4431d --- /dev/null +++ b/web__old/android/settings.gradle @@ -0,0 +1,5 @@ +include ':app' +include ':capacitor-cordova-android-plugins' +project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') + +apply from: 'capacitor.settings.gradle' \ No newline at end of file diff --git a/web__old/android/variables.gradle b/web__old/android/variables.gradle new file mode 100644 index 0000000..2c8e408 --- /dev/null +++ b/web__old/android/variables.gradle @@ -0,0 +1,16 @@ +ext { + minSdkVersion = 23 + compileSdkVersion = 35 + targetSdkVersion = 35 + androidxActivityVersion = '1.9.2' + androidxAppCompatVersion = '1.7.0' + androidxCoordinatorLayoutVersion = '1.2.0' + androidxCoreVersion = '1.15.0' + androidxFragmentVersion = '1.8.4' + coreSplashScreenVersion = '1.0.1' + androidxWebkitVersion = '1.12.1' + junitVersion = '4.13.2' + androidxJunitVersion = '1.2.1' + androidxEspressoCoreVersion = '3.6.1' + cordovaAndroidVersion = '10.1.1' +} \ No newline at end of file diff --git a/web/assets/logos/logo-1024-a.png b/web__old/assets/logos/logo-1024-a.png similarity index 100% rename from web/assets/logos/logo-1024-a.png rename to web__old/assets/logos/logo-1024-a.png diff --git a/web/assets/logos/logo-with-name-light.png b/web__old/assets/logos/logo-with-name-light.png similarity index 100% rename from web/assets/logos/logo-with-name-light.png rename to web__old/assets/logos/logo-with-name-light.png diff --git a/web/assets/logos/logo-with-name-light.svg b/web__old/assets/logos/logo-with-name-light.svg similarity index 100% rename from web/assets/logos/logo-with-name-light.svg rename to web__old/assets/logos/logo-with-name-light.svg diff --git a/web/biome.json b/web__old/biome.json similarity index 100% rename from web/biome.json rename to web__old/biome.json diff --git a/web__old/build-docker-web.sh b/web__old/build-docker-web.sh new file mode 100644 index 0000000..9829037 --- /dev/null +++ b/web__old/build-docker-web.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# This script builds the docker image for the taskview-ce-webapp + +VERSION=$1 + +pnpm build + +docker buildx build --platform=linux/amd64,linux/arm64 -t gimanhead/taskview-ce-webapp:$VERSION -t gimanhead/taskview-ce-webapp:latest . --load \ No newline at end of file diff --git a/web/build-upload.sh b/web__old/build-upload.sh similarity index 100% rename from web/build-upload.sh rename to web__old/build-upload.sh diff --git a/web__old/capacitor.config.ts b/web__old/capacitor.config.ts new file mode 100644 index 0000000..5390c5e --- /dev/null +++ b/web__old/capacitor.config.ts @@ -0,0 +1,30 @@ +import type { CapacitorConfig } from '@capacitor/cli'; + +const config: CapacitorConfig = { + appId: 'com.handscream.taskview.app', + appName: 'TaskView', + webDir: 'dist', + zoomEnabled: false, + android: { + zoomEnabled: false, + }, + ios: { + zoomEnabled: false, + }, + server: { + hostname: 'app.taskview.tech', + }, + plugins: { + CapacitorUpdater: { + autoUpdate: false, + }, + CapacitorCookies: { + enabled: true, + }, + CapacitorHttp: { + enabled: true, + }, + }, +}; + +export default config; diff --git a/web/cypress.config.ts b/web__old/cypress.config.ts similarity index 100% rename from web/cypress.config.ts rename to web__old/cypress.config.ts diff --git a/web/cypress/e2e/example.cy.ts b/web__old/cypress/e2e/example.cy.ts similarity index 100% rename from web/cypress/e2e/example.cy.ts rename to web__old/cypress/e2e/example.cy.ts diff --git a/web/cypress/e2e/tsconfig.json b/web__old/cypress/e2e/tsconfig.json similarity index 100% rename from web/cypress/e2e/tsconfig.json rename to web__old/cypress/e2e/tsconfig.json diff --git a/web/cypress/fixtures/example.json b/web__old/cypress/fixtures/example.json similarity index 100% rename from web/cypress/fixtures/example.json rename to web__old/cypress/fixtures/example.json diff --git a/web/cypress/support/commands.ts b/web__old/cypress/support/commands.ts similarity index 100% rename from web/cypress/support/commands.ts rename to web__old/cypress/support/commands.ts diff --git a/web/cypress/support/e2e.ts b/web__old/cypress/support/e2e.ts similarity index 100% rename from web/cypress/support/e2e.ts rename to web__old/cypress/support/e2e.ts diff --git a/web__old/dockerfile b/web__old/dockerfile new file mode 100644 index 0000000..335fcb5 --- /dev/null +++ b/web__old/dockerfile @@ -0,0 +1,8 @@ +FROM nginx:alpine + +COPY dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/web/env.d.ts b/web__old/env.d.ts similarity index 100% rename from web/env.d.ts rename to web__old/env.d.ts diff --git a/web/eslint.config.mjs b/web__old/eslint.config.mjs similarity index 100% rename from web/eslint.config.mjs rename to web__old/eslint.config.mjs diff --git a/web__old/index.html b/web__old/index.html new file mode 100644 index 0000000..044c5e8 --- /dev/null +++ b/web__old/index.html @@ -0,0 +1,14 @@ + + + + + + + Task View + + + +
+ + + diff --git a/web__old/ios/.gitignore b/web__old/ios/.gitignore new file mode 100644 index 0000000..f470299 --- /dev/null +++ b/web__old/ios/.gitignore @@ -0,0 +1,13 @@ +App/build +App/Pods +App/output +App/App/public +DerivedData +xcuserdata + +# Cordova plugins for Capacitor +capacitor-cordova-ios-plugins + +# Generated Config files +App/App/capacitor.config.json +App/App/config.xml diff --git a/web__old/ios/App/App.xcodeproj/project.pbxproj b/web__old/ios/App/App.xcodeproj/project.pbxproj new file mode 100644 index 0000000..b2433eb --- /dev/null +++ b/web__old/ios/App/App.xcodeproj/project.pbxproj @@ -0,0 +1,420 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 48; + objects = { + +/* Begin PBXBuildFile section */ + 2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; }; + 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; }; + 504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; }; + 504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; }; + 504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; }; + 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; + 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; + A75AC5A184BA226F87D9DFB4 /* Pods_TaskView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33826060E12180E2E6AE45B /* Pods_TaskView.framework */; }; + AA2DDEAF2BD5EE2800137350 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = AA2DDEAE2BD5EE2800137350 /* PrivacyInfo.xcprivacy */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = ""; }; + 3DDB5660BE7C0283C0A61E6B /* Pods-TaskView.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TaskView.release.xcconfig"; path = "Pods/Target Support Files/Pods-TaskView/Pods-TaskView.release.xcconfig"; sourceTree = ""; }; + 4ED8FB2BBB1F064473F2BF99 /* Pods-TaskView.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TaskView.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TaskView/Pods-TaskView.debug.xcconfig"; sourceTree = ""; }; + 50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = ""; }; + 504EC3041FED79650016851F /* TaskView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TaskView.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 504EC30C1FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 504EC30E1FED79650016851F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; + AA2DDEAE2BD5EE2800137350 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; }; + C33826060E12180E2E6AE45B /* Pods_TaskView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TaskView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 504EC3011FED79650016851F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A75AC5A184BA226F87D9DFB4 /* Pods_TaskView.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = { + isa = PBXGroup; + children = ( + C33826060E12180E2E6AE45B /* Pods_TaskView.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 504EC2FB1FED79650016851F = { + isa = PBXGroup; + children = ( + AA2DDEAE2BD5EE2800137350 /* PrivacyInfo.xcprivacy */, + 504EC3061FED79650016851F /* App */, + 504EC3051FED79650016851F /* Products */, + 7F8756D8B27F46E3366F6CEA /* Pods */, + 27E2DDA53C4D2A4D1A88CE4A /* Frameworks */, + ); + sourceTree = ""; + }; + 504EC3051FED79650016851F /* Products */ = { + isa = PBXGroup; + children = ( + 504EC3041FED79650016851F /* TaskView.app */, + ); + name = Products; + sourceTree = ""; + }; + 504EC3061FED79650016851F /* App */ = { + isa = PBXGroup; + children = ( + 50379B222058CBB4000EE86E /* capacitor.config.json */, + 504EC3071FED79650016851F /* AppDelegate.swift */, + 504EC30B1FED79650016851F /* Main.storyboard */, + 504EC30E1FED79650016851F /* Assets.xcassets */, + 504EC3101FED79650016851F /* LaunchScreen.storyboard */, + 504EC3131FED79650016851F /* Info.plist */, + 2FAD9762203C412B000D30F8 /* config.xml */, + 50B271D01FEDC1A000F3C39B /* public */, + ); + path = App; + sourceTree = ""; + }; + 7F8756D8B27F46E3366F6CEA /* Pods */ = { + isa = PBXGroup; + children = ( + FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */, + AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */, + 4ED8FB2BBB1F064473F2BF99 /* Pods-TaskView.debug.xcconfig */, + 3DDB5660BE7C0283C0A61E6B /* Pods-TaskView.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 504EC3031FED79650016851F /* TaskView */ = { + isa = PBXNativeTarget; + buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "TaskView" */; + buildPhases = ( + 6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */, + 504EC3001FED79650016851F /* Sources */, + 504EC3011FED79650016851F /* Frameworks */, + 504EC3021FED79650016851F /* Resources */, + 9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TaskView; + productName = App; + productReference = 504EC3041FED79650016851F /* TaskView.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 504EC2FC1FED79650016851F /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 920; + LastUpgradeCheck = 920; + TargetAttributes = { + 504EC3031FED79650016851F = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 504EC2FB1FED79650016851F; + productRefGroup = 504EC3051FED79650016851F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 504EC3031FED79650016851F /* TaskView */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 504EC3021FED79650016851F /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */, + AA2DDEAF2BD5EE2800137350 /* PrivacyInfo.xcprivacy in Resources */, + 50B271D11FEDC1A000F3C39B /* public in Resources */, + 504EC30F1FED79650016851F /* Assets.xcassets in Resources */, + 50379B232058CBB4000EE86E /* capacitor.config.json in Resources */, + 504EC30D1FED79650016851F /* Main.storyboard in Resources */, + 2FAD9763203C412B000D30F8 /* config.xml in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-TaskView-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-TaskView/Pods-TaskView-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 504EC3001FED79650016851F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 504EC3081FED79650016851F /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 504EC30B1FED79650016851F /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 504EC30C1FED79650016851F /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 504EC3101FED79650016851F /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 504EC3111FED79650016851F /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 504EC3141FED79650016851F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 504EC3151FED79650016851F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 504EC3171FED79650016851F /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4ED8FB2BBB1F064473F2BF99 /* Pods-TaskView.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.19.7; + DEVELOPMENT_TEAM = H2W2SG48JT; + INFOPLIST_FILE = App/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = TaskView; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.19.7; + OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; + PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 504EC3181FED79650016851F /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3DDB5660BE7C0283C0A61E6B /* Pods-TaskView.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1.19.7; + DEVELOPMENT_TEAM = H2W2SG48JT; + INFOPLIST_FILE = App/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = TaskView; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.19.7; + PRODUCT_BUNDLE_IDENTIFIER = com.handscream.taskview.app; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 504EC3141FED79650016851F /* Debug */, + 504EC3151FED79650016851F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "TaskView" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 504EC3171FED79650016851F /* Debug */, + 504EC3181FED79650016851F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 504EC2FC1FED79650016851F /* Project object */; +} diff --git a/web/ios/App/App.xcworkspace/contents.xcworkspacedata b/web__old/ios/App/App.xcworkspace/contents.xcworkspacedata similarity index 100% rename from web/ios/App/App.xcworkspace/contents.xcworkspacedata rename to web__old/ios/App/App.xcworkspace/contents.xcworkspacedata diff --git a/web/ios/App/App.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/web__old/ios/App/App.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from web/ios/App/App.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to web__old/ios/App/App.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/web__old/ios/App/App/AppDelegate.swift b/web__old/ios/App/App/AppDelegate.swift new file mode 100644 index 0000000..c3cd83b --- /dev/null +++ b/web__old/ios/App/App/AppDelegate.swift @@ -0,0 +1,49 @@ +import UIKit +import Capacitor + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + // Called when the app was launched with a url. Feel free to add additional processing here, + // but if you want the App API to support tracking app url opens, make sure to keep this call + return ApplicationDelegateProxy.shared.application(app, open: url, options: options) + } + + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { + // Called when the app was launched with an activity, including Universal Links. + // Feel free to add additional processing here, but if you want the App API to support + // tracking app url opens, make sure to keep this call + return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler) + } + +} diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/100.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/100.png new file mode 100644 index 0000000..a64c2ac Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/100.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/102.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/102.png new file mode 100644 index 0000000..4dab9db Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/102.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/1024.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..1b20ea6 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/114.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..1d90783 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/120.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..232f07c Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/128.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100644 index 0000000..0df96db Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/144.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/144.png new file mode 100644 index 0000000..f7604a3 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/144.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/152.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/152.png new file mode 100644 index 0000000..6f41702 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/152.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/16.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 0000000..dcf9294 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/167.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/167.png new file mode 100644 index 0000000..0a7b3ec Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/167.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/172.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/172.png new file mode 100644 index 0000000..94934ae Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/172.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/180.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..8f37f60 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/196.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/196.png new file mode 100644 index 0000000..f61c70a Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/196.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/20.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/20.png new file mode 100644 index 0000000..8af2f54 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/20.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/216.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/216.png new file mode 100644 index 0000000..b56f59c Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/216.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/256.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100644 index 0000000..ce71c7d Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/29.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..c0ac23d Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/32.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 0000000..5a52bd9 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/40.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..c020316 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/48.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/48.png new file mode 100644 index 0000000..07c0b20 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/48.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/50.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/50.png new file mode 100644 index 0000000..892bf44 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/50.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/512.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 0000000..4bb424c Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/55.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/55.png new file mode 100644 index 0000000..bada042 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/55.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/57.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..e4c936f Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/58.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..11d32f6 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/60.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..d22f668 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/64.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 0000000..527c77c Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/66.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/66.png new file mode 100644 index 0000000..9edf5e3 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/66.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/72.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/72.png new file mode 100644 index 0000000..03d4b4c Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/72.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/76.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/76.png new file mode 100644 index 0000000..365cf94 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/76.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/80.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..ab07d80 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/87.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..d68a09d Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/88.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/88.png new file mode 100644 index 0000000..cde14fa Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/88.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/92.png b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/92.png new file mode 100644 index 0000000..22d3953 Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/92.png differ diff --git a/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2e5c79d --- /dev/null +++ b/web__old/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,347 @@ +{ + "images" : [ + { + "filename" : "40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "57.png", + "idiom" : "iphone", + "scale" : "1x", + "size" : "57x57" + }, + { + "filename" : "114.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "57x57" + }, + { + "filename" : "120.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "20.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "29.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "filename" : "58.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "40.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "filename" : "80.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "50.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "50x50" + }, + { + "filename" : "100.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "50x50" + }, + { + "filename" : "72.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "72x72" + }, + { + "filename" : "144.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "72x72" + }, + { + "filename" : "76.png", + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "filename" : "152.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "filename" : "167.png", + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "filename" : "1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + }, + { + "filename" : "16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "64.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "1024.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + }, + { + "filename" : "48.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "24x24", + "subtype" : "38mm" + }, + { + "filename" : "55.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "27.5x27.5", + "subtype" : "42mm" + }, + { + "filename" : "58.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "87.png", + "idiom" : "watch", + "role" : "companionSettings", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "66.png", + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "33x33", + "subtype" : "45mm" + }, + { + "filename" : "80.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "40x40", + "subtype" : "38mm" + }, + { + "filename" : "88.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "44x44", + "subtype" : "40mm" + }, + { + "filename" : "92.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "46x46", + "subtype" : "41mm" + }, + { + "filename" : "100.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "50x50", + "subtype" : "44mm" + }, + { + "filename" : "102.png", + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "51x51", + "subtype" : "45mm" + }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "54x54", + "subtype" : "49mm" + }, + { + "filename" : "172.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "86x86", + "subtype" : "38mm" + }, + { + "filename" : "196.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "98x98", + "subtype" : "42mm" + }, + { + "filename" : "216.png", + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "108x108", + "subtype" : "44mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "117x117", + "subtype" : "45mm" + }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "129x129", + "subtype" : "49mm" + }, + { + "filename" : "1024.png", + "idiom" : "watch-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/web__old/ios/App/App/Assets.xcassets/Contents.json b/web__old/ios/App/App/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/web__old/ios/App/App/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/web__old/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json b/web__old/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json new file mode 100644 index 0000000..9f76111 --- /dev/null +++ b/web__old/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "logo-1024-a.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "logo-1024-a.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "logo-1024-a.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/web__old/ios/App/App/Assets.xcassets/Splash.imageset/logo-1024-a.png b/web__old/ios/App/App/Assets.xcassets/Splash.imageset/logo-1024-a.png new file mode 100644 index 0000000..9328acb Binary files /dev/null and b/web__old/ios/App/App/Assets.xcassets/Splash.imageset/logo-1024-a.png differ diff --git a/web__old/ios/App/App/Base.lproj/LaunchScreen.storyboard b/web__old/ios/App/App/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..3ed14eb --- /dev/null +++ b/web__old/ios/App/App/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web__old/ios/App/App/Base.lproj/Main.storyboard b/web__old/ios/App/App/Base.lproj/Main.storyboard new file mode 100644 index 0000000..b44df7b --- /dev/null +++ b/web__old/ios/App/App/Base.lproj/Main.storyboard @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/web/ios/App/Podfile b/web__old/ios/App/Podfile similarity index 100% rename from web/ios/App/Podfile rename to web__old/ios/App/Podfile diff --git a/web/ios/App/Podfile.lock b/web__old/ios/App/Podfile.lock similarity index 100% rename from web/ios/App/Podfile.lock rename to web__old/ios/App/Podfile.lock diff --git a/web/ios/App/PrivacyInfo.xcprivacy b/web__old/ios/App/PrivacyInfo.xcprivacy similarity index 100% rename from web/ios/App/PrivacyInfo.xcprivacy rename to web__old/ios/App/PrivacyInfo.xcprivacy diff --git a/web__old/nginx.conf b/web__old/nginx.conf new file mode 100644 index 0000000..2145dfa --- /dev/null +++ b/web__old/nginx.conf @@ -0,0 +1,35 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Don't cache index.html + location = /index.html { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + } + + error_page 404 /index.html; +} + diff --git a/web__old/package.json b/web__old/package.json new file mode 100644 index 0000000..89b3a63 --- /dev/null +++ b/web__old/package.json @@ -0,0 +1,99 @@ +{ + "name": "taskview-ce-webapp", + "version": "1.19.6", + "private": true, + "scripts": { + "dev": "vite", + "build:dev": "BRANCH_MODE=dev pnpm run build", + "build": "run-p type-check build-only", + "preview": "vite preview", + "test:unit": "vitest --environment jsdom --root src/", + "test:e2e": "start-server-and-test preview :4173 'cypress run --e2e'", + "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress open --e2e'", + "build-only": "vite build", + "type-check": "vue-tsc --noEmit --project tsconfig.json", + "lint": "eslint . --config eslint.config.mjs --fix", + "lint:biome": "biome lint .", + "format": "biome format --write .", + "check": "biome check --write ." + }, + "dependencies": { + "@capacitor/android": "^7.0.0", + "@capacitor/app": "7.0.1", + "@capacitor/browser": "^7.0.3", + "@capacitor/core": "^7.0.0", + "@capacitor/device": "7.0.1", + "@capacitor/ios": "^7.0.0", + "@capacitor/preferences": "7.0.1", + "@capacitor/splash-screen": "^7.0.1", + "@capgo/capacitor-updater": "^7.7.7", + "@dagrejs/dagre": "^1.1.5", + "@tiptap/extension-color": "^3.0.0", + "@tiptap/extension-link": "^3.0.0", + "@tiptap/extension-list-item": "^3.0.0", + "@tiptap/extension-placeholder": "^3.0.0", + "@tiptap/extension-text-style": "^3.0.0", + "@tiptap/pm": "^3.0.0", + "@tiptap/starter-kit": "^3.0.0", + "@tiptap/vue-3": "^3.0.0", + "@typescript-eslint/eslint-plugin": "^8.24.1", + "@typescript-eslint/parser": "^8.24.1", + "@vue-flow/background": "^1.3.2", + "@vue-flow/controls": "^1.1.3", + "@vue-flow/core": "^1.46.4", + "@vue-flow/minimap": "^1.5.4", + "@vue-flow/node-resizer": "^1.5.0", + "@vue-flow/node-toolbar": "^1.1.1", + "@vue/eslint-config-typescript": "^14.4.0", + "@vueuse/components": "^10.9.0", + "@vueuse/core": "^10.9.0", + "echarts": "^5.5.1", + "globals": "^15.15.0", + "motion-v": "^1.1.0-alpha.1", + "pinia": "^2.0.28", + "taskview-api": "workspace:^", + "vue": "^3.2.45", + "vue-router": "4.3.2", + "vuedraggable": "^4.1.0" + }, + "devDependencies": { + "@biomejs/biome": "^2.3.8", + "@capacitor/assets": "^3.0.5", + "@capacitor/cli": "^7.0.0", + "@date-io/date-fns": "^3.2.0", + "@mdi/js": "^7.1.96", + "@rushstack/eslint-patch": "^1.1.4", + "@types/jsdom": "^20.0.1", + "@types/node": "^18.19.119", + "@types/qs": "^6.9.7", + "@vitejs/plugin-vue": "^4.0.0", + "@vitejs/plugin-vue-jsx": "^3.0.0", + "@vue/eslint-config-prettier": "^7.0.0", + "@vue/test-utils": "^2.2.6", + "@vue/tsconfig": "0.7.0", + "autoprefixer": "^10.4.20", + "axios": "^1.2.3", + "check-password-strength": "^2.0.7", + "eslint": "9.20.1", + "eslint-plugin-vue": "^9.3.0", + "jsdom": "^20.0.3", + "npm-run-all": "^4.1.5", + "postcss": "^8.4.49", + "prettier": "^2.7.1", + "qs": "^6.11.0", + "sass": "^1.69.7", + "sass-loader": "^13.3.3", + "start-server-and-test": "^1.15.2", + "tailwindcss": "^3.4.16", + "typescript": "^5.7.3", + "typescript-eslint": "^8.22.0", + "vite": "^4.0.0", + "vitest": "^0.25.6", + "vue-i18n": "11.1.10", + "vue-tsc": "3.0.3", + "vuetify": "3.7.9" + }, + "engines": { + "node": ">=24 <25" + } +} \ No newline at end of file diff --git a/web/pnpm-lock.yaml b/web__old/pnpm-lock.yaml similarity index 100% rename from web/pnpm-lock.yaml rename to web__old/pnpm-lock.yaml diff --git a/web/postcss.config.js b/web__old/postcss.config.js similarity index 100% rename from web/postcss.config.js rename to web__old/postcss.config.js diff --git a/web__old/public/tv-logo.svg b/web__old/public/tv-logo.svg new file mode 100644 index 0000000..8d483ab --- /dev/null +++ b/web__old/public/tv-logo.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web__old/src/App.vue b/web__old/src/App.vue new file mode 100644 index 0000000..cba2be0 --- /dev/null +++ b/web__old/src/App.vue @@ -0,0 +1,49 @@ + + + diff --git a/web/src/assets/all-styles/base-screen.scss b/web__old/src/assets/all-styles/base-screen.scss similarity index 100% rename from web/src/assets/all-styles/base-screen.scss rename to web__old/src/assets/all-styles/base-screen.scss diff --git a/web/src/assets/all-styles/font.scss b/web__old/src/assets/all-styles/font.scss similarity index 100% rename from web/src/assets/all-styles/font.scss rename to web__old/src/assets/all-styles/font.scss diff --git a/web/src/assets/all-styles/height.scss b/web__old/src/assets/all-styles/height.scss similarity index 100% rename from web/src/assets/all-styles/height.scss rename to web__old/src/assets/all-styles/height.scss diff --git a/web/src/assets/all-styles/icons.scss b/web__old/src/assets/all-styles/icons.scss similarity index 100% rename from web/src/assets/all-styles/icons.scss rename to web__old/src/assets/all-styles/icons.scss diff --git a/web/src/assets/all-styles/margins.scss b/web__old/src/assets/all-styles/margins.scss similarity index 100% rename from web/src/assets/all-styles/margins.scss rename to web__old/src/assets/all-styles/margins.scss diff --git a/web/src/assets/all-styles/radius.scss b/web__old/src/assets/all-styles/radius.scss similarity index 100% rename from web/src/assets/all-styles/radius.scss rename to web__old/src/assets/all-styles/radius.scss diff --git a/web/src/assets/all-styles/text.scss b/web__old/src/assets/all-styles/text.scss similarity index 100% rename from web/src/assets/all-styles/text.scss rename to web__old/src/assets/all-styles/text.scss diff --git a/web/src/assets/app.scss b/web__old/src/assets/app.scss similarity index 100% rename from web/src/assets/app.scss rename to web__old/src/assets/app.scss diff --git a/web/src/assets/base.css b/web__old/src/assets/base.css similarity index 100% rename from web/src/assets/base.css rename to web__old/src/assets/base.css diff --git a/web/src/assets/components/CollaborationDialogDsk.scss b/web__old/src/assets/components/CollaborationDialogDsk.scss similarity index 100% rename from web/src/assets/components/CollaborationDialogDsk.scss rename to web__old/src/assets/components/CollaborationDialogDsk.scss diff --git a/web/src/assets/components/DesktopScreen.scss b/web__old/src/assets/components/DesktopScreen.scss similarity index 100% rename from web/src/assets/components/DesktopScreen.scss rename to web__old/src/assets/components/DesktopScreen.scss diff --git a/web/src/assets/components/MTaskNote.scss b/web__old/src/assets/components/MTaskNote.scss similarity index 100% rename from web/src/assets/components/MTaskNote.scss rename to web__old/src/assets/components/MTaskNote.scss diff --git a/web/src/assets/components/MainTasks.scss b/web__old/src/assets/components/MainTasks.scss similarity index 100% rename from web/src/assets/components/MainTasks.scss rename to web__old/src/assets/components/MainTasks.scss diff --git a/web/src/assets/components/TaskAssignUser.scss b/web__old/src/assets/components/TaskAssignUser.scss similarity index 100% rename from web/src/assets/components/TaskAssignUser.scss rename to web__old/src/assets/components/TaskAssignUser.scss diff --git a/web/src/assets/components/TaskDialog.scss b/web__old/src/assets/components/TaskDialog.scss similarity index 100% rename from web/src/assets/components/TaskDialog.scss rename to web__old/src/assets/components/TaskDialog.scss diff --git a/web/src/assets/components/TaskEditItem.scss b/web__old/src/assets/components/TaskEditItem.scss similarity index 100% rename from web/src/assets/components/TaskEditItem.scss rename to web__old/src/assets/components/TaskEditItem.scss diff --git a/web/src/assets/components/TaskHistory.scss b/web__old/src/assets/components/TaskHistory.scss similarity index 100% rename from web/src/assets/components/TaskHistory.scss rename to web__old/src/assets/components/TaskHistory.scss diff --git a/web/src/assets/components/TaskItemUi.scss b/web__old/src/assets/components/TaskItemUi.scss similarity index 100% rename from web/src/assets/components/TaskItemUi.scss rename to web__old/src/assets/components/TaskItemUi.scss diff --git a/web/src/assets/components/TaskPriority.scss b/web__old/src/assets/components/TaskPriority.scss similarity index 100% rename from web/src/assets/components/TaskPriority.scss rename to web__old/src/assets/components/TaskPriority.scss diff --git a/web/src/assets/components/TaskSubtaskItem.scss b/web__old/src/assets/components/TaskSubtaskItem.scss similarity index 100% rename from web/src/assets/components/TaskSubtaskItem.scss rename to web__old/src/assets/components/TaskSubtaskItem.scss diff --git a/web/src/assets/components/TaskSubtasksSection.scss b/web__old/src/assets/components/TaskSubtasksSection.scss similarity index 100% rename from web/src/assets/components/TaskSubtasksSection.scss rename to web__old/src/assets/components/TaskSubtasksSection.scss diff --git a/web/src/assets/components/TvNawDrawer.scss b/web__old/src/assets/components/TvNawDrawer.scss similarity index 100% rename from web/src/assets/components/TvNawDrawer.scss rename to web__old/src/assets/components/TvNawDrawer.scss diff --git a/web/src/assets/components/index.scss b/web__old/src/assets/components/index.scss similarity index 100% rename from web/src/assets/components/index.scss rename to web__old/src/assets/components/index.scss diff --git a/web/src/assets/fonts/Open_Sans/OFL.txt b/web__old/src/assets/fonts/Open_Sans/OFL.txt similarity index 100% rename from web/src/assets/fonts/Open_Sans/OFL.txt rename to web__old/src/assets/fonts/Open_Sans/OFL.txt diff --git a/web/src/assets/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf b/web__old/src/assets/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf rename to web__old/src/assets/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf diff --git a/web/src/assets/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf b/web__old/src/assets/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf rename to web__old/src/assets/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf diff --git a/web/src/assets/fonts/Open_Sans/README.txt b/web__old/src/assets/fonts/Open_Sans/README.txt similarity index 100% rename from web/src/assets/fonts/Open_Sans/README.txt rename to web__old/src/assets/fonts/Open_Sans/README.txt diff --git a/web/src/assets/fonts/Open_Sans/open-sans.css b/web__old/src/assets/fonts/Open_Sans/open-sans.css similarity index 100% rename from web/src/assets/fonts/Open_Sans/open-sans.css rename to web__old/src/assets/fonts/Open_Sans/open-sans.css diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-Bold.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-Bold.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-Bold.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-Bold.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-BoldItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-BoldItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-BoldItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-BoldItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBold.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBold.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBold.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBold.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBoldItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBoldItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBoldItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBoldItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-Italic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-Italic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-Italic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-Italic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-Light.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-Light.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-Light.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-Light.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-LightItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-LightItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-LightItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-LightItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-Medium.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-Medium.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-Medium.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-Medium.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-MediumItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-MediumItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-MediumItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-MediumItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-Regular.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-Regular.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-Regular.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-Regular.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-SemiBold.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-SemiBold.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-SemiBold.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-SemiBold.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans-SemiBoldItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans-SemiBoldItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans-SemiBoldItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans-SemiBoldItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Bold.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Bold.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Bold.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Bold.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-BoldItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-BoldItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-BoldItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-BoldItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBold.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBold.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBold.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBold.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Italic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Italic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Italic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Italic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Light.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Light.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Light.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Light.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-LightItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-LightItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-LightItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-LightItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Medium.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Medium.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Medium.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Medium.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-MediumItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-MediumItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-MediumItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-MediumItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Regular.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Regular.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Regular.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Regular.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBold.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBold.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBold.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBold.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBoldItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBoldItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBoldItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBoldItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Bold.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Bold.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Bold.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Bold.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-BoldItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-BoldItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-BoldItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-BoldItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBold.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBold.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBold.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBold.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Italic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Italic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Italic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Italic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Light.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Light.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Light.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Light.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-LightItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-LightItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-LightItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-LightItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Medium.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Medium.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Medium.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Medium.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-MediumItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-MediumItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-MediumItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-MediumItalic.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Regular.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Regular.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Regular.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Regular.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBold.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBold.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBold.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBold.ttf diff --git a/web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf b/web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf similarity index 100% rename from web/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf rename to web__old/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf diff --git a/web/src/assets/override.scss b/web__old/src/assets/override.scss similarity index 100% rename from web/src/assets/override.scss rename to web__old/src/assets/override.scss diff --git a/web/src/assets/reusable.scss b/web__old/src/assets/reusable.scss similarity index 100% rename from web/src/assets/reusable.scss rename to web__old/src/assets/reusable.scss diff --git a/web/src/assets/variables.scss b/web__old/src/assets/variables.scss similarity index 100% rename from web/src/assets/variables.scss rename to web__old/src/assets/variables.scss diff --git a/web/src/components/AppAlert.vue b/web__old/src/components/AppAlert.vue similarity index 100% rename from web/src/components/AppAlert.vue rename to web__old/src/components/AppAlert.vue diff --git a/web/src/components/AppDateTimePicker.vue b/web__old/src/components/AppDateTimePicker.vue similarity index 100% rename from web/src/components/AppDateTimePicker.vue rename to web__old/src/components/AppDateTimePicker.vue diff --git a/web/src/components/AppLang/AppLang.ts b/web__old/src/components/AppLang/AppLang.ts similarity index 100% rename from web/src/components/AppLang/AppLang.ts rename to web__old/src/components/AppLang/AppLang.ts diff --git a/web/src/components/AppLang/AppLang.vue b/web__old/src/components/AppLang/AppLang.vue similarity index 100% rename from web/src/components/AppLang/AppLang.vue rename to web__old/src/components/AppLang/AppLang.vue diff --git a/web/src/components/AppLang/index.ts b/web__old/src/components/AppLang/index.ts similarity index 100% rename from web/src/components/AppLang/index.ts rename to web__old/src/components/AppLang/index.ts diff --git a/web/src/components/AppLogo/AppLogo.vue b/web__old/src/components/AppLogo/AppLogo.vue similarity index 100% rename from web/src/components/AppLogo/AppLogo.vue rename to web__old/src/components/AppLogo/AppLogo.vue diff --git a/web/src/components/AppLogo/index.ts b/web__old/src/components/AppLogo/index.ts similarity index 100% rename from web/src/components/AppLogo/index.ts rename to web__old/src/components/AppLogo/index.ts diff --git a/web/src/components/AppLogo/tv-grouped-logo.svg b/web__old/src/components/AppLogo/tv-grouped-logo.svg similarity index 100% rename from web/src/components/AppLogo/tv-grouped-logo.svg rename to web__old/src/components/AppLogo/tv-grouped-logo.svg diff --git a/web/src/components/AppLogo/tv-logo-circle.svg b/web__old/src/components/AppLogo/tv-logo-circle.svg similarity index 100% rename from web/src/components/AppLogo/tv-logo-circle.svg rename to web__old/src/components/AppLogo/tv-logo-circle.svg diff --git a/web/src/components/AppLogo/tv-logo-text.svg b/web__old/src/components/AppLogo/tv-logo-text.svg similarity index 100% rename from web/src/components/AppLogo/tv-logo-text.svg rename to web__old/src/components/AppLogo/tv-logo-text.svg diff --git a/web/src/components/Atoms/ABtn/ABtn.vue b/web__old/src/components/Atoms/ABtn/ABtn.vue similarity index 100% rename from web/src/components/Atoms/ABtn/ABtn.vue rename to web__old/src/components/Atoms/ABtn/ABtn.vue diff --git a/web/src/components/Atoms/ABtn/index.ts b/web__old/src/components/Atoms/ABtn/index.ts similarity index 100% rename from web/src/components/Atoms/ABtn/index.ts rename to web__old/src/components/Atoms/ABtn/index.ts diff --git a/web/src/components/Atoms/ACheckbox/ACheckbox.vue b/web__old/src/components/Atoms/ACheckbox/ACheckbox.vue similarity index 100% rename from web/src/components/Atoms/ACheckbox/ACheckbox.vue rename to web__old/src/components/Atoms/ACheckbox/ACheckbox.vue diff --git a/web/src/components/Atoms/ACheckbox/index.ts b/web__old/src/components/Atoms/ACheckbox/index.ts similarity index 100% rename from web/src/components/Atoms/ACheckbox/index.ts rename to web__old/src/components/Atoms/ACheckbox/index.ts diff --git a/web/src/components/Atoms/ATextField/ATextField.vue b/web__old/src/components/Atoms/ATextField/ATextField.vue similarity index 100% rename from web/src/components/Atoms/ATextField/ATextField.vue rename to web__old/src/components/Atoms/ATextField/ATextField.vue diff --git a/web/src/components/Atoms/ATextField/index.ts b/web__old/src/components/Atoms/ATextField/index.ts similarity index 100% rename from web/src/components/Atoms/ATextField/index.ts rename to web__old/src/components/Atoms/ATextField/index.ts diff --git a/web/src/components/Atoms/NoTasksAlert.vue b/web__old/src/components/Atoms/NoTasksAlert.vue similarity index 100% rename from web/src/components/Atoms/NoTasksAlert.vue rename to web__old/src/components/Atoms/NoTasksAlert.vue diff --git a/web/src/components/Atoms/ToggleCompletedTasks.vue b/web__old/src/components/Atoms/ToggleCompletedTasks.vue similarity index 100% rename from web/src/components/Atoms/ToggleCompletedTasks.vue rename to web__old/src/components/Atoms/ToggleCompletedTasks.vue diff --git a/web/src/components/Authentication/AppCredentialsForm/AppCredentialsForm.ts b/web__old/src/components/Authentication/AppCredentialsForm/AppCredentialsForm.ts similarity index 100% rename from web/src/components/Authentication/AppCredentialsForm/AppCredentialsForm.ts rename to web__old/src/components/Authentication/AppCredentialsForm/AppCredentialsForm.ts diff --git a/web/src/components/Authentication/AppCredentialsForm/AppCredentialsForm.vue b/web__old/src/components/Authentication/AppCredentialsForm/AppCredentialsForm.vue similarity index 100% rename from web/src/components/Authentication/AppCredentialsForm/AppCredentialsForm.vue rename to web__old/src/components/Authentication/AppCredentialsForm/AppCredentialsForm.vue diff --git a/web/src/components/Authentication/AppCredentialsForm/index.ts b/web__old/src/components/Authentication/AppCredentialsForm/index.ts similarity index 100% rename from web/src/components/Authentication/AppCredentialsForm/index.ts rename to web__old/src/components/Authentication/AppCredentialsForm/index.ts diff --git a/web/src/components/Authentication/AppCredentialsForm/tabs.ts b/web__old/src/components/Authentication/AppCredentialsForm/tabs.ts similarity index 100% rename from web/src/components/Authentication/AppCredentialsForm/tabs.ts rename to web__old/src/components/Authentication/AppCredentialsForm/tabs.ts diff --git a/web/src/components/Authentication/AuthByApple.vue b/web__old/src/components/Authentication/AuthByApple.vue similarity index 100% rename from web/src/components/Authentication/AuthByApple.vue rename to web__old/src/components/Authentication/AuthByApple.vue diff --git a/web/src/components/Authentication/AuthByGithub.vue b/web__old/src/components/Authentication/AuthByGithub.vue similarity index 100% rename from web/src/components/Authentication/AuthByGithub.vue rename to web__old/src/components/Authentication/AuthByGithub.vue diff --git a/web/src/components/Authentication/AuthByGoogle.vue b/web__old/src/components/Authentication/AuthByGoogle.vue similarity index 100% rename from web/src/components/Authentication/AuthByGoogle.vue rename to web__old/src/components/Authentication/AuthByGoogle.vue diff --git a/web/src/components/Authentication/ExternalAuthBaseBtn.vue b/web__old/src/components/Authentication/ExternalAuthBaseBtn.vue similarity index 100% rename from web/src/components/Authentication/ExternalAuthBaseBtn.vue rename to web__old/src/components/Authentication/ExternalAuthBaseBtn.vue diff --git a/web/src/components/Authentication/LoginByCode/LoginByCode.vue b/web__old/src/components/Authentication/LoginByCode/LoginByCode.vue similarity index 100% rename from web/src/components/Authentication/LoginByCode/LoginByCode.vue rename to web__old/src/components/Authentication/LoginByCode/LoginByCode.vue diff --git a/web/src/components/Authentication/LoginForm/LoginForm.ts b/web__old/src/components/Authentication/LoginForm/LoginForm.ts similarity index 100% rename from web/src/components/Authentication/LoginForm/LoginForm.ts rename to web__old/src/components/Authentication/LoginForm/LoginForm.ts diff --git a/web/src/components/Authentication/LoginForm/LoginForm.vue b/web__old/src/components/Authentication/LoginForm/LoginForm.vue similarity index 100% rename from web/src/components/Authentication/LoginForm/LoginForm.vue rename to web__old/src/components/Authentication/LoginForm/LoginForm.vue diff --git a/web/src/components/Authentication/LoginForm/Types.ts b/web__old/src/components/Authentication/LoginForm/Types.ts similarity index 100% rename from web/src/components/Authentication/LoginForm/Types.ts rename to web__old/src/components/Authentication/LoginForm/Types.ts diff --git a/web/src/components/Authentication/LoginForm/index.ts b/web__old/src/components/Authentication/LoginForm/index.ts similarity index 100% rename from web/src/components/Authentication/LoginForm/index.ts rename to web__old/src/components/Authentication/LoginForm/index.ts diff --git a/web/src/components/Authentication/Logout/Logout.ts b/web__old/src/components/Authentication/Logout/Logout.ts similarity index 100% rename from web/src/components/Authentication/Logout/Logout.ts rename to web__old/src/components/Authentication/Logout/Logout.ts diff --git a/web/src/components/Authentication/Logout/Logout.vue b/web__old/src/components/Authentication/Logout/Logout.vue similarity index 100% rename from web/src/components/Authentication/Logout/Logout.vue rename to web__old/src/components/Authentication/Logout/Logout.vue diff --git a/web/src/components/Authentication/Logout/index.ts b/web__old/src/components/Authentication/Logout/index.ts similarity index 100% rename from web/src/components/Authentication/Logout/index.ts rename to web__old/src/components/Authentication/Logout/index.ts diff --git a/web/src/components/Authentication/PasswordRecovery/PasswordRecovery.ts b/web__old/src/components/Authentication/PasswordRecovery/PasswordRecovery.ts similarity index 100% rename from web/src/components/Authentication/PasswordRecovery/PasswordRecovery.ts rename to web__old/src/components/Authentication/PasswordRecovery/PasswordRecovery.ts diff --git a/web/src/components/Authentication/PasswordRecovery/PasswordRecovery.vue b/web__old/src/components/Authentication/PasswordRecovery/PasswordRecovery.vue similarity index 100% rename from web/src/components/Authentication/PasswordRecovery/PasswordRecovery.vue rename to web__old/src/components/Authentication/PasswordRecovery/PasswordRecovery.vue diff --git a/web/src/components/Authentication/PasswordRecovery/index.ts b/web__old/src/components/Authentication/PasswordRecovery/index.ts similarity index 100% rename from web/src/components/Authentication/PasswordRecovery/index.ts rename to web__old/src/components/Authentication/PasswordRecovery/index.ts diff --git a/web/src/components/Authentication/RegistrationForm/RegistrationForm.ts b/web__old/src/components/Authentication/RegistrationForm/RegistrationForm.ts similarity index 100% rename from web/src/components/Authentication/RegistrationForm/RegistrationForm.ts rename to web__old/src/components/Authentication/RegistrationForm/RegistrationForm.ts diff --git a/web/src/components/Authentication/RegistrationForm/RegistrationForm.vue b/web__old/src/components/Authentication/RegistrationForm/RegistrationForm.vue similarity index 100% rename from web/src/components/Authentication/RegistrationForm/RegistrationForm.vue rename to web__old/src/components/Authentication/RegistrationForm/RegistrationForm.vue diff --git a/web/src/components/Authentication/RegistrationForm/index.ts b/web__old/src/components/Authentication/RegistrationForm/index.ts similarity index 100% rename from web/src/components/Authentication/RegistrationForm/index.ts rename to web__old/src/components/Authentication/RegistrationForm/index.ts diff --git a/web/src/components/Authentication/ResetPassword/ResetPassword.ts b/web__old/src/components/Authentication/ResetPassword/ResetPassword.ts similarity index 100% rename from web/src/components/Authentication/ResetPassword/ResetPassword.ts rename to web__old/src/components/Authentication/ResetPassword/ResetPassword.ts diff --git a/web/src/components/Authentication/ResetPassword/ResetPassword.vue b/web__old/src/components/Authentication/ResetPassword/ResetPassword.vue similarity index 100% rename from web/src/components/Authentication/ResetPassword/ResetPassword.vue rename to web__old/src/components/Authentication/ResetPassword/ResetPassword.vue diff --git a/web/src/components/Authentication/ResetPassword/index.ts b/web__old/src/components/Authentication/ResetPassword/index.ts similarity index 100% rename from web/src/components/Authentication/ResetPassword/index.ts rename to web__old/src/components/Authentication/ResetPassword/index.ts diff --git a/web/src/components/Collaboration/CollaborationDialog.vue b/web__old/src/components/Collaboration/CollaborationDialog.vue similarity index 100% rename from web/src/components/Collaboration/CollaborationDialog.vue rename to web__old/src/components/Collaboration/CollaborationDialog.vue diff --git a/web/src/components/Collaboration/CollaborationDialog/CollaborationDialogDsk.vue b/web__old/src/components/Collaboration/CollaborationDialog/CollaborationDialogDsk.vue similarity index 100% rename from web/src/components/Collaboration/CollaborationDialog/CollaborationDialogDsk.vue rename to web__old/src/components/Collaboration/CollaborationDialog/CollaborationDialogDsk.vue diff --git a/web/src/components/Collaboration/CollaborationDialog/CollaborationDialogMob.vue b/web__old/src/components/Collaboration/CollaborationDialog/CollaborationDialogMob.vue similarity index 100% rename from web/src/components/Collaboration/CollaborationDialog/CollaborationDialogMob.vue rename to web__old/src/components/Collaboration/CollaborationDialog/CollaborationDialogMob.vue diff --git a/web/src/components/Collaboration/InviteUserToProject.vue b/web__old/src/components/Collaboration/InviteUserToProject.vue similarity index 100% rename from web/src/components/Collaboration/InviteUserToProject.vue rename to web__old/src/components/Collaboration/InviteUserToProject.vue diff --git a/web/src/components/Collaboration/InvitedUsersIntoProject.vue b/web__old/src/components/Collaboration/InvitedUsersIntoProject.vue similarity index 100% rename from web/src/components/Collaboration/InvitedUsersIntoProject.vue rename to web__old/src/components/Collaboration/InvitedUsersIntoProject.vue diff --git a/web/src/components/Collaboration/InvitedUsersItem.vue b/web__old/src/components/Collaboration/InvitedUsersItem.vue similarity index 100% rename from web/src/components/Collaboration/InvitedUsersItem.vue rename to web__old/src/components/Collaboration/InvitedUsersItem.vue diff --git a/web/src/components/Collaboration/Roles/AddCollaborationRole.vue b/web__old/src/components/Collaboration/Roles/AddCollaborationRole.vue similarity index 100% rename from web/src/components/Collaboration/Roles/AddCollaborationRole.vue rename to web__old/src/components/Collaboration/Roles/AddCollaborationRole.vue diff --git a/web/src/components/Collaboration/Roles/ListCollaborationRoles.vue b/web__old/src/components/Collaboration/Roles/ListCollaborationRoles.vue similarity index 100% rename from web/src/components/Collaboration/Roles/ListCollaborationRoles.vue rename to web__old/src/components/Collaboration/Roles/ListCollaborationRoles.vue diff --git a/web/src/components/Common/TvCheckbox.vue b/web__old/src/components/Common/TvCheckbox.vue similarity index 100% rename from web/src/components/Common/TvCheckbox.vue rename to web__old/src/components/Common/TvCheckbox.vue diff --git a/web/src/components/Common/assets/checkbox-blank-outline.svg b/web__old/src/components/Common/assets/checkbox-blank-outline.svg similarity index 100% rename from web/src/components/Common/assets/checkbox-blank-outline.svg rename to web__old/src/components/Common/assets/checkbox-blank-outline.svg diff --git a/web/src/components/Common/assets/checkbox-marked.svg b/web__old/src/components/Common/assets/checkbox-marked.svg similarity index 100% rename from web/src/components/Common/assets/checkbox-marked.svg rename to web__old/src/components/Common/assets/checkbox-marked.svg diff --git a/web/src/components/ContextActions/ContextActions.ts b/web__old/src/components/ContextActions/ContextActions.ts similarity index 100% rename from web/src/components/ContextActions/ContextActions.ts rename to web__old/src/components/ContextActions/ContextActions.ts diff --git a/web/src/components/ContextActions/ContextActions.vue b/web__old/src/components/ContextActions/ContextActions.vue similarity index 100% rename from web/src/components/ContextActions/ContextActions.vue rename to web__old/src/components/ContextActions/ContextActions.vue diff --git a/web/src/components/ContextActions/index.ts b/web__old/src/components/ContextActions/index.ts similarity index 100% rename from web/src/components/ContextActions/index.ts rename to web__old/src/components/ContextActions/index.ts diff --git a/web/src/components/ForLayout/TvNavItem.vue b/web__old/src/components/ForLayout/TvNavItem.vue similarity index 100% rename from web/src/components/ForLayout/TvNavItem.vue rename to web__old/src/components/ForLayout/TvNavItem.vue diff --git a/web/src/components/ForLayout/TvNawDrawer.vue b/web__old/src/components/ForLayout/TvNawDrawer.vue similarity index 100% rename from web/src/components/ForLayout/TvNawDrawer.vue rename to web__old/src/components/ForLayout/TvNawDrawer.vue diff --git a/web/src/components/FormDelete/FormDelete.ts b/web__old/src/components/FormDelete/FormDelete.ts similarity index 100% rename from web/src/components/FormDelete/FormDelete.ts rename to web__old/src/components/FormDelete/FormDelete.ts diff --git a/web/src/components/FormDelete/FormDelete.vue b/web__old/src/components/FormDelete/FormDelete.vue similarity index 100% rename from web/src/components/FormDelete/FormDelete.vue rename to web__old/src/components/FormDelete/FormDelete.vue diff --git a/web/src/components/FormDelete/index.ts b/web__old/src/components/FormDelete/index.ts similarity index 100% rename from web/src/components/FormDelete/index.ts rename to web__old/src/components/FormDelete/index.ts diff --git a/web/src/components/GoalLists/GoalLists.vue b/web__old/src/components/GoalLists/GoalLists.vue similarity index 100% rename from web/src/components/GoalLists/GoalLists.vue rename to web__old/src/components/GoalLists/GoalLists.vue diff --git a/web/src/components/GoalLists/components/GoalListAdd/GoalListAdd.vue b/web__old/src/components/GoalLists/components/GoalListAdd/GoalListAdd.vue similarity index 100% rename from web/src/components/GoalLists/components/GoalListAdd/GoalListAdd.vue rename to web__old/src/components/GoalLists/components/GoalListAdd/GoalListAdd.vue diff --git a/web/src/components/GoalLists/components/GoalListAdd/index.ts b/web__old/src/components/GoalLists/components/GoalListAdd/index.ts similarity index 100% rename from web/src/components/GoalLists/components/GoalListAdd/index.ts rename to web__old/src/components/GoalLists/components/GoalListAdd/index.ts diff --git a/web/src/components/GoalLists/components/GoalListArchived.vue b/web__old/src/components/GoalLists/components/GoalListArchived.vue similarity index 100% rename from web/src/components/GoalLists/components/GoalListArchived.vue rename to web__old/src/components/GoalLists/components/GoalListArchived.vue diff --git a/web/src/components/GoalLists/components/GoalListEdit/GoalListEdit.ts b/web__old/src/components/GoalLists/components/GoalListEdit/GoalListEdit.ts similarity index 100% rename from web/src/components/GoalLists/components/GoalListEdit/GoalListEdit.ts rename to web__old/src/components/GoalLists/components/GoalListEdit/GoalListEdit.ts diff --git a/web/src/components/GoalLists/components/GoalListEdit/GoalListEdit.vue b/web__old/src/components/GoalLists/components/GoalListEdit/GoalListEdit.vue similarity index 100% rename from web/src/components/GoalLists/components/GoalListEdit/GoalListEdit.vue rename to web__old/src/components/GoalLists/components/GoalListEdit/GoalListEdit.vue diff --git a/web/src/components/GoalLists/components/GoalListEdit/index.ts b/web__old/src/components/GoalLists/components/GoalListEdit/index.ts similarity index 100% rename from web/src/components/GoalLists/components/GoalListEdit/index.ts rename to web__old/src/components/GoalLists/components/GoalListEdit/index.ts diff --git a/web/src/components/GoalLists/components/GoalListItem/GoalListItem.ts b/web__old/src/components/GoalLists/components/GoalListItem/GoalListItem.ts similarity index 100% rename from web/src/components/GoalLists/components/GoalListItem/GoalListItem.ts rename to web__old/src/components/GoalLists/components/GoalListItem/GoalListItem.ts diff --git a/web/src/components/GoalLists/components/GoalListItem/GoalListItem.vue b/web__old/src/components/GoalLists/components/GoalListItem/GoalListItem.vue similarity index 100% rename from web/src/components/GoalLists/components/GoalListItem/GoalListItem.vue rename to web__old/src/components/GoalLists/components/GoalListItem/GoalListItem.vue diff --git a/web/src/components/GoalLists/components/GoalListItem/index.ts b/web__old/src/components/GoalLists/components/GoalListItem/index.ts similarity index 100% rename from web/src/components/GoalLists/components/GoalListItem/index.ts rename to web__old/src/components/GoalLists/components/GoalListItem/index.ts diff --git a/web/src/components/GoalLists/index.ts b/web__old/src/components/GoalLists/index.ts similarity index 100% rename from web/src/components/GoalLists/index.ts rename to web__old/src/components/GoalLists/index.ts diff --git a/web/src/components/Goals/Goals.vue b/web__old/src/components/Goals/Goals.vue similarity index 100% rename from web/src/components/Goals/Goals.vue rename to web__old/src/components/Goals/Goals.vue diff --git a/web/src/components/Goals/components/ArchiveGoal.vue b/web__old/src/components/Goals/components/ArchiveGoal.vue similarity index 100% rename from web/src/components/Goals/components/ArchiveGoal.vue rename to web__old/src/components/Goals/components/ArchiveGoal.vue diff --git a/web/src/components/Goals/components/ArchivedGoals.vue b/web__old/src/components/Goals/components/ArchivedGoals.vue similarity index 100% rename from web/src/components/Goals/components/ArchivedGoals.vue rename to web__old/src/components/Goals/components/ArchivedGoals.vue diff --git a/web/src/components/Goals/components/GoalAdd/GoalAdd.vue b/web__old/src/components/Goals/components/GoalAdd/GoalAdd.vue similarity index 100% rename from web/src/components/Goals/components/GoalAdd/GoalAdd.vue rename to web__old/src/components/Goals/components/GoalAdd/GoalAdd.vue diff --git a/web/src/components/Goals/components/GoalAdd/index.ts b/web__old/src/components/Goals/components/GoalAdd/index.ts similarity index 100% rename from web/src/components/Goals/components/GoalAdd/index.ts rename to web__old/src/components/Goals/components/GoalAdd/index.ts diff --git a/web/src/components/Goals/components/GoalEdit/GoalEdit.ts b/web__old/src/components/Goals/components/GoalEdit/GoalEdit.ts similarity index 100% rename from web/src/components/Goals/components/GoalEdit/GoalEdit.ts rename to web__old/src/components/Goals/components/GoalEdit/GoalEdit.ts diff --git a/web/src/components/Goals/components/GoalEdit/GoalEdit.vue b/web__old/src/components/Goals/components/GoalEdit/GoalEdit.vue similarity index 100% rename from web/src/components/Goals/components/GoalEdit/GoalEdit.vue rename to web__old/src/components/Goals/components/GoalEdit/GoalEdit.vue diff --git a/web/src/components/Goals/components/GoalEdit/index.ts b/web__old/src/components/Goals/components/GoalEdit/index.ts similarity index 100% rename from web/src/components/Goals/components/GoalEdit/index.ts rename to web__old/src/components/Goals/components/GoalEdit/index.ts diff --git a/web/src/components/Goals/components/GoalItemActions.vue b/web__old/src/components/Goals/components/GoalItemActions.vue similarity index 100% rename from web/src/components/Goals/components/GoalItemActions.vue rename to web__old/src/components/Goals/components/GoalItemActions.vue diff --git a/web/src/components/Goals/components/GoalsItem/GoalsItem.vue b/web__old/src/components/Goals/components/GoalsItem/GoalsItem.vue similarity index 100% rename from web/src/components/Goals/components/GoalsItem/GoalsItem.vue rename to web__old/src/components/Goals/components/GoalsItem/GoalsItem.vue diff --git a/web/src/components/Goals/components/GoalsItem/index.ts b/web__old/src/components/Goals/components/GoalsItem/index.ts similarity index 100% rename from web/src/components/Goals/components/GoalsItem/index.ts rename to web__old/src/components/Goals/components/GoalsItem/index.ts diff --git a/web/src/components/Goals/index.ts b/web__old/src/components/Goals/index.ts similarity index 100% rename from web/src/components/Goals/index.ts rename to web__old/src/components/Goals/index.ts diff --git a/web/src/components/Graph/AddNewTaskToGraph.vue b/web__old/src/components/Graph/AddNewTaskToGraph.vue similarity index 100% rename from web/src/components/Graph/AddNewTaskToGraph.vue rename to web__old/src/components/Graph/AddNewTaskToGraph.vue diff --git a/web/src/components/Graph/ProjectGraph.vue b/web__old/src/components/Graph/ProjectGraph.vue similarity index 100% rename from web/src/components/Graph/ProjectGraph.vue rename to web__old/src/components/Graph/ProjectGraph.vue diff --git a/web/src/components/Graph/TaskNode.vue b/web__old/src/components/Graph/TaskNode.vue similarity index 100% rename from web/src/components/Graph/TaskNode.vue rename to web__old/src/components/Graph/TaskNode.vue diff --git a/web/src/components/Graph/composables/useLayout.ts b/web__old/src/components/Graph/composables/useLayout.ts similarity index 100% rename from web/src/components/Graph/composables/useLayout.ts rename to web__old/src/components/Graph/composables/useLayout.ts diff --git a/web/src/components/ItemWithAvatar.vue b/web__old/src/components/ItemWithAvatar.vue similarity index 100% rename from web/src/components/ItemWithAvatar.vue rename to web__old/src/components/ItemWithAvatar.vue diff --git a/web/src/components/Kanban/KanbanBoard.vue b/web__old/src/components/Kanban/KanbanBoard.vue similarity index 100% rename from web/src/components/Kanban/KanbanBoard.vue rename to web__old/src/components/Kanban/KanbanBoard.vue diff --git a/web/src/components/Kanban/components/KanbanAddStatus.vue b/web__old/src/components/Kanban/components/KanbanAddStatus.vue similarity index 100% rename from web/src/components/Kanban/components/KanbanAddStatus.vue rename to web__old/src/components/Kanban/components/KanbanAddStatus.vue diff --git a/web/src/components/Kanban/components/KanbanAddTask.vue b/web__old/src/components/Kanban/components/KanbanAddTask.vue similarity index 100% rename from web/src/components/Kanban/components/KanbanAddTask.vue rename to web__old/src/components/Kanban/components/KanbanAddTask.vue diff --git a/web/src/components/Kanban/components/KanbanDelete.vue b/web__old/src/components/Kanban/components/KanbanDelete.vue similarity index 100% rename from web/src/components/Kanban/components/KanbanDelete.vue rename to web__old/src/components/Kanban/components/KanbanDelete.vue diff --git a/web/src/components/Kanban/components/KanbanTitleEdit.vue b/web__old/src/components/Kanban/components/KanbanTitleEdit.vue similarity index 100% rename from web/src/components/Kanban/components/KanbanTitleEdit.vue rename to web__old/src/components/Kanban/components/KanbanTitleEdit.vue diff --git a/web/src/components/Kanban/components/KanbanTitleMenu.vue b/web__old/src/components/Kanban/components/KanbanTitleMenu.vue similarity index 100% rename from web/src/components/Kanban/components/KanbanTitleMenu.vue rename to web__old/src/components/Kanban/components/KanbanTitleMenu.vue diff --git a/web/src/components/Kanban/kanban.scss b/web__old/src/components/Kanban/kanban.scss similarity index 100% rename from web/src/components/Kanban/kanban.scss rename to web__old/src/components/Kanban/kanban.scss diff --git a/web/src/components/MobileHeader.vue b/web__old/src/components/MobileHeader.vue similarity index 100% rename from web/src/components/MobileHeader.vue rename to web__old/src/components/MobileHeader.vue diff --git a/web/src/components/MobileHeaderTasksActions copy.vue b/web__old/src/components/MobileHeaderTasksActions copy.vue similarity index 100% rename from web/src/components/MobileHeaderTasksActions copy.vue rename to web__old/src/components/MobileHeaderTasksActions copy.vue diff --git a/web/src/components/Molecules/MTaskNote.vue b/web__old/src/components/Molecules/MTaskNote.vue similarity index 100% rename from web/src/components/Molecules/MTaskNote.vue rename to web__old/src/components/Molecules/MTaskNote.vue diff --git a/web/src/components/Molecules/TaskNote.scss b/web__old/src/components/Molecules/TaskNote.scss similarity index 100% rename from web/src/components/Molecules/TaskNote.scss rename to web__old/src/components/Molecules/TaskNote.scss diff --git a/web/src/components/Screens/BasePageDialog.vue b/web__old/src/components/Screens/BasePageDialog.vue similarity index 100% rename from web/src/components/Screens/BasePageDialog.vue rename to web__old/src/components/Screens/BasePageDialog.vue diff --git a/web/src/components/Screens/BoardUserProjects.vue b/web__old/src/components/Screens/BoardUserProjects.vue similarity index 100% rename from web/src/components/Screens/BoardUserProjects.vue rename to web__old/src/components/Screens/BoardUserProjects.vue diff --git a/web/src/components/Screens/MainScreenAddGoalDialog.vue b/web__old/src/components/Screens/MainScreenAddGoalDialog.vue similarity index 100% rename from web/src/components/Screens/MainScreenAddGoalDialog.vue rename to web__old/src/components/Screens/MainScreenAddGoalDialog.vue diff --git a/web/src/components/Screens/MainScreenAddTaskDialog.vue b/web__old/src/components/Screens/MainScreenAddTaskDialog.vue similarity index 100% rename from web/src/components/Screens/MainScreenAddTaskDialog.vue rename to web__old/src/components/Screens/MainScreenAddTaskDialog.vue diff --git a/web/src/components/Screens/MobileHeaderUserPage.vue b/web__old/src/components/Screens/MobileHeaderUserPage.vue similarity index 100% rename from web/src/components/Screens/MobileHeaderUserPage.vue rename to web__old/src/components/Screens/MobileHeaderUserPage.vue diff --git a/web/src/components/Screens/ProjectHelperActions.vue b/web__old/src/components/Screens/ProjectHelperActions.vue similarity index 100% rename from web/src/components/Screens/ProjectHelperActions.vue rename to web__old/src/components/Screens/ProjectHelperActions.vue diff --git a/web/src/components/Screens/StructureText.vue b/web__old/src/components/Screens/StructureText.vue similarity index 100% rename from web/src/components/Screens/StructureText.vue rename to web__old/src/components/Screens/StructureText.vue diff --git a/web/src/components/Screens/components/AppBottomNavigationBarMobile.vue b/web__old/src/components/Screens/components/AppBottomNavigationBarMobile.vue similarity index 100% rename from web/src/components/Screens/components/AppBottomNavigationBarMobile.vue rename to web__old/src/components/Screens/components/AppBottomNavigationBarMobile.vue diff --git a/web/src/components/Screens/components/ProjectItem.vue b/web__old/src/components/Screens/components/ProjectItem.vue similarity index 100% rename from web/src/components/Screens/components/ProjectItem.vue rename to web__old/src/components/Screens/components/ProjectItem.vue diff --git a/web/src/components/Screens/components/ProjectUsers/UsersByProject.vue b/web__old/src/components/Screens/components/ProjectUsers/UsersByProject.vue similarity index 100% rename from web/src/components/Screens/components/ProjectUsers/UsersByProject.vue rename to web__old/src/components/Screens/components/ProjectUsers/UsersByProject.vue diff --git a/web/src/components/Screens/components/ProjectUsers/UsersByProjectDesktop.vue b/web__old/src/components/Screens/components/ProjectUsers/UsersByProjectDesktop.vue similarity index 100% rename from web/src/components/Screens/components/ProjectUsers/UsersByProjectDesktop.vue rename to web__old/src/components/Screens/components/ProjectUsers/UsersByProjectDesktop.vue diff --git a/web/src/components/Screens/components/ProjectUsers/UsersByProjectMobile.vue b/web__old/src/components/Screens/components/ProjectUsers/UsersByProjectMobile.vue similarity index 100% rename from web/src/components/Screens/components/ProjectUsers/UsersByProjectMobile.vue rename to web__old/src/components/Screens/components/ProjectUsers/UsersByProjectMobile.vue diff --git a/web/src/components/Screens/components/ProjectUsers/components/GoalUsersIDesktopItem.vue b/web__old/src/components/Screens/components/ProjectUsers/components/GoalUsersIDesktopItem.vue similarity index 100% rename from web/src/components/Screens/components/ProjectUsers/components/GoalUsersIDesktopItem.vue rename to web__old/src/components/Screens/components/ProjectUsers/components/GoalUsersIDesktopItem.vue diff --git a/web/src/components/Screens/components/ProjectUsers/components/GoalUsersIMobileItem.vue b/web__old/src/components/Screens/components/ProjectUsers/components/GoalUsersIMobileItem.vue similarity index 100% rename from web/src/components/Screens/components/ProjectUsers/components/GoalUsersIMobileItem.vue rename to web__old/src/components/Screens/components/ProjectUsers/components/GoalUsersIMobileItem.vue diff --git a/web/src/components/ServerSelection.vue b/web__old/src/components/ServerSelection.vue similarity index 100% rename from web/src/components/ServerSelection.vue rename to web__old/src/components/ServerSelection.vue diff --git a/web/src/components/SocialLinks/SocialLinks.scss b/web__old/src/components/SocialLinks/SocialLinks.scss similarity index 100% rename from web/src/components/SocialLinks/SocialLinks.scss rename to web__old/src/components/SocialLinks/SocialLinks.scss diff --git a/web/src/components/SocialLinks/SocialLinks.vue b/web__old/src/components/SocialLinks/SocialLinks.vue similarity index 100% rename from web/src/components/SocialLinks/SocialLinks.vue rename to web__old/src/components/SocialLinks/SocialLinks.vue diff --git a/web/src/components/TTextField/TTextField.ts b/web__old/src/components/TTextField/TTextField.ts similarity index 100% rename from web/src/components/TTextField/TTextField.ts rename to web__old/src/components/TTextField/TTextField.ts diff --git a/web/src/components/TTextField/TTextField.vue b/web__old/src/components/TTextField/TTextField.vue similarity index 100% rename from web/src/components/TTextField/TTextField.vue rename to web__old/src/components/TTextField/TTextField.vue diff --git a/web/src/components/TTextField/index.ts b/web__old/src/components/TTextField/index.ts similarity index 100% rename from web/src/components/TTextField/index.ts rename to web__old/src/components/TTextField/index.ts diff --git a/web/src/components/Tasks/Tasks.vue b/web__old/src/components/Tasks/Tasks.vue similarity index 100% rename from web/src/components/Tasks/Tasks.vue rename to web__old/src/components/Tasks/Tasks.vue diff --git a/web/src/components/Tasks/components/AllGoalsTasks.vue b/web__old/src/components/Tasks/components/AllGoalsTasks.vue similarity index 100% rename from web/src/components/Tasks/components/AllGoalsTasks.vue rename to web__old/src/components/Tasks/components/AllGoalsTasks.vue diff --git a/web/src/components/Tasks/components/MobileHeaderTask.vue b/web__old/src/components/Tasks/components/MobileHeaderTask.vue similarity index 100% rename from web/src/components/Tasks/components/MobileHeaderTask.vue rename to web__old/src/components/Tasks/components/MobileHeaderTask.vue diff --git a/web/src/components/Tasks/components/TagItem.vue b/web__old/src/components/Tasks/components/TagItem.vue similarity index 100% rename from web/src/components/Tasks/components/TagItem.vue rename to web__old/src/components/Tasks/components/TagItem.vue diff --git a/web/src/components/Tasks/components/TaskAddSubtask.vue b/web__old/src/components/Tasks/components/TaskAddSubtask.vue similarity index 100% rename from web/src/components/Tasks/components/TaskAddSubtask.vue rename to web__old/src/components/Tasks/components/TaskAddSubtask.vue diff --git a/web/src/components/Tasks/components/TaskAddTag.vue b/web__old/src/components/Tasks/components/TaskAddTag.vue similarity index 100% rename from web/src/components/Tasks/components/TaskAddTag.vue rename to web__old/src/components/Tasks/components/TaskAddTag.vue diff --git a/web/src/components/Tasks/components/TaskAmount/TaskAmount.module.scss b/web__old/src/components/Tasks/components/TaskAmount/TaskAmount.module.scss similarity index 100% rename from web/src/components/Tasks/components/TaskAmount/TaskAmount.module.scss rename to web__old/src/components/Tasks/components/TaskAmount/TaskAmount.module.scss diff --git a/web/src/components/Tasks/components/TaskAmount/TaskAmount.vue b/web__old/src/components/Tasks/components/TaskAmount/TaskAmount.vue similarity index 100% rename from web/src/components/Tasks/components/TaskAmount/TaskAmount.vue rename to web__old/src/components/Tasks/components/TaskAmount/TaskAmount.vue diff --git a/web/src/components/Tasks/components/TaskAssignUser.vue b/web__old/src/components/Tasks/components/TaskAssignUser.vue similarity index 100% rename from web/src/components/Tasks/components/TaskAssignUser.vue rename to web__old/src/components/Tasks/components/TaskAssignUser.vue diff --git a/web/src/components/Tasks/components/TaskBackToLists.vue b/web__old/src/components/Tasks/components/TaskBackToLists.vue similarity index 100% rename from web/src/components/Tasks/components/TaskBackToLists.vue rename to web__old/src/components/Tasks/components/TaskBackToLists.vue diff --git a/web/src/components/Tasks/components/TaskBreadcrumbs/TaskBreadcrumbs.module.scss b/web__old/src/components/Tasks/components/TaskBreadcrumbs/TaskBreadcrumbs.module.scss similarity index 100% rename from web/src/components/Tasks/components/TaskBreadcrumbs/TaskBreadcrumbs.module.scss rename to web__old/src/components/Tasks/components/TaskBreadcrumbs/TaskBreadcrumbs.module.scss diff --git a/web/src/components/Tasks/components/TaskBreadcrumbs/TaskBreadcrumbs.vue b/web__old/src/components/Tasks/components/TaskBreadcrumbs/TaskBreadcrumbs.vue similarity index 100% rename from web/src/components/Tasks/components/TaskBreadcrumbs/TaskBreadcrumbs.vue rename to web__old/src/components/Tasks/components/TaskBreadcrumbs/TaskBreadcrumbs.vue diff --git a/web/src/components/Tasks/components/TaskChangeList.vue b/web__old/src/components/Tasks/components/TaskChangeList.vue similarity index 100% rename from web/src/components/Tasks/components/TaskChangeList.vue rename to web__old/src/components/Tasks/components/TaskChangeList.vue diff --git a/web/src/components/Tasks/components/TaskChangeStatus.vue b/web__old/src/components/Tasks/components/TaskChangeStatus.vue similarity index 100% rename from web/src/components/Tasks/components/TaskChangeStatus.vue rename to web__old/src/components/Tasks/components/TaskChangeStatus.vue diff --git a/web/src/components/Tasks/components/TaskCopyId.vue b/web__old/src/components/Tasks/components/TaskCopyId.vue similarity index 100% rename from web/src/components/Tasks/components/TaskCopyId.vue rename to web__old/src/components/Tasks/components/TaskCopyId.vue diff --git a/web/src/components/Tasks/components/TaskDate.vue b/web__old/src/components/Tasks/components/TaskDate.vue similarity index 100% rename from web/src/components/Tasks/components/TaskDate.vue rename to web__old/src/components/Tasks/components/TaskDate.vue diff --git a/web/src/components/Tasks/components/TaskDeadline.vue b/web__old/src/components/Tasks/components/TaskDeadline.vue similarity index 100% rename from web/src/components/Tasks/components/TaskDeadline.vue rename to web__old/src/components/Tasks/components/TaskDeadline.vue diff --git a/web/src/components/Tasks/components/TaskDeleteTag.vue b/web__old/src/components/Tasks/components/TaskDeleteTag.vue similarity index 100% rename from web/src/components/Tasks/components/TaskDeleteTag.vue rename to web__old/src/components/Tasks/components/TaskDeleteTag.vue diff --git a/web/src/components/Tasks/components/TaskDialog/TaskDialog.vue b/web__old/src/components/Tasks/components/TaskDialog/TaskDialog.vue similarity index 100% rename from web/src/components/Tasks/components/TaskDialog/TaskDialog.vue rename to web__old/src/components/Tasks/components/TaskDialog/TaskDialog.vue diff --git a/web/src/components/Tasks/components/TaskDialog/TaskDialogDesk.vue b/web__old/src/components/Tasks/components/TaskDialog/TaskDialogDesk.vue similarity index 100% rename from web/src/components/Tasks/components/TaskDialog/TaskDialogDesk.vue rename to web__old/src/components/Tasks/components/TaskDialog/TaskDialogDesk.vue diff --git a/web/src/components/Tasks/components/TaskDialog/TaskDialogMob.vue b/web__old/src/components/Tasks/components/TaskDialog/TaskDialogMob.vue similarity index 100% rename from web/src/components/Tasks/components/TaskDialog/TaskDialogMob.vue rename to web__old/src/components/Tasks/components/TaskDialog/TaskDialogMob.vue diff --git a/web/src/components/Tasks/components/TaskDialog/useTaskDialog.ts b/web__old/src/components/Tasks/components/TaskDialog/useTaskDialog.ts similarity index 100% rename from web/src/components/Tasks/components/TaskDialog/useTaskDialog.ts rename to web__old/src/components/Tasks/components/TaskDialog/useTaskDialog.ts diff --git a/web/src/components/Tasks/components/TaskEditItem.vue b/web__old/src/components/Tasks/components/TaskEditItem.vue similarity index 100% rename from web/src/components/Tasks/components/TaskEditItem.vue rename to web__old/src/components/Tasks/components/TaskEditItem.vue diff --git a/web/src/components/Tasks/components/TaskEditTag.vue b/web__old/src/components/Tasks/components/TaskEditTag.vue similarity index 100% rename from web/src/components/Tasks/components/TaskEditTag.vue rename to web__old/src/components/Tasks/components/TaskEditTag.vue diff --git a/web/src/components/Tasks/components/TaskHistory.vue b/web__old/src/components/Tasks/components/TaskHistory.vue similarity index 100% rename from web/src/components/Tasks/components/TaskHistory.vue rename to web__old/src/components/Tasks/components/TaskHistory.vue diff --git a/web/src/components/Tasks/components/TaskItemMain.vue b/web__old/src/components/Tasks/components/TaskItemMain.vue similarity index 100% rename from web/src/components/Tasks/components/TaskItemMain.vue rename to web__old/src/components/Tasks/components/TaskItemMain.vue diff --git a/web/src/components/Tasks/components/TaskMoneySection/TaskMoneySection.vue b/web__old/src/components/Tasks/components/TaskMoneySection/TaskMoneySection.vue similarity index 100% rename from web/src/components/Tasks/components/TaskMoneySection/TaskMoneySection.vue rename to web__old/src/components/Tasks/components/TaskMoneySection/TaskMoneySection.vue diff --git a/web/src/components/Tasks/components/TaskPriority.vue b/web__old/src/components/Tasks/components/TaskPriority.vue similarity index 100% rename from web/src/components/Tasks/components/TaskPriority.vue rename to web__old/src/components/Tasks/components/TaskPriority.vue diff --git a/web/src/components/Tasks/components/TaskPriorityUi.vue b/web__old/src/components/Tasks/components/TaskPriorityUi.vue similarity index 100% rename from web/src/components/Tasks/components/TaskPriorityUi.vue rename to web__old/src/components/Tasks/components/TaskPriorityUi.vue diff --git a/web/src/components/Tasks/components/TaskSearchAdd/TaskSearchAdd.vue b/web__old/src/components/Tasks/components/TaskSearchAdd/TaskSearchAdd.vue similarity index 100% rename from web/src/components/Tasks/components/TaskSearchAdd/TaskSearchAdd.vue rename to web__old/src/components/Tasks/components/TaskSearchAdd/TaskSearchAdd.vue diff --git a/web/src/components/Tasks/components/TaskSearchAdd/index.ts b/web__old/src/components/Tasks/components/TaskSearchAdd/index.ts similarity index 100% rename from web/src/components/Tasks/components/TaskSearchAdd/index.ts rename to web__old/src/components/Tasks/components/TaskSearchAdd/index.ts diff --git a/web/src/components/Tasks/components/TaskSubtaskItem.vue b/web__old/src/components/Tasks/components/TaskSubtaskItem.vue similarity index 100% rename from web/src/components/Tasks/components/TaskSubtaskItem.vue rename to web__old/src/components/Tasks/components/TaskSubtaskItem.vue diff --git a/web/src/components/Tasks/components/TaskSubtasksSection.vue b/web__old/src/components/Tasks/components/TaskSubtasksSection.vue similarity index 100% rename from web/src/components/Tasks/components/TaskSubtasksSection.vue rename to web__old/src/components/Tasks/components/TaskSubtasksSection.vue diff --git a/web/src/components/Tasks/components/TaskTags.vue b/web__old/src/components/Tasks/components/TaskTags.vue similarity index 100% rename from web/src/components/Tasks/components/TaskTags.vue rename to web__old/src/components/Tasks/components/TaskTags.vue diff --git a/web/src/components/Tasks/components/TaskTransactionType/TaskTransactionType.vue b/web__old/src/components/Tasks/components/TaskTransactionType/TaskTransactionType.vue similarity index 100% rename from web/src/components/Tasks/components/TaskTransactionType/TaskTransactionType.vue rename to web__old/src/components/Tasks/components/TaskTransactionType/TaskTransactionType.vue diff --git a/web/src/components/Tasks/components/TasksFilterBar.vue b/web__old/src/components/Tasks/components/TasksFilterBar.vue similarity index 100% rename from web/src/components/Tasks/components/TasksFilterBar.vue rename to web__old/src/components/Tasks/components/TasksFilterBar.vue diff --git a/web/src/components/Tasks/components/TasksFilterInList.vue b/web__old/src/components/Tasks/components/TasksFilterInList.vue similarity index 100% rename from web/src/components/Tasks/components/TasksFilterInList.vue rename to web__old/src/components/Tasks/components/TasksFilterInList.vue diff --git a/web/src/components/Tasks/components/TasksFiltersCommon.vue b/web__old/src/components/Tasks/components/TasksFiltersCommon.vue similarity index 100% rename from web/src/components/Tasks/components/TasksFiltersCommon.vue rename to web__old/src/components/Tasks/components/TasksFiltersCommon.vue diff --git a/web/src/components/Tasks/components/TasksReorder.vue b/web__old/src/components/Tasks/components/TasksReorder.vue similarity index 100% rename from web/src/components/Tasks/components/TasksReorder.vue rename to web__old/src/components/Tasks/components/TasksReorder.vue diff --git a/web/src/components/Tasks/index.ts b/web__old/src/components/Tasks/index.ts similarity index 100% rename from web/src/components/Tasks/index.ts rename to web__old/src/components/Tasks/index.ts diff --git a/web/src/components/TvBtn/TvBtn.ts b/web__old/src/components/TvBtn/TvBtn.ts similarity index 100% rename from web/src/components/TvBtn/TvBtn.ts rename to web__old/src/components/TvBtn/TvBtn.ts diff --git a/web/src/components/TvBtn/TvBtn.vue b/web__old/src/components/TvBtn/TvBtn.vue similarity index 100% rename from web/src/components/TvBtn/TvBtn.vue rename to web__old/src/components/TvBtn/TvBtn.vue diff --git a/web/src/components/TvBtn/index.ts b/web__old/src/components/TvBtn/index.ts similarity index 100% rename from web/src/components/TvBtn/index.ts rename to web__old/src/components/TvBtn/index.ts diff --git a/web/src/components/User/Account.vue b/web__old/src/components/User/Account.vue similarity index 100% rename from web/src/components/User/Account.vue rename to web__old/src/components/User/Account.vue diff --git a/web/src/components/UserUiUtils.vue b/web__old/src/components/UserUiUtils.vue similarity index 100% rename from web/src/components/UserUiUtils.vue rename to web__old/src/components/UserUiUtils.vue diff --git a/web/src/components/UserUiUtils/ThemeSwitcher.vue b/web__old/src/components/UserUiUtils/ThemeSwitcher.vue similarity index 100% rename from web/src/components/UserUiUtils/ThemeSwitcher.vue rename to web__old/src/components/UserUiUtils/ThemeSwitcher.vue diff --git a/web/src/components/modules/screen-main/DesktopScreen.vue b/web__old/src/components/modules/screen-main/DesktopScreen.vue similarity index 100% rename from web/src/components/modules/screen-main/DesktopScreen.vue rename to web__old/src/components/modules/screen-main/DesktopScreen.vue diff --git a/web/src/components/modules/screen-main/components/MainScreenWidgetTemplate.vue b/web__old/src/components/modules/screen-main/components/MainScreenWidgetTemplate.vue similarity index 100% rename from web/src/components/modules/screen-main/components/MainScreenWidgetTemplate.vue rename to web__old/src/components/modules/screen-main/components/MainScreenWidgetTemplate.vue diff --git a/web/src/components/modules/screen-main/components/NoGoals.vue b/web__old/src/components/modules/screen-main/components/NoGoals.vue similarity index 100% rename from web/src/components/modules/screen-main/components/NoGoals.vue rename to web__old/src/components/modules/screen-main/components/NoGoals.vue diff --git a/web/src/components/modules/screen-main/components/SearchAll.vue b/web__old/src/components/modules/screen-main/components/SearchAll.vue similarity index 100% rename from web/src/components/modules/screen-main/components/SearchAll.vue rename to web__old/src/components/modules/screen-main/components/SearchAll.vue diff --git a/web/src/components/modules/screen-main/components/WidgetBase.vue b/web__old/src/components/modules/screen-main/components/WidgetBase.vue similarity index 100% rename from web/src/components/modules/screen-main/components/WidgetBase.vue rename to web__old/src/components/modules/screen-main/components/WidgetBase.vue diff --git a/web/src/components/modules/screen-main/components/WidgetLastAddedTasks.vue b/web__old/src/components/modules/screen-main/components/WidgetLastAddedTasks.vue similarity index 100% rename from web/src/components/modules/screen-main/components/WidgetLastAddedTasks.vue rename to web__old/src/components/modules/screen-main/components/WidgetLastAddedTasks.vue diff --git a/web/src/components/modules/screen-main/components/WidgetToday.vue b/web__old/src/components/modules/screen-main/components/WidgetToday.vue similarity index 100% rename from web/src/components/modules/screen-main/components/WidgetToday.vue rename to web__old/src/components/modules/screen-main/components/WidgetToday.vue diff --git a/web/src/components/modules/screen-main/components/WidgetUpcoming.vue b/web__old/src/components/modules/screen-main/components/WidgetUpcoming.vue similarity index 100% rename from web/src/components/modules/screen-main/components/WidgetUpcoming.vue rename to web__old/src/components/modules/screen-main/components/WidgetUpcoming.vue diff --git a/web/src/components/tv-ui/TAssignedUsers.vue b/web__old/src/components/tv-ui/TAssignedUsers.vue similarity index 100% rename from web/src/components/tv-ui/TAssignedUsers.vue rename to web__old/src/components/tv-ui/TAssignedUsers.vue diff --git a/web/src/components/tv-ui/TChip.vue b/web__old/src/components/tv-ui/TChip.vue similarity index 100% rename from web/src/components/tv-ui/TChip.vue rename to web__old/src/components/tv-ui/TChip.vue diff --git a/web/src/components/tv-ui/TChipShape.vue b/web__old/src/components/tv-ui/TChipShape.vue similarity index 100% rename from web/src/components/tv-ui/TChipShape.vue rename to web__old/src/components/tv-ui/TChipShape.vue diff --git a/web/src/components/tv-ui/TDateChip.vue b/web__old/src/components/tv-ui/TDateChip.vue similarity index 100% rename from web/src/components/tv-ui/TDateChip.vue rename to web__old/src/components/tv-ui/TDateChip.vue diff --git a/web/src/components/tv-ui/TListName.vue b/web__old/src/components/tv-ui/TListName.vue similarity index 100% rename from web/src/components/tv-ui/TListName.vue rename to web__old/src/components/tv-ui/TListName.vue diff --git a/web/src/components/tv-ui/TMoney.vue b/web__old/src/components/tv-ui/TMoney.vue similarity index 100% rename from web/src/components/tv-ui/TMoney.vue rename to web__old/src/components/tv-ui/TMoney.vue diff --git a/web/src/components/tv-ui/TPriority.vue b/web__old/src/components/tv-ui/TPriority.vue similarity index 100% rename from web/src/components/tv-ui/TPriority.vue rename to web__old/src/components/tv-ui/TPriority.vue diff --git a/web/src/components/tv-ui/TProjectName.vue b/web__old/src/components/tv-ui/TProjectName.vue similarity index 100% rename from web/src/components/tv-ui/TProjectName.vue rename to web__old/src/components/tv-ui/TProjectName.vue diff --git a/web/src/components/tv-ui/buttons/TRoundedBtn/README.md b/web__old/src/components/tv-ui/buttons/TRoundedBtn/README.md similarity index 100% rename from web/src/components/tv-ui/buttons/TRoundedBtn/README.md rename to web__old/src/components/tv-ui/buttons/TRoundedBtn/README.md diff --git a/web/src/components/tv-ui/buttons/TRoundedBtn/TRoundBtn.examples.md b/web__old/src/components/tv-ui/buttons/TRoundedBtn/TRoundBtn.examples.md similarity index 100% rename from web/src/components/tv-ui/buttons/TRoundedBtn/TRoundBtn.examples.md rename to web__old/src/components/tv-ui/buttons/TRoundedBtn/TRoundBtn.examples.md diff --git a/web/src/components/tv-ui/buttons/TRoundedBtn/TRoundedBtn.vue b/web__old/src/components/tv-ui/buttons/TRoundedBtn/TRoundedBtn.vue similarity index 100% rename from web/src/components/tv-ui/buttons/TRoundedBtn/TRoundedBtn.vue rename to web__old/src/components/tv-ui/buttons/TRoundedBtn/TRoundedBtn.vue diff --git a/web/src/components/tv-ui/helpers/TaskItemUi.module.scss b/web__old/src/components/tv-ui/helpers/TaskItemUi.module.scss similarity index 100% rename from web/src/components/tv-ui/helpers/TaskItemUi.module.scss rename to web__old/src/components/tv-ui/helpers/TaskItemUi.module.scss diff --git a/web/src/components/tv-ui/helpers/checkbox-task-priority.ts b/web__old/src/components/tv-ui/helpers/checkbox-task-priority.ts similarity index 100% rename from web/src/components/tv-ui/helpers/checkbox-task-priority.ts rename to web__old/src/components/tv-ui/helpers/checkbox-task-priority.ts diff --git a/web/src/components/tv-ui/tabs/TTabs.examples.vue b/web__old/src/components/tv-ui/tabs/TTabs.examples.vue similarity index 100% rename from web/src/components/tv-ui/tabs/TTabs.examples.vue rename to web__old/src/components/tv-ui/tabs/TTabs.examples.vue diff --git a/web/src/components/tv-ui/tabs/TTabs.module.scss b/web__old/src/components/tv-ui/tabs/TTabs.module.scss similarity index 100% rename from web/src/components/tv-ui/tabs/TTabs.module.scss rename to web__old/src/components/tv-ui/tabs/TTabs.module.scss diff --git a/web/src/components/tv-ui/tabs/TTabs.vue b/web__old/src/components/tv-ui/tabs/TTabs.vue similarity index 100% rename from web/src/components/tv-ui/tabs/TTabs.vue rename to web__old/src/components/tv-ui/tabs/TTabs.vue diff --git a/web/src/composition/README-useTvFormatDate.md b/web__old/src/composition/README-useTvFormatDate.md similarity index 100% rename from web/src/composition/README-useTvFormatDate.md rename to web__old/src/composition/README-useTvFormatDate.md diff --git a/web/src/composition/use-widget-start-screen-task-amination.ts b/web__old/src/composition/use-widget-start-screen-task-amination.ts similarity index 100% rename from web/src/composition/use-widget-start-screen-task-amination.ts rename to web__old/src/composition/use-widget-start-screen-task-amination.ts diff --git a/web/src/composition/useAdditionalServer.ts b/web__old/src/composition/useAdditionalServer.ts similarity index 100% rename from web/src/composition/useAdditionalServer.ts rename to web__old/src/composition/useAdditionalServer.ts diff --git a/web/src/composition/useDateTemplate.ts b/web__old/src/composition/useDateTemplate.ts similarity index 100% rename from web/src/composition/useDateTemplate.ts rename to web__old/src/composition/useDateTemplate.ts diff --git a/web/src/composition/useDialogWidth.ts b/web__old/src/composition/useDialogWidth.ts similarity index 100% rename from web/src/composition/useDialogWidth.ts rename to web__old/src/composition/useDialogWidth.ts diff --git a/web/src/composition/useGoalPermissions.ts b/web__old/src/composition/useGoalPermissions.ts similarity index 100% rename from web/src/composition/useGoalPermissions.ts rename to web__old/src/composition/useGoalPermissions.ts diff --git a/web/src/composition/useLogout.ts b/web__old/src/composition/useLogout.ts similarity index 100% rename from web/src/composition/useLogout.ts rename to web__old/src/composition/useLogout.ts diff --git a/web/src/composition/useMobile.ts b/web__old/src/composition/useMobile.ts similarity index 100% rename from web/src/composition/useMobile.ts rename to web__old/src/composition/useMobile.ts diff --git a/web/src/composition/useRouteLocation.ts b/web__old/src/composition/useRouteLocation.ts similarity index 100% rename from web/src/composition/useRouteLocation.ts rename to web__old/src/composition/useRouteLocation.ts diff --git a/web/src/composition/useScrollToBottom.ts b/web__old/src/composition/useScrollToBottom.ts similarity index 100% rename from web/src/composition/useScrollToBottom.ts rename to web__old/src/composition/useScrollToBottom.ts diff --git a/web/src/composition/useTaskView.ts b/web__old/src/composition/useTaskView.ts similarity index 100% rename from web/src/composition/useTaskView.ts rename to web__old/src/composition/useTaskView.ts diff --git a/web/src/composition/useTaskViewMainUrl.ts b/web__old/src/composition/useTaskViewMainUrl.ts similarity index 100% rename from web/src/composition/useTaskViewMainUrl.ts rename to web__old/src/composition/useTaskViewMainUrl.ts diff --git a/web/src/composition/useTextFieldStyles.ts b/web__old/src/composition/useTextFieldStyles.ts similarity index 100% rename from web/src/composition/useTextFieldStyles.ts rename to web__old/src/composition/useTextFieldStyles.ts diff --git a/web/src/composition/useTransactionType.ts b/web__old/src/composition/useTransactionType.ts similarity index 100% rename from web/src/composition/useTransactionType.ts rename to web__old/src/composition/useTransactionType.ts diff --git a/web/src/composition/useTvFormatDate.ts b/web__old/src/composition/useTvFormatDate.ts similarity index 100% rename from web/src/composition/useTvFormatDate.ts rename to web__old/src/composition/useTvFormatDate.ts diff --git a/web/src/composition/useUpdater.ts b/web__old/src/composition/useUpdater.ts similarity index 100% rename from web/src/composition/useUpdater.ts rename to web__old/src/composition/useUpdater.ts diff --git a/web/src/composition/useWidgetButtonStyles.ts b/web__old/src/composition/useWidgetButtonStyles.ts similarity index 100% rename from web/src/composition/useWidgetButtonStyles.ts rename to web__old/src/composition/useWidgetButtonStyles.ts diff --git a/web/src/composition/useWidgetScrollContainer.ts b/web__old/src/composition/useWidgetScrollContainer.ts similarity index 100% rename from web/src/composition/useWidgetScrollContainer.ts rename to web__old/src/composition/useWidgetScrollContainer.ts diff --git a/web__old/src/helpers/AppTypes.ts b/web__old/src/helpers/AppTypes.ts new file mode 100644 index 0000000..8e0aac5 --- /dev/null +++ b/web__old/src/helpers/AppTypes.ts @@ -0,0 +1,86 @@ +export type RestaurantContacts = { + email: string; + phone: string; + address: string; + workStart: string; + workEnd: string; +}; + +export type RestaurantItem = { + id: number; + name: string; + owner: number; + date: string; + currency: string; + contacts: RestaurantContacts; + restaurantLogin: string; +}; +export type RestaurantItems = RestaurantItem[]; + +export type UpdateRestaurantInfoResponse = { + restaurant: RestaurantItem; + update: boolean; + updateContacts: boolean; +}; +export type AppConfigResponse = { + response: { + namespace: string; + updateBlockedInterval: number; + }; + rid: string | null; +}; + +export type JWTPayload = { + exp: number; + id: number; + type: 'jwt'; + userData: { + email: string; + id: number; + login: string; + permissions: { + [key: string]: { + id: number; + name: string; + description: string; + }; + }; + }; +}; + +export type RefreshTokenResponse = { + access: string; + refresh: string; +}; + +export interface VuetifyForm { + validate(): boolean; +} + +// export type AppResponse = { +// response: S +// rid: string +// }; + +export type FormFieldRules = ((v: string) => true | string)[]; + +export type RegistrationResult = { + registration: boolean; + confirmEmail: boolean; +}; + +export type AppCredentialsFormTabs = { title: string; component: string; recovery: boolean }[]; + +export type RecoveryRequestResponse = { sent: boolean }; + +export type ResetPasswordResponse = { reset: boolean }; + +export type AppLanguages = { id: string; title: string }[]; + +export type AppVersionsData = { [key: string]: { date: string; description: string; items: string[] } }; + +export type VuetifyHeaderItem = { text: string; value: string; sortable?: boolean; align?: string }; + +export type VuetifyHeaderItems = VuetifyHeaderItem[]; + +export const IMAGE_HEIGHT = 290; diff --git a/web__old/src/helpers/Helper.ts b/web__old/src/helpers/Helper.ts new file mode 100644 index 0000000..5a964d9 --- /dev/null +++ b/web__old/src/helpers/Helper.ts @@ -0,0 +1,51 @@ +export function parseJwt(token: string): R | undefined { + if (token.split('.').length === 3) { + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const jsonPayload = decodeURIComponent( + atob(base64) + .split('') + .map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`) + .join('') + ); + return JSON.parse(jsonPayload); + } +} + +export function isEmail(email: string): boolean { + const re = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(String(email).toLowerCase()); +} + +export function validLogin(login: string) { + const re = /^[a-z0-9]{4,30}$/; + return re.test(String(login).toLowerCase()); +} + +export function validCompanyLogin(login: string) { + const re = /^[a-z0-9-]{4,30}$/; + return re.test(String(login).toLowerCase()) && login.lastIndexOf('-') !== login.length - 1; +} + +export function getRandomColor(): string { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +} + +export function isValidHttpUrl(string: string) { + let url: URL; + + try { + url = new URL(string); + //eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_) { + return false; + } + + return url.protocol === 'http:' || url.protocol === 'https:'; +} diff --git a/web__old/src/helpers/LocalStorage.ts b/web__old/src/helpers/LocalStorage.ts new file mode 100644 index 0000000..71666e1 --- /dev/null +++ b/web__old/src/helpers/LocalStorage.ts @@ -0,0 +1,114 @@ +import { Preferences } from '@capacitor/preferences'; +import type { AxiosInstance } from 'axios'; +import type { JWTPayload } from '@/helpers/AppTypes'; +import { parseJwt } from '@/helpers/Helper'; +import { useUserStore } from '@/stores/user.store'; + +export const ACCESS_TOKEN_KEY = 'access'; +export const REFRESH_TOKEN_KEY = 'refresh'; + +export default class LocalStorage { + protected namespace: string = ''; + + protected axios!: AxiosInstance; + + public isLoggedIn: boolean = false; + + protected userStore!: ReturnType; + + constructor(options: { namespace: string; axios: AxiosInstance }) { + this.namespace = options.namespace; + this.axios = options.axios; + this.getToken().then(async (token) => { + this.isLoggedIn = !!token; + this.userStore = useUserStore(); + this.userStore.loading = false; + await this.updateUserStoreByToken(); + }); + } + + private key(key: string): string { + return `${this.namespace}.${key}`; + } + + async getValue(key: string): Promise { + await Preferences.configure({ group: `store_${this.namespace}` }); + return (await Preferences.get({ key: this.key(key) })).value; + } + + async removeKey(key: string) { + await Preferences.configure({ group: `store_${this.namespace}` }); + await Preferences.remove({ key: this.key(key) }); + } + + //eslint-disable-next-line @typescript-eslint/no-explicit-any + async setValue(key: string, value: { [key: string]: any } | string): Promise { + await Preferences.configure({ group: `store_${this.namespace}` }); + if (typeof value !== 'string') { + value = JSON.stringify(value); + } + Preferences.set({ + key: this.key(key), + value: value, + }); + } + + setToken(token: string): void { + this.setValue(ACCESS_TOKEN_KEY, token); + this.checkTokenAndSetForAxios(); + this.isLoggedIn = true; + } + + setRefreshToken(refreshToken: string): void { + this.setValue(REFRESH_TOKEN_KEY, refreshToken); + } + + async getToken(): Promise { + const token = await this.getValue(ACCESS_TOKEN_KEY); + if (token && token.split('.').length === 3) { + return token; + } + return null; + } + + async getRefreshToken(): Promise { + return await this.getValue(REFRESH_TOKEN_KEY); + } + + async checkTokenAndSetForAxios(): Promise { + const token = await this.getToken(); + if (token) { + this.axios.defaults.headers.common.Authorization = `Bearer ${token}`; + } + } + + async invalidateTokens() { + await this.removeKey(REFRESH_TOKEN_KEY); + await this.removeKey(ACCESS_TOKEN_KEY); + delete this.axios.defaults.headers.common.Authorization; + this.isLoggedIn = false; + if (this.userStore) { + this.userStore.setAccessToken(''); + this.userStore.setRefreshToken(''); + this.userStore.setLogin(''); + this.userStore.setEmail(''); + } + } + + async updateUserStoreByToken() { + const accessToken = await this.getToken(); + if (accessToken && this.userStore) { + this.userStore.setAccessToken(accessToken); + const refreshToken = await this.getRefreshToken(); + if (refreshToken) { + this.userStore.setRefreshToken(refreshToken); + } + + const payload = parseJwt(accessToken); + if (payload) { + this.userStore.setLogin(payload.userData.login); + this.userStore.setEmail(payload.userData.email); + } + } + } +} diff --git a/web/src/helpers/PasswordHelper.ts b/web__old/src/helpers/PasswordHelper.ts similarity index 100% rename from web/src/helpers/PasswordHelper.ts rename to web__old/src/helpers/PasswordHelper.ts diff --git a/web/src/helpers/RequestQueue.ts b/web__old/src/helpers/RequestQueue.ts similarity index 100% rename from web/src/helpers/RequestQueue.ts rename to web__old/src/helpers/RequestQueue.ts diff --git a/web/src/helpers/app-helper.ts b/web__old/src/helpers/app-helper.ts similarity index 100% rename from web/src/helpers/app-helper.ts rename to web__old/src/helpers/app-helper.ts diff --git a/web/src/helpers/at.ts b/web__old/src/helpers/at.ts similarity index 100% rename from web/src/helpers/at.ts rename to web__old/src/helpers/at.ts diff --git a/web__old/src/helpers/axios.ts b/web__old/src/helpers/axios.ts new file mode 100644 index 0000000..23ea5f2 --- /dev/null +++ b/web__old/src/helpers/axios.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; +import { useTaskViewMainUrl } from '@/composition/useTaskViewMainUrl'; + +const $api = axios.create({ + withCredentials: true, + baseURL: useTaskViewMainUrl(), + timeout: 10000, +}); + +export default $api; diff --git a/web/src/lang/en.json b/web__old/src/lang/en.json similarity index 100% rename from web/src/lang/en.json rename to web__old/src/lang/en.json diff --git a/web/src/lang/ru.json b/web__old/src/lang/ru.json similarity index 100% rename from web/src/lang/ru.json rename to web__old/src/lang/ru.json diff --git a/web/src/layout/AdminLayout.vue b/web__old/src/layout/AdminLayout.vue similarity index 100% rename from web/src/layout/AdminLayout.vue rename to web__old/src/layout/AdminLayout.vue diff --git a/web/src/layout/DefaultLayout.ts b/web__old/src/layout/DefaultLayout.ts similarity index 100% rename from web/src/layout/DefaultLayout.ts rename to web__old/src/layout/DefaultLayout.ts diff --git a/web/src/layout/DefaultLayout.vue b/web__old/src/layout/DefaultLayout.vue similarity index 100% rename from web/src/layout/DefaultLayout.vue rename to web__old/src/layout/DefaultLayout.vue diff --git a/web/src/layout/UserLayout.vue b/web__old/src/layout/UserLayout.vue similarity index 100% rename from web/src/layout/UserLayout.vue rename to web__old/src/layout/UserLayout.vue diff --git a/web__old/src/main.ts b/web__old/src/main.ts new file mode 100644 index 0000000..3a0391d --- /dev/null +++ b/web__old/src/main.ts @@ -0,0 +1,24 @@ +import { createPinia } from 'pinia'; +import { createApp } from 'vue'; + +import App from './App.vue'; +import router from './router'; +import '@/helpers/at'; +import api from '@/plugins/axios'; +import i18n from '@/plugins/i18n'; +import vuetify from '@/plugins/vuetify'; + +import './assets/base.css'; +import './assets/app.scss'; + +const app = createApp(App); + +app.use(createPinia()); +app.use(router); +app.use(api); +app.use(i18n); +app.use(vuetify); + +app.mount('#app'); + +export { app }; diff --git a/web__old/src/middleware/authenticated.ts b/web__old/src/middleware/authenticated.ts new file mode 100644 index 0000000..90974dd --- /dev/null +++ b/web__old/src/middleware/authenticated.ts @@ -0,0 +1,10 @@ +import { $ls } from '@/plugins/axios'; + +export default async function authenticated() { + await $ls.updateUserStoreByToken(); + if (!(await $ls.getToken())) { + console.warn('authenticated'); + return { name: 'login' }; + } + return true; +} diff --git a/web/src/pages/AdminPage/AdminPage.ts b/web__old/src/pages/AdminPage/AdminPage.ts similarity index 100% rename from web/src/pages/AdminPage/AdminPage.ts rename to web__old/src/pages/AdminPage/AdminPage.ts diff --git a/web/src/pages/AdminPage/AdminPage.vue b/web__old/src/pages/AdminPage/AdminPage.vue similarity index 100% rename from web/src/pages/AdminPage/AdminPage.vue rename to web__old/src/pages/AdminPage/AdminPage.vue diff --git a/web/src/pages/AdminPage/index.ts b/web__old/src/pages/AdminPage/index.ts similarity index 100% rename from web/src/pages/AdminPage/index.ts rename to web__old/src/pages/AdminPage/index.ts diff --git a/web/src/pages/IndexPage/IndexPage.vue b/web__old/src/pages/IndexPage/IndexPage.vue similarity index 100% rename from web/src/pages/IndexPage/IndexPage.vue rename to web__old/src/pages/IndexPage/IndexPage.vue diff --git a/web/src/pages/IndexPage/index.ts b/web__old/src/pages/IndexPage/index.ts similarity index 100% rename from web/src/pages/IndexPage/index.ts rename to web__old/src/pages/IndexPage/index.ts diff --git a/web/src/pages/LoginPage/LoginPage.vue b/web__old/src/pages/LoginPage/LoginPage.vue similarity index 100% rename from web/src/pages/LoginPage/LoginPage.vue rename to web__old/src/pages/LoginPage/LoginPage.vue diff --git a/web/src/pages/UserPage/UserPage.vue b/web__old/src/pages/UserPage/UserPage.vue similarity index 100% rename from web/src/pages/UserPage/UserPage.vue rename to web__old/src/pages/UserPage/UserPage.vue diff --git a/web/src/pages/UserPage/index.ts b/web__old/src/pages/UserPage/index.ts similarity index 100% rename from web/src/pages/UserPage/index.ts rename to web__old/src/pages/UserPage/index.ts diff --git a/web__old/src/plugins/axios.ts b/web__old/src/plugins/axios.ts new file mode 100644 index 0000000..6bcfda8 --- /dev/null +++ b/web__old/src/plugins/axios.ts @@ -0,0 +1,108 @@ +import qs from 'qs'; +import { TvApi } from 'taskview-api'; +import type { App } from 'vue'; +import type { RefreshTokenResponse } from '@/helpers/AppTypes'; +import $api from '@/helpers/axios'; +import LocalStorage from '@/helpers/LocalStorage'; + +let $ls: LocalStorage; +const $tvApi: TvApi = new TvApi($api); + +const api = { + async install(app: App) { + console.log('Install axios ------------'); + app.config.globalProperties.$ls = new LocalStorage({ + namespace: 'task_view', + axios: $api, + }); + app.config.globalProperties.$axios = $api; + + $ls = app.config.globalProperties.$ls; + + // const apiToken = await $ls.getToken(); + // if (apiToken) { + // $tvApi = new TvApi($api); + // } + + console.debug('Install axios'); + await $ls.checkTokenAndSetForAxios(); + + // TODO move to helpers/axios.ts + const $axios = $api; + let isRefreshing = false; + let failedQueue: { resolve: (value: unknown) => void; reject: (value: unknown) => void }[] = []; + + //eslint-disable-next-line @typescript-eslint/no-explicit-any + const processQueue = (error: any, token: string | null = null) => { + failedQueue.forEach((prom) => { + if (error) { + prom.reject(error); + } else { + prom.resolve(token); + } + }); + failedQueue = []; + }; + + $axios.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + if (error.response.status === 401 && !originalRequest._retry) { + console.debug('We have 401'); + + if (isRefreshing) { + console.debug('isRefreshing token '); + return new Promise((resolve, reject) => { + failedQueue.push({ resolve, reject }); + }) + .then((token) => { + console.debug('We got new token', token); + originalRequest.headers.Authorization = `Bearer ${token}`; + return $axios(originalRequest); + }) + .catch((err) => { + return Promise.reject(err); + }); + } + originalRequest._retry = true; + isRefreshing = true; + const refreshToken = await $ls.getRefreshToken(); + console.log('We get refresh token'); + if (refreshToken) { + return new Promise((resolve, reject) => { + $axios + .post( + '/module/auth/refresh/token', + qs.stringify({ refreshToken }) + ) + .then((data) => { + console.log('We got refresh data tokens', data); + $ls.setToken(data.data.access); + $ls.setRefreshToken(data.data.refresh); + $ls.checkTokenAndSetForAxios(); + $ls.updateUserStoreByToken(); + originalRequest.headers.Authorization = `Bearer ${data.data.access}`; + processQueue(null, data.data.access); + resolve($axios(originalRequest)); + }) + .catch((err) => { + processQueue(err, null); + $ls.invalidateTokens(); + app.config.globalProperties.$router.push('/'); + reject(err); + }) + .finally(() => { + isRefreshing = false; + }); + }); + } + } + return Promise.reject(error); + } + ); + }, +}; + +export { $ls, $tvApi }; +export default api; diff --git a/web__old/src/plugins/i18n.ts b/web__old/src/plugins/i18n.ts new file mode 100644 index 0000000..7c6fd8b --- /dev/null +++ b/web__old/src/plugins/i18n.ts @@ -0,0 +1,20 @@ +import { createI18n } from 'vue-i18n'; +import en from '@/lang/en.json'; +import ru from '@/lang/ru.json'; +import type { TI18nLocalesInApp } from '@/types/app.types'; + +const messages: TI18nLocalesInApp = { + en: en, + ru: ru, +}; +const i18n = createI18n({ + locale: 'ru', + fallbackLocale: 'en', + missingWarn: false, // Отключает предупреждения о пропущенных ключах + fallbackWarn: false, // Отключает предупреждения о fallback + messages, + legacy: false, +}); +export default i18n; +const $i18n = i18n.global; +export { $i18n }; diff --git a/web/src/plugins/vuetify.ts b/web__old/src/plugins/vuetify.ts similarity index 100% rename from web/src/plugins/vuetify.ts rename to web__old/src/plugins/vuetify.ts diff --git a/web/src/router/index.ts b/web__old/src/router/index.ts similarity index 100% rename from web/src/router/index.ts rename to web__old/src/router/index.ts diff --git a/web/src/router/router.types.ts b/web__old/src/router/router.types.ts similarity index 100% rename from web/src/router/router.types.ts rename to web__old/src/router/router.types.ts diff --git a/web/src/shims-vue.d.ts b/web__old/src/shims-vue.d.ts similarity index 100% rename from web/src/shims-vue.d.ts rename to web__old/src/shims-vue.d.ts diff --git a/web__old/src/stores/additional-url.store.ts b/web__old/src/stores/additional-url.store.ts new file mode 100644 index 0000000..091a1c8 --- /dev/null +++ b/web__old/src/stores/additional-url.store.ts @@ -0,0 +1,10 @@ +import { defineStore } from 'pinia'; +import { ref } from 'vue'; + +export const additionalUrlStore = defineStore('additionalUrlStore', () => { + const allServers = ref([]); + const mainServer = ref(''); + const systemServer = ref(''); + + return { allServers, mainServer, systemServer }; +}); diff --git a/web__old/src/stores/app.store.ts b/web__old/src/stores/app.store.ts new file mode 100644 index 0000000..b61c795 --- /dev/null +++ b/web__old/src/stores/app.store.ts @@ -0,0 +1,15 @@ +import { defineStore } from 'pinia'; +import type { AppStoreState } from '@/types/global-app.types'; + +export const useAppStore = defineStore('app', { + state(): AppStoreState { + return { + drawer: false, + }; + }, + actions: { + setDrawer(state: boolean) { + this.drawer = state; + }, + }, +}); diff --git a/web__old/src/stores/base-screen.store.ts b/web__old/src/stores/base-screen.store.ts new file mode 100644 index 0000000..827f170 --- /dev/null +++ b/web__old/src/stores/base-screen.store.ts @@ -0,0 +1,198 @@ +import { useDateFormat } from '@vueuse/core'; +import { defineStore } from 'pinia'; +import { logError } from '@/helpers/app-helper'; +import $api from '@/helpers/axios'; +import { $tvApi } from '@/plugins/axios'; +import { useTagsStore } from '@/stores/tag.store'; +import { + type BaseScreenSearchResponse, + type BaseScreenState, + FILTER_DEFAULT, + type GoalAndListsResp, + type MainScreenAllStateResponse, +} from '@/types/base-screen.types'; +import type { AppResponse } from '@/types/global-app.types'; +import type { TaskItem } from '@/types/tasks.types'; + +export const useBaseScreenStore = defineStore('base-screen-store', { + state(): BaseScreenState { + return { + activeWidgetInMobile: 'today', + wasCalled: false, + tasks: [], //all tasks in the app + tasksToday: [], + tasksUpcoming: [], + tasksLastCompleted: [], + //todo rename "users" to "usersByProject" + users: [], //show users by project + searchTask: '', + // addTaskDialog: false,//shoud we offer add goals + lists: [], + // listToGoal: {}, + loading: false, + //todo rename "assignees" to "assigneesByTask" + assignees: [], + filterAndSorting: { + sort: 'new', + filters: { + ...FILTER_DEFAULT, + }, + }, + taskIdToUser: {}, + }; + }, + actions: { + async fetchAllState() { + this.taskIdToUser = {}; + this.loading = true; + const result = await $api + .get>( + `/module/about/fetchallstate?tz=${encodeURIComponent(Intl.DateTimeFormat().resolvedOptions().timeZone)}` + ) + .catch((err) => console.log(err)); + + const tagsStore = useTagsStore(); + tagsStore.fetchAllTags(); + this.wasCalled = true; + if (result) { + this.tasks = result.data.response.tasks; + this.users = result.data.response.users; + this.tasksToday = result.data.response.tasksToday; + this.tasksUpcoming = result.data.response.tasksUpcoming; + this.tasksLastCompleted = result.data.response.tasksLastCompleted; + // this.listToGoal = result.data.response.listToGoal; + this.assignees = result.data.response.assignees; + + this.assignees.forEach((assignee) => { + if (!this.taskIdToUser[assignee.taskId]) { + this.taskIdToUser[assignee.taskId] = [assignee]; + } else { + this.taskIdToUser[assignee.taskId].push(assignee); + } + }); + } + setTimeout(() => { + this.loading = false; + }, 100); + }, + + async fetchAllAvailableLists() { + const result = await $api + .get>('/module/about/fetch/lists') + .catch((err) => console.log(err)); + if (result) { + this.lists = result.data.response; + } + }, + + async updateTaskStatusNew( + data: { status: boolean; taskId: TaskItem['id'] }, + prop: 'tasksToday' | 'tasksUpcoming' | 'tasksLastCompleted' | 'tasks' + ) { + const task = await $tvApi.tasks.updateTask({ + id: data.taskId, + complete: data.status, + }); + + if (!task) return; + + const localTask = this[prop].find((tsk) => tsk.id === data.taskId); + if (localTask) { + localTask.complete = task.complete; + + switch (prop) { + case 'tasks': + this.processLastAdded(task); + break; + case 'tasksToday': + this.processToday(task); + break; + case 'tasksUpcoming': + this.processUpcoming(task); + break; + case 'tasksLastCompleted': + this.processLastCompleted(task); + break; + } + } + }, + + processLastAdded(task: TaskItem) { + if (task.complete) { + this.tasksLastCompleted.unshift(task); + this.tasks = this.tasks.filter((t) => t.id !== task.id); + } else { + this.processLastCompleted(task); + } + }, + + processUpcoming(task: TaskItem) { + if (task.complete) { + this.tasksLastCompleted.unshift(task); + } else { + this.processLastCompleted(task); + } + }, + + processToday(task: TaskItem) { + if (task.complete) { + this.tasksLastCompleted.unshift(task); + } else { + this.processLastCompleted(task); + } + }, + + processLastCompleted(task: TaskItem) { + if (task.complete) return; + + const deadline = new Date(useDateFormat(new Date(task.endDate || ''), 'YYYY-MM-DD').value); + const today = new Date(useDateFormat(new Date(), 'YYYY-MM-DD').value); + + const removeTask = () => { + this.tasksLastCompleted = this.tasksLastCompleted.filter((t) => t.id !== task.id); + }; + + const key = !task.endDate ? 'tasks' : deadline <= today ? 'tasksToday' : 'tasksUpcoming'; + + const existingTask = this[key].find((tsk) => tsk.id === task.id); + + if (existingTask) { + existingTask.complete = task.complete; + } else { + this[key].unshift(task); + } + + removeTask(); + }, + + async updateTaskStatus(taskId: TaskItem['id']) { + const task = await $tvApi.tasks.updateTask({ + id: taskId, + complete: true, + }); + + if (!task) return; + + const index = this.tasks.findIndex((tsk) => tsk.id === taskId); + if (index !== -1) { + this.tasks.splice(index, 1); + } + }, + + async searchTaskRequest(taskDescription: string): Promise { + const result = await $api + .get>(`/module/about/search-task?description=${taskDescription}`) + .catch(logError); + if (!result) return []; + return result.data.response; + }, + + async removeTasksByGoalId(goalId: number) { + const filterFunc = (task: TaskItem) => task.goalId !== goalId; + this.tasks = this.tasks.filter(filterFunc); + this.tasksToday = this.tasksToday.filter(filterFunc); + this.tasksUpcoming = this.tasksUpcoming.filter(filterFunc); + this.tasksLastCompleted = this.tasksLastCompleted.filter(filterFunc); + }, + }, +}); diff --git a/web__old/src/stores/collaboration-permissions.store.ts b/web__old/src/stores/collaboration-permissions.store.ts new file mode 100644 index 0000000..8dc8142 --- /dev/null +++ b/web__old/src/stores/collaboration-permissions.store.ts @@ -0,0 +1,36 @@ +import { defineStore } from 'pinia'; +import { $tvApi } from '@/plugins/axios'; +import type { CollaborationPermissionsState, PermissioinGroupsIds } from '@/types/collaboration-permissions.types'; + +export const useCollaborationPermissionsStore = defineStore('collaboration-permissions', { + state(): CollaborationPermissionsState { + return { + permissions: [], + }; + }, + getters: { + groupedPermissions(state): Record { + const map = new Map(); + state.permissions.forEach((perm) => { + const arr = map.get(perm.permission_group); + if (!arr) { + map.set(perm.permission_group, [perm]); + } else { + arr.push(perm); + } + }); + + return Object.fromEntries(map) as Record< + PermissioinGroupsIds, + CollaborationPermissionsState['permissions'] + >; + }, + }, + actions: { + async fetchAllPermissions(): Promise { + const result = await $tvApi.collaboration.fetchAllPermissions().catch((err) => console.log(err)); + if (!result) return; + this.permissions = result; + }, + }, +}); diff --git a/web__old/src/stores/collaboration-roles.store.ts b/web__old/src/stores/collaboration-roles.store.ts new file mode 100644 index 0000000..4698d18 --- /dev/null +++ b/web__old/src/stores/collaboration-roles.store.ts @@ -0,0 +1,75 @@ +import { defineStore } from 'pinia'; +import type { + CollaborationArgCreateRoleForGoal, + CollaborationArgDeleteRoleFromGoal, + CollaborationArgToggleRolePermission, + GoalItem, +} from 'taskview-api'; +import { $tvApi } from '@/plugins/axios'; +import type { CollaborationRolesStore } from '@/types/collaboration-roles.types'; + +export const useCollaborationRolesStore = defineStore('collaborationRoles', { + state(): CollaborationRolesStore { + return { + roles: [], + rolesPermissions: {}, + }; + }, + + actions: { + async fetchCollaborationRolesForGoal(goalId: GoalItem['id']): Promise { + const roles = await $tvApi.collaboration.fetchRolesForGoal(goalId); + if (!roles) return; + this.roles = roles; + }, + + async addCollaborationRole(data: CollaborationArgCreateRoleForGoal): Promise { + const role = await $tvApi.collaboration.createRoleForGoal(data); + if (!role) return; + + this.roles.push(role); + + if (!this.rolesPermissions[role.id]) { + this.rolesPermissions[role.id] = {}; + } + }, + + async deleteCollaborationRole(data: CollaborationArgDeleteRoleFromGoal): Promise { + const result = await $tvApi.collaboration.deleteRoleFromGoal(data); + if (!result) return; + + const index = this.roles.findIndex((role) => role.id === data.id); + if (index !== -1) { + this.roles.splice(index, 1); + } + }, + + async fetchAllRolePermissionsForGoal(goalId: GoalItem['id']): Promise { + const result = await $tvApi.collaboration.fetchRoleToPermissionsForGoal(goalId); + if (!result) return; + + result.forEach((item) => { + if (!item.role_id || !item.permission_id) return; + + if (!this.rolesPermissions[item.role_id]) { + this.rolesPermissions[item.role_id] = {}; + } + this.rolesPermissions[item.role_id][item.permission_id] = true; + }); + }, + + async togglePermissionForRole(data: CollaborationArgToggleRolePermission) { + const result = await $tvApi.collaboration.toggleRolePermission(data); + if (!result) return; + + if (result.add) { + if (!this.rolesPermissions[data.roleId]) { + this.rolesPermissions[data.roleId] = {}; + } + this.rolesPermissions[data.roleId][data.permissionId] = true; + } else { + delete this.rolesPermissions[data.roleId][data.permissionId]; + } + }, + }, +}); diff --git a/web__old/src/stores/collaboration.store.ts b/web__old/src/stores/collaboration.store.ts new file mode 100644 index 0000000..e14359d --- /dev/null +++ b/web__old/src/stores/collaboration.store.ts @@ -0,0 +1,70 @@ +import { defineStore } from 'pinia'; +import type { + CollaborationArgAddUser, + CollaborationArgDeleteUser, + CollaborationArgToggleUserRoles, + GoalItem, +} from 'taskview-api'; +import { $tvApi } from '@/plugins/axios'; +import type { CollaborationStore } from '@/types/collaboration.types'; + +export const useCollaborationStore = defineStore('collaboration', { + state(): CollaborationStore { + return { + users: [], + }; + }, + + getters: { + userMap: (state) => { + return new Map(state.users.map((user) => [user.id, user])); + }, + }, + + actions: { + /** + * Fetch all users for collaboration + * we use it in the main screen to show the users + * in the assignees section for tasks + */ + async fetchAllCollaborationUsers(): Promise { + const users = await $tvApi.collaboration.fetchAllUsers(); + if (!users) return; + this.users = users; + }, + + /** + * Fetch users for a specific goal for collaboration section + */ + async fetchCollaborationUsersForGoal(goalId: GoalItem['id']): Promise { + const users = await $tvApi.collaboration.fetchUsersForGoal(goalId); + if (!users) return; + this.users = users; + }, + + async addCollaborationUser(data: CollaborationArgAddUser): Promise { + const result = await $tvApi.collaboration.inviteUserToGoal(data); + if (!result) return false; + this.users.push(result); + return true; + }, + + async deleteUserFromCollaboration(data: CollaborationArgDeleteUser): Promise { + const result = await $tvApi.collaboration.deleteUserFromGoal(data); + if (!result) return; + const index = this.users.findIndex((usr) => usr.id === data.id); + if (index !== -1) { + this.users.splice(index, 1); + } + }, + + async toggleUserRole(data: CollaborationArgToggleUserRoles) { + const roles = await $tvApi.collaboration.toggleUserRoles(data); + if (!roles) return; + const user = this.users.find((us) => us.id === data.userId); + if (user && roles) { + user.roles = roles; + } + }, + }, +}); diff --git a/web__old/src/stores/goal-lists.store.ts b/web__old/src/stores/goal-lists.store.ts new file mode 100644 index 0000000..48128c8 --- /dev/null +++ b/web__old/src/stores/goal-lists.store.ts @@ -0,0 +1,73 @@ +import { defineStore } from 'pinia'; +import type { + GoalListItemArgAdd, + GoalListItemArgDelete, + GoalListItemArgFetch, + GoalListItemArgUpdate, +} from 'taskview-api'; +import { $tvApi } from '@/plugins/axios'; +import type { GoalListsStoreState } from '@/types/goal-lists.types'; +import { useGoalsStore } from './goals.store'; + +export const useGoalListsStore = defineStore('goal-lists', { + state: (): GoalListsStoreState => ({ + currentGoalId: -1, + lists: [], + loading: false, + }), + getters: { + listMap: (state) => { + return new Map(state.lists.map((list) => [list.id, list])); + }, + }, + actions: { + async addList(listItem: GoalListItemArgAdd) { + this.loading = true; + const list = await $tvApi.goalLists.createList(listItem).catch(console.error); + this.loading = false; + if (list) { + this.lists.unshift(list); + return true; + } + return false; + }, + + async fetchLists(goalId: GoalListItemArgFetch['goalId']) { + this.loading = true; + const goalsStore = useGoalsStore(); + goalsStore.selectedItemId = goalId; + this.currentGoalId = goalId; + this.lists = []; + const lists = await $tvApi.goalLists.fetchLists({ goalId }).catch(console.error); + this.loading = false; + this.lists = lists ?? []; + }, + + async deleteList(listId: GoalListItemArgDelete) { + this.loading = true; + const result = await $tvApi.goalLists.deleteList(listId).catch(console.error); + this.loading = false; + if (!result) return false; + const index = this.lists.findIndex((item) => +item.id === +listId); + if (index !== -1) { + this.lists.splice(index, 1); + } + }, + + async updateList(listData: GoalListItemArgUpdate) { + this.loading = true; + const list = await $tvApi.goalLists.updateList(listData).catch(console.error); + this.loading = false; + + if (!list) return false; + + const index = this.lists.findIndex((item) => +item.id === +listData.id); + if (index !== -1) { + this.lists[index] = list; + return true; + } + + return false; + }, + }, +}); diff --git a/web__old/src/stores/goals.store.ts b/web__old/src/stores/goals.store.ts new file mode 100644 index 0000000..39f1b96 --- /dev/null +++ b/web__old/src/stores/goals.store.ts @@ -0,0 +1,69 @@ +import { defineStore } from 'pinia'; +import type { GoalArgItemAdd, GoalArgItemUpdate, GoalItem } from 'taskview-api'; +import { $tvApi } from '@/plugins/axios'; +import type { GoalsStoreState } from '@/types/goals.types'; +import { useUserStore } from './user.store'; + +export const useGoalsStore = defineStore('goals', { + state: (): GoalsStoreState => ({ + loading: false, + selectedItemId: -1, + goals: [], + }), + getters: { + goalMap: (state) => { + return new Map(state.goals.map((goal) => [goal.id, goal])); + }, + + selectedGoal: (state) => state.goals.find((goal) => goal.id === state.selectedItemId), + + amIOwner(): boolean { + const userStore = useUserStore(); + return this.selectedGoal?.owner === userStore.payloadData?.id; + }, + + goalIdToName(): Record { + const map: Record = {}; + this.goals.forEach((g) => { + map[g.id] = g.name; + }); + return map; + }, + }, + actions: { + async fetchGoals(): Promise { + this.loading = true; + const result = await $tvApi.goals.fetchGoals(); + if (!result) return; + this.goals = result || []; + this.loading = false; + }, + + async addGoal(goal: GoalArgItemAdd): Promise { + const goalWithPermissions = await $tvApi.goals.createGoal(goal); + if (!goalWithPermissions) return false; + this.goals.unshift(goalWithPermissions); + return true; + }, + + async updateGoal(goal: GoalArgItemUpdate): Promise { + const goalWithPermissions = await $tvApi.goals.updateGoal(goal); + if (!goalWithPermissions) return false; + const index = this.goals.findIndex((g) => +g.id === +goal.id); + if (index !== -1) { + this.goals[index] = goalWithPermissions; + } + return true; + }, + + async deleteGoal(goalId: GoalItem['id']): Promise { + const result = await $tvApi.goals.deleteGoal(goalId); + if (!result) return; + const index = this.goals.findIndex((g) => +g.id === +goalId); + if (index !== -1) { + this.goals.splice(index, 1); + } + return; + }, + }, +}); diff --git a/web__old/src/stores/graph.store.ts b/web__old/src/stores/graph.store.ts new file mode 100644 index 0000000..4dca2a8 --- /dev/null +++ b/web__old/src/stores/graph.store.ts @@ -0,0 +1,96 @@ +import { defineStore } from 'pinia'; +import type { GoalItem, GraphArgAddEdge, TaskArgUpdate } from 'taskview-api'; +import { logError } from '@/helpers/app-helper'; +import { $tvApi } from '@/plugins/axios'; +import type { GraphStore } from '@/types/graph.types'; +import type { TaskItem } from '@/types/tasks.types'; +import { useGoalListsStore } from './goal-lists.store'; +import { useTasksStore } from './tasks.store'; + +export const useGraphStore = defineStore('use-graph-store', { + state: (): GraphStore => { + return { + nodes: [], + edges: [], + }; + }, + actions: { + getStores() { + return { + tasksStore: useTasksStore(), + goalListsStore: useGoalListsStore(), + }; + }, + + async fetchAllTasksAndLists(goalId: GoalItem['id']): Promise { + const { tasksStore, goalListsStore } = this.getStores(); + + goalListsStore.currentGoalId = goalId; + await Promise.all([ + goalListsStore.fetchLists(goalListsStore.currentGoalId), + tasksStore.fetchAllTasks(goalListsStore.currentGoalId), + ]); + + this.nodes = tasksStore.tasks.map((task) => ({ + id: task.id.toString(), + data: { task, label: `${task.description} == ${task.id}` }, + position: task.nodeGraphPosition ?? { x: 0, y: 0 }, + type: 'task', + style: { resize: 'none' }, + })); + await this.fetchAllEdges(goalId); + }, + + async addNode(task: TaskItem) { + this.nodes.push({ + id: task.id.toString(), + data: { task, label: `${task.description} == ${task.id}` }, + position: task.nodeGraphPosition ?? { x: 0, y: 0 }, + type: 'task', + style: { resize: 'none' }, + }); + return task.id.toString(); + }, + + async addEdge(data: GraphArgAddEdge) { + const result = await $tvApi.graph.addEdge(data).catch((err: unknown) => logError(err)); + if (!result) return; + + const edge = result; + + const newEdge = { + id: edge.id.toString(), + source: edge.fromTaskId.toString(), + target: edge.toTaskId.toString(), + }; + + this.edges.push(newEdge); + + return newEdge; + }, + + async fetchAllEdges(goalId: number) { + const result = await $tvApi.graph.fetchAllEdges(goalId).catch((err: unknown) => logError(err)); + if (!result) return; + this.edges = result.map((edge) => ({ + id: edge.id.toString(), + source: edge.fromTaskId.toString(), + target: edge.toTaskId.toString(), + })); + }, + + async deleteEdge(id: number) { + const result = await $tvApi.graph.deleteEdge(id).catch((err: unknown) => logError(err)); + if (!result) return; + const index = this.edges.findIndex((edge) => edge.id === id.toString()); + if (index !== -1) { + this.edges.splice(index, 1); + } + }, + + async updateTaskPosition(data: TaskArgUpdate) { + const result = await $tvApi.tasks.updateTask(data).catch((err: unknown) => logError(err)); + if (!result) return; + }, + }, +}); diff --git a/web__old/src/stores/kanban.store.ts b/web__old/src/stores/kanban.store.ts new file mode 100644 index 0000000..3663ec7 --- /dev/null +++ b/web__old/src/stores/kanban.store.ts @@ -0,0 +1,155 @@ +import { defineStore } from 'pinia'; +import type { GoalItem, KanbanArgDeleteColumn, KanbanArgUpdateColumn, KanbanArgUpdateTasksOrder, KanbanColumnItem, Task, TaskArgAdd } from 'taskview-api'; +import { DEFAULT_ID } from '@/types/app.types'; +import type { KanbanStoreState } from '@/types/kanban.types'; +import { useTasksStore } from './tasks.store'; +import { $tvApi } from '@/plugins/axios'; +import type { KanbanArgAddColumn } from 'taskview-api'; + +const tasksStore = useTasksStore(); + +export const useKanbanStore = defineStore('kanban', { + state(): KanbanStoreState { + return { + tasksData: {}, + statuses: [], + goalId: DEFAULT_ID, + }; + }, + actions: { + async fetchTasksForColumn(goalId: GoalItem['id'], columnId: KanbanColumnItem['id'] | null, cursor: string | number | null) { + const result = await $tvApi.kanban.fetchTasksForColumn(goalId, columnId, cursor).catch(console.error); + + if (!result) { + if (!this.tasksData[columnId ?? DEFAULT_ID]) { + this.tasksData[columnId ?? DEFAULT_ID] = { + tasks: [], + nextCursor: null, + columnVersion: null, + }; + } + return; + } + + if (!this.tasksData[columnId ?? DEFAULT_ID]) { + this.tasksData[columnId ?? DEFAULT_ID] = { + tasks: [], + nextCursor: null, + columnVersion: null, + }; + } + + this.tasksData[columnId ?? DEFAULT_ID]!.tasks.push(...result.tasks); + this.tasksData[columnId ?? DEFAULT_ID]!.nextCursor = result.nextCursor; + this.tasksData[columnId ?? DEFAULT_ID]!.columnVersion = result.columnVersion; + }, + + // async fetchTasksOrderForColumnAndCursor(goalId: GoalItem['id'], columnId: Task['statusId'], cursor: string | number | null) { + // const result = await $tvApi.kanban.getTasksOrderForColumnAndCursor(goalId, columnId, cursor).catch(console.error); + // if (!result) { + // return; + // } + + // result.forEach((task) => { + // const localTask = this.tasksData[columnId ?? DEFAULT_ID]?.tasks.find((t) => t.id === task.id); + // if (!localTask) return; + // localTask.kanbanOrder = task.kanbanOrder; + // }); + // }, + + async updateTasksOrderAndColumn(data: KanbanArgUpdateTasksOrder) { + const result = await $tvApi.kanban.updateTasksOrderAndColumn(data).catch(console.error); + + if (!result) { + return; + } + + result.tasks.forEach((task) => { + const localTask = this.tasksData[data.columnId ?? DEFAULT_ID]?.tasks.find((t) => t.id === task.id); + if (!localTask) return; + localTask.kanbanOrder = task.kanbanOrder; + }); + + // if the column version is different, fetch the tasks again + if (this.tasksData[data.columnId ?? DEFAULT_ID]!.columnVersion !== result.columnVersion) { + this.tasksData[data.columnId ?? DEFAULT_ID]!.tasks = []; + this.tasksData[data.columnId ?? DEFAULT_ID]!.nextCursor = null; + this.tasksData[data.columnId ?? DEFAULT_ID]!.columnVersion = null; + await this.fetchTasksForColumn(this.goalId, data.columnId ?? null, null); + } + + this.tasksData[data.columnId ?? DEFAULT_ID]!.columnVersion = result.columnVersion; + }, + + async addTask(taskArg: TaskArgAdd) { + const result = await $tvApi.tasks.createTask(taskArg).catch(console.error); + + if (!result) { + return; + } + + this.tasksData[taskArg.statusId ?? DEFAULT_ID]?.tasks.unshift(result); + }, + + async fetchStatuses(goalId: GoalItem['id']) { + const result = await $tvApi.kanban.fetchAllColumns(goalId); + if (!result) { + return; + } + this.statuses = result; + this.statuses.unshift({ + id: DEFAULT_ID, + name: 'msg.allTasks', + goalId: this.goalId, + viewOrder: 0, + }); + }, + + async addStatus(data: KanbanArgAddColumn) { + const result = await $tvApi.kanban.addColumn(data); + if (!result) { + return; + } + this.statuses.push(result); + + this.tasksData[result.id] = { + tasks: [], + nextCursor: null, + columnVersion: null, + }; + }, + + async updateStatus(data: KanbanArgUpdateColumn) { + const result = await $tvApi.kanban.updateColumn(data); + if (!result) { + return; + } + + const index = this.statuses.findIndex((stat) => stat.id === data.id); + + if (index !== -1) { + this.statuses[index] = { ...this.statuses[index], ...data }; + } + }, + + async deleteStatus(data: KanbanArgDeleteColumn) { + const result = await $tvApi.kanban.deleteColumn(data); + if (!result) { + return; + } + const index = this.statuses.findIndex((stat) => stat.id === data.id); + + if (index !== -1) { + this.statuses.splice(index, 1); + } + + this.tasksData[data.id]?.tasks.forEach((t) => { + if (t.statusId === data.id) { + t.statusId = null; + } + }); + + delete this.tasksData[data.id]; + }, + }, +}); diff --git a/web__old/src/stores/tag.store.ts b/web__old/src/stores/tag.store.ts new file mode 100644 index 0000000..9cf1748 --- /dev/null +++ b/web__old/src/stores/tag.store.ts @@ -0,0 +1,109 @@ +import { defineStore } from 'pinia'; +import type { TagItemArgAdd, TagItemArgToggle, TagItemArgUpdate, TagItemResponseToggle } from 'taskview-api'; +import { $tvApi } from '@/plugins/axios'; +import type { TagItem, TagsState } from '@/types/tags.types'; +import { useGoalListsStore } from './goal-lists.store'; +import { useGoalsStore } from './goals.store'; + +export const useTagsStore = defineStore('tags', { + state(): TagsState { + return { + tags: [], + tagsMap: new Map(), + loading: false, + }; + }, + + getters: { + filteredTags(state) { + const goalListStore = useGoalListsStore(); + const goalsStore = useGoalsStore(); + return state.tags.filter( + (tag) => + (tag.goalId === null || tag.goalId === goalListStore.currentGoalId) && + tag.owner === goalsStore.selectedGoal?.owner + ); + }, + }, + actions: { + async fetchAllTags(): Promise { + this.loading = true; + + const tags = await $tvApi.tags + .fetchAllTagsForUser() + .catch((err) => console.log(err)) + .finally(() => { + this.loading = false; + }); + + if (!tags) { + return; + } + this.tags = tags; + + this.tags.forEach((tag) => { + this.tagsMap.set(tag.id, tag); + }); + }, + + async addTag(tagData: TagItemArgAdd): Promise { + this.loading = true; + const result = await $tvApi.tags + .createTag(tagData) + .catch((err) => console.log(err)) + .finally(() => { + this.loading = false; + }); + if (!result) return false; + this.tags.push(result); + this.tagsMap.set(result.id, result); + return true; + }, + + async deleteTag(tagId: TagItem['id']): Promise { + this.loading = true; + + const deleteResult = await $tvApi.tags + .deleteTag({ tagId }) + .catch((err) => console.log(err)) + .finally(() => { + this.loading = false; + }); + + if (!deleteResult) return; + + const index = this.tags.findIndex((tag) => +tag.id === +tagId); + if (index === -1) return; + + this.tagsMap.delete(this.tags[index].id); + this.tags.splice(index, 1); + }, + + async toggleTag(data: TagItemArgToggle): Promise { + const result = await $tvApi.tags + .toggleTag(data) + .catch(console.error) + .finally(() => { + this.loading = false; + }); + + if (!result) return undefined; + + return result.action; + }, + + async updateTag(newTag: TagItemArgUpdate) { + this.loading = true; + const updateResult = await $tvApi.tags + .updateTag(newTag) + .catch((err) => console.log(err)) + .finally(() => { + this.loading = false; + }); + if (!updateResult) return; + const tag = this.tags.find((tag) => tag.id === newTag.id); + if (!tag) return; + Object.assign(tag, newTag); + }, + }, +}); diff --git a/web__old/src/stores/task-history.store.ts b/web__old/src/stores/task-history.store.ts new file mode 100644 index 0000000..3993281 --- /dev/null +++ b/web__old/src/stores/task-history.store.ts @@ -0,0 +1,26 @@ +import { defineStore } from 'pinia'; +import { $tvApi } from '@/plugins/axios'; +import type { TaskHistoryState } from '@/types/task-history.types'; +import type { TaskItem } from '@/types/tasks.types'; + +export const useTaskHistory = defineStore('taskHistory', { + state(): TaskHistoryState { + return { + history: [], + }; + }, + + actions: { + async fetchHistoryForTask(taskId: TaskItem['id']): Promise { + const result = await $tvApi.tasks.fetchTaskHistory(taskId); + if (!result) return; + this.history = result.history; + }, + + async recoverState(historyId: number, taskId: TaskItem['id']): Promise { + const result = await $tvApi.tasks.recoveryTaskHistory(historyId, taskId); + if (!result) return false; + return result.recovery; + }, + }, +}); diff --git a/web__old/src/stores/tasks.store.ts b/web__old/src/stores/tasks.store.ts new file mode 100644 index 0000000..86f462a --- /dev/null +++ b/web__old/src/stores/tasks.store.ts @@ -0,0 +1,521 @@ +import { defineStore } from 'pinia'; +import type { + TagItemArgToggle, + Task, + TaskArgAdd, + TaskArgDelete, + TaskArgToggleAssignee, + TaskArgUpdate, + TaskBase, +} from 'taskview-api'; +import { delay, isNotNullable, logError } from '@/helpers/app-helper'; +import { $tvApi } from '@/plugins/axios'; +import { DEFAULT_ID } from '@/types/app.types'; +import { ALL_TASKS_LIST_ID, type TasksStoreState } from '@/types/tasks.types'; +import { useTagsStore } from './tag.store'; + +export const useTasksStore = defineStore('tasks', { + state(): TasksStoreState { + return { + tasks: [], + selectedTask: null, + fetchRules: { + showCompleted: 0, + currentListId: DEFAULT_ID, + endOfTasks: false, + currentPage: 0, + searchText: '', + firstNew: 1, + filters: {}, + goalId: -1, + }, + loading: false, + }; + }, + actions: { + updateFetchRules(data: Partial) { + this.fetchRules = { ...this.fetchRules, ...data }; + }, + + resetTasks() { + this.tasks = []; + }, + + updateSelectedTask(task: Task) { + if (this.selectedTask && this.selectedTask.id === task.id) { + Object.assign(this.selectedTask, task); + } + }, + + async fetchTaskById(taskId: TaskBase['id']) { + if (taskId === this.selectedTask?.id) return; + + const task = await $tvApi.tasks.fetchTaskById(taskId).catch(logError); + + if (!task) return; + + this.selectedTask = task; + }, + + /** @deprecated */ + setTaskOrder(data: Partial) { + const task = this.tasks.find((t) => data.id === t.id); + if (!task) return; + + if (isNotNullable(data.taskOrder)) { + task.taskOrder = data.taskOrder; + } + if (isNotNullable(data.kanbanOrder)) { + task.kanbanOrder = data.kanbanOrder; + } + }, + + // setTaskStatusId(data: TaskUpdateStatusIdArg) { + + // if (data.taskId === this.selectedTask?.id) { + // this.selectedTask.statusId = data.statusId; + // } + + // const task = this.tasks.find((tsk) => tsk.id === data.taskId); + + // if (!task) return; + + // task.statusId = data.statusId; + // this.updateSelectedTask(task); + // }, + + /** @deprecated */ + async updateOrders(data: TaskArgUpdate): Promise { + const task = await $tvApi.tasks.updateTask(data).catch(logError); + if (!task) return null; + + this.setTaskOrder(task); + return task; + }, + + async addTask(taskArg: TaskArgAdd): Promise { + if (taskArg.goalListId === ALL_TASKS_LIST_ID) { + taskArg.goalListId = null; + } + + const task = await $tvApi.tasks.createTask(taskArg).catch(logError); + if (!task) return false; + + if (task.parentId) { + let mainTask = this.tasks.find((t) => t.id === task.parentId); + + if (!mainTask && this.selectedTask?.id === task.parentId) { + mainTask = this.selectedTask + } + + if (!mainTask) { + throw new Error('Main task not found for adding subtask'); + } + + if (mainTask) { + mainTask.subtasks.push(task); + } + + if (this.selectedTask?.id === task.parentId) { + this.selectedTask.subtasks.push(task); + } + } else { + this.tasks.unshift(task); + } + return task; + }, + + appendTasks(items: Task[]) { + this.tasks = [...this.tasks, ...items]; + }, + + async fetchAllTasks(goalId: number, showCompleted: 0 | 1 = 0, firstNew: 0 | 1 = 1) { + this.loading = true; + const searchParams = { + showCompleted: +showCompleted as 0 | 1, + goalId: +goalId, + firstNew: +firstNew as 0 | 1, + componentId: ALL_TASKS_LIST_ID, + page: +this.fetchRules.currentPage, + searchText: this.fetchRules.searchText, + filters: this.fetchRules.filters, + unlimited: true, + }; + + const tasks = await $tvApi.tasks + .fetch(searchParams) + .catch(logError) + .finally(() => { + this.loading = false; + }); + if (!tasks) return false; + this.tasks = tasks; + + return false; + }, + + async fetchTasks(): Promise { + this.loading = true; + + const searchParams = { + showCompleted: +this.fetchRules.showCompleted as 0 | 1, + goalId: +this.fetchRules.goalId, + firstNew: +this.fetchRules.firstNew as 0 | 1, + componentId: +this.fetchRules.currentListId, + page: +this.fetchRules.currentPage, + searchText: this.fetchRules.searchText, + filters: this.fetchRules.filters, + }; + + const tasks = await $tvApi.tasks + .fetch(searchParams) + .catch(logError) + .finally(() => { + this.loading = false; + }); + if (!tasks) return false; + + this.fetchRules.endOfTasks = tasks.length < 1; + if (!this.fetchRules.endOfTasks) { + this.fetchRules.currentPage++; + } + this.appendTasks(tasks); + return true; + }, + + /**@todo rename to updateTaskComplete */ + async updateTaskCompleteStatus( + data: TaskArgUpdate, + deleteCompleted: boolean = true + ): Promise { + this.loading = true; + + const taskResult = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false; + }); + + if (!taskResult) return undefined; + + // this.setTaskStatusId({ taskId: taskResult.id, statusId: taskResult.statusId }); + + const searchId = taskResult.parentId || taskResult.id; + + let parentTask: Task | undefined; + + parentTask = this.tasks.find((tsk: Task) => tsk.id === searchId); + + if (!parentTask && searchId === this.selectedTask?.id) { + parentTask = this.selectedTask; + } + + if (!parentTask) { + return undefined; + } + + //we updating subtasks + if (taskResult.parentId) { + const subTask = parentTask.subtasks.find((sub) => sub.id === +taskResult.id); + if (subTask) { + subTask.complete = taskResult.complete; + } + } else { + //we updating main task + parentTask.complete = taskResult.complete; + //we deleting task from list if deleteCompleted is true + // and task complete is different from showCompleted rule for fetching + if (deleteCompleted && !!this.fetchRules.showCompleted !== data.complete) { + //handle condition with showing tasks in diff mode (showCompleted) + await delay(500); + const taskIndex = this.tasks.findIndex((task) => task.id === taskResult.id); + if (taskIndex !== -1) { + this.tasks.splice(taskIndex, 1); + } + } + } + + this.updateSelectedTask(parentTask); + + return taskResult.complete; + }, + + async updateTaskDescription(data: TaskArgUpdate): Promise { + this.loading = true; + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false; + }); + if (!task) return false; + + const localTask = this.tasks.find(({ id }) => id === task.id); + if (localTask) { + Object.assign(localTask, task); + } + this.updateSelectedTask(task); + return true; + }, + + async deleteTask(id: TaskArgDelete): Promise { + this.loading = true; + + const result = await $tvApi.tasks + .deleteTask(id) + .catch(logError) + .finally(() => { + this.loading = false; + }); + if (!result) return false; + + const taskIndex = this.tasks.findIndex((task) => task.id === id); + + if (taskIndex !== -1) { + this.tasks.splice(taskIndex, 1); + } + + if (this.selectedTask?.id === id) { + this.selectedTask = null; + } + + return true; + }, + + async updateTaskNote(data: TaskArgUpdate): Promise { + this.loading = true; + + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false; + }); + + if (!task) return false; + + const localTask = this.tasks.find(({ id }) => id === task.id); + if (localTask) { + Object.assign(localTask, task); + } + this.updateSelectedTask(task); + + return true; + }, + + //we do not fetch subtasks from server separately, we fetch all tasks with subtasks + // async fetchSubtasks(taskId: TaskItem['id']): Promise { + // this.loading = true; + // const subtasks = await $tvApi.tasks.fetch(taskId).catch(logError).finally(() => this.loading = false); + // // const result = await $api + // // .get>(`${this.urls.fetchSubtasks}?taskId=${taskId}`) + // // .catch((err) => console.log(err)); + // // this.loading = false; + // // if (result) { + // // if (result.data.response.length > 0) { + // // const task = this.tasks.find(({ id }) => id === +taskId); + // // if (task) { + // // task.subtasks = result.data.response; + // // this.updateSelectedTask(task); + // // } else { + // // if (this.selectedTask && +this.selectedTask.id === +taskId) { + // // this.selectedTask.subtasks = result.data.response; + // // } + // // } + // // } + // // } + // }, + + async toggleTagForTask(data: TagItemArgToggle) { + const tagsStore = useTagsStore(); + const action = await tagsStore.toggleTag(data); + + if (!action) { + return; + } + + const task = this.tasks.find((tsk) => +tsk.id === +data.taskId); + + const toggleTag = (task: Task) => { + if (action === 'delete') { + const tagIndex = task.tags.indexOf(data.tagId); + if (tagIndex !== -1) { + task.tags.splice(tagIndex, 1); + } + } else { + task.tags.push(data.tagId); + } + }; + + if (task) { + toggleTag(task); + this.updateSelectedTask(task); + } else if (this.selectedTask?.id === data.taskId) { + toggleTag(this.selectedTask); + } + }, + + async udpatePriority(data: TaskArgUpdate): Promise { + this.loading = true; + + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false; + }); + + if (!task) return; + + const localTask = this.tasks.find(({ id }) => id === task.id); + + if (localTask) { + Object.assign(localTask, task); + } + + this.updateSelectedTask(task); + }, + + //TODO fixme consider refactoring to make one method for all updates + async moveTaskToAnotherList(data: TaskArgUpdate) { + this.loading = true; + + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false; + }); + + if (!task) return; + + const localTask = this.tasks.find(({ id }) => id === task.id); + + if (localTask) { + Object.assign(localTask, task); + } + + this.updateSelectedTask(task); + }, + + async saveDateForTask(data: TaskArgUpdate) { + this.loading = true; + + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false; + }); + + if (!task) return; + + const localTask = this.tasks.find(({ id }) => id === task.id); + + if (localTask) { + Object.assign(localTask, task); + } + + this.updateSelectedTask(task); + }, + + async updateTaskAssignee(data: TaskArgToggleAssignee) { + this.loading = true; + + const updateAssignee = await $tvApi.tasks + .toggleTasksAssignee(data) + .catch(logError) + .finally(() => { + this.loading = false; + }); + + if (!updateAssignee) return; + + const task = this.tasks.find((tsk) => tsk.id === data.taskId); + + if (!task) return; + + task.assignedUsers = updateAssignee.userIds; + this.updateSelectedTask(task); + }, + + // we do not fetch assigned users from server separately, we fetch all tasks with assigned users + // async fetchTaskAssignedUsers(data: Pick) { + // this.loading = true; + // const result = await $api + // .post>(this.urls.taskFetchAssignedUsers, data) + // .catch((err) => console.log(err)); + // this.loading = false; + + // if (result) { + // const task = this.tasks.find((tsk) => tsk.id === data.taskId); + // if (task) { + // task.assignedUsers = result.data.response.usersIds; + // this.updateSelectedTask(task); + // } else if (this.selectedTask?.id === data.taskId) { + // this.selectedTask.assignedUsers = result.data.response.usersIds; + // } + // } + // }, + + async updateTaskStatusId(data: TaskArgUpdate) { + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false; + }); + + if (!task) return; + + const localTask = this.tasks.find(({ id }) => id === task.id); + + if (localTask) { + Object.assign(localTask, task); + } + + this.updateSelectedTask(task); + }, + + async updateTaskAmount(data: TaskArgUpdate) { + this.loading = true; + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false; + }); + + if (!task) return; + + const localTask = this.tasks.find(({ id }) => id === task.id); + + if (localTask) { + Object.assign(localTask, task); + } + + this.updateSelectedTask(task); + }, + + async updateTaskTransactionType(data: TaskArgUpdate) { + this.loading = true; + const task = await $tvApi.tasks + .updateTask(data) + .catch(logError) + .finally(() => { + this.loading = false; + }); + + if (!task) return; + + const localTask = this.tasks.find(({ id }) => id === task.id); + + if (localTask) { + Object.assign(localTask, task); + } + + this.updateSelectedTask(task); + }, + }, +}); diff --git a/web__old/src/stores/user.store.ts b/web__old/src/stores/user.store.ts new file mode 100644 index 0000000..61b0edc --- /dev/null +++ b/web__old/src/stores/user.store.ts @@ -0,0 +1,42 @@ +import { defineStore } from 'pinia'; +import type { JWTPayload } from '@/helpers/AppTypes'; +import { parseJwt } from '@/helpers/Helper'; + +export const useUserStore = defineStore('user', { + state: () => ({ + loading: true, + login: '', + email: '', + accessToken: '', + refreshToken: '', + authType: 'jwt', + }), + getters: { + isLoggedIn(state): boolean { + return state.login.trim() !== '' && state.accessToken.trim() !== '' && state.refreshToken.trim() !== ''; + }, + payloadData(state) { + const tokenData = parseJwt(state.accessToken); + return tokenData?.userData; + }, + }, + actions: { + setAccessToken(token: string) { + this.accessToken = token; + }, + setRefreshToken(token: string) { + this.refreshToken = token; + }, + setLogin(login: string) { + this.login = login; + }, + + setEmail(email: string) { + this.email = email; + }, + + setAuthType(type: JWTPayload['type']) { + this.authType = type; + }, + }, +}); diff --git a/web__old/src/types/analytics.types.ts b/web__old/src/types/analytics.types.ts new file mode 100644 index 0000000..9b73976 --- /dev/null +++ b/web__old/src/types/analytics.types.ts @@ -0,0 +1,23 @@ +import type { GoalItem } from 'taskview-api'; +import type { CollaborationUsers } from './collaboration.types'; +import type { AppResponse } from './global-app.types'; +import type { TaskItem } from './tasks.types'; + +export type AnalyticsStoreState = { + loading: boolean; + tasks: TaskItem[]; + users: CollaborationUsers; + tasksForProject: TaskItem[]; +}; + +export type FetchAnalyticsDataResponse = AppResponse<{ + tasks: AnalyticsStoreState['tasks']; + users: CollaborationUsers; +}>; + +export type FetchAnalyticsTasksArg = { startDate: string; endDate: string }; + +export type FetchAnalyticsForProject = { + goalId: GoalItem['id']; + dates: FetchAnalyticsTasksArg; +}; diff --git a/web__old/src/types/app.types.ts b/web__old/src/types/app.types.ts new file mode 100644 index 0000000..8ceb829 --- /dev/null +++ b/web__old/src/types/app.types.ts @@ -0,0 +1,8 @@ +export type TI18nLocalesInApp = { + //eslint-disable-next-line @typescript-eslint/no-explicit-any + en: Record; + //eslint-disable-next-line @typescript-eslint/no-explicit-any + ru: Record; +}; + +export const DEFAULT_ID = -1; diff --git a/web__old/src/types/base-screen.types.ts b/web__old/src/types/base-screen.types.ts new file mode 100644 index 0000000..6af03c8 --- /dev/null +++ b/web__old/src/types/base-screen.types.ts @@ -0,0 +1,75 @@ +import { mdiAccountBoxMultipleOutline, mdiClipboardEditOutline, mdiLightbulbOn10 } from '@mdi/js'; +import type { TagItem } from './tags.types'; +import type { Task } from 'taskview-api'; +import type { TaskItem } from './tasks.types'; + +type RespTasks = TaskItem[]; +type RespUsers = { id: number; name: string; users: { id: number; email: string }[] }[]; + +export type TaskAssignees = { taskId: TaskItem['id']; collabUserId: number; email: string }[]; + +export type MainScreenAllStateResponse = { + tasks: RespTasks; + tasksToday: RespTasks; + tasksUpcoming: RespTasks; + tasksLastCompleted: RespTasks; + users: RespUsers; + assignees: TaskAssignees; + listToGoal: Record; +}; + +export type BaseScreenState = { + activeWidgetInMobile: 'today' | 'lastAdded' | 'upcoming' | null | 'all'; + wasCalled: boolean; + tasks: RespTasks; + users: RespUsers; + tasksToday: RespTasks; + tasksUpcoming: RespTasks; + tasksLastCompleted: RespTasks; + searchTask: string; + // addTaskDialog: boolean; + /**@deprecated */ + lists: GoalAndListsResp; + // listToGoal: BoardsResponse['listToGoal']; + loading: boolean; + assignees: TaskAssignees; + filterAndSorting: { + sort: SortTasks; + filters: FiltersTasks; + }; + taskIdToUser: Record; +}; + +export type GoalAndListsResp = { + goalName: string; + listName: string; + listId: number; + goalId: number; +}[]; + +export type SortTasks = 'nameAsc' | 'nameDesc' | 'new' | 'old'; +export type FiltersTasks = { + projects: number[]; + priorities: Task['priorityId'][]; + tags: TagItem[]; + assignees: TaskAssignees[number]['collabUserId'][]; +}; + +export const FILTER_DEFAULT: FiltersTasks = { + projects: [], + priorities: [], + tags: [], + assignees: [], +}; +// export type SortTasks = { +// byId: typeof SORT_ASC | typeof SORT_DESC | typeof SORT_DEFAULT; +// byName: typeof SORT_ASC | typeof SORT_DESC | typeof SORT_DEFAULT; +// }; + +export const ICON_PROJECT = { icon: mdiLightbulbOn10, color: '#FFB800' }; +export const ICON_TASKS = { icon: mdiClipboardEditOutline, color: '#38D681' }; +export const ICON_USERS = { icon: mdiAccountBoxMultipleOutline, color: '#BE8CFF' }; +export const DIALOG_WIDTH = 600; +export const DIALOG_DESKTOP_HEIGHT = '50%'; + +export type BaseScreenSearchResponse = TaskItem[]; diff --git a/web__old/src/types/collaboration-permissions.types.ts b/web__old/src/types/collaboration-permissions.types.ts new file mode 100644 index 0000000..a3d628a --- /dev/null +++ b/web__old/src/types/collaboration-permissions.types.ts @@ -0,0 +1,15 @@ +import type { CollaborationPermission } from 'taskview-api'; +import type { TI18nLocalesInApp } from './app.types'; + +export type PermissioinGroupsIds = 1 | 2 | 3 | 4; + +export const PermissionGroups: Record> = { + 1: { en: 'Applicarion level', ru: 'Уровня приложения' }, + 2: { en: 'Project', ru: 'Проект' }, + 3: { en: 'Task list', ru: 'Список задач' }, + 4: { en: 'Tasks', ru: 'Задачи' }, +}; + +export type CollaborationPermissionsState = { + permissions: CollaborationPermission[]; +}; diff --git a/web__old/src/types/collaboration-roles.types.ts b/web__old/src/types/collaboration-roles.types.ts new file mode 100644 index 0000000..8cfc1a4 --- /dev/null +++ b/web__old/src/types/collaboration-roles.types.ts @@ -0,0 +1,12 @@ +import type { CollaborationPermission } from 'taskview-api'; + +export type CollaborationRole = { + id: number; + name: string; + goal_id: number; +}; + +export type CollaborationRolesStore = { + roles: CollaborationRole[]; + rolesPermissions: Record>; +}; diff --git a/web__old/src/types/collaboration.types.ts b/web__old/src/types/collaboration.types.ts new file mode 100644 index 0000000..986d826 --- /dev/null +++ b/web__old/src/types/collaboration.types.ts @@ -0,0 +1,7 @@ +import type { CollaborationResponseFetchAllUsers } from 'taskview-api'; + +export type CollaborationUsers = CollaborationResponseFetchAllUsers[]; + +export type CollaborationStore = { + users: CollaborationUsers; +}; diff --git a/web__old/src/types/global-app.types.ts b/web__old/src/types/global-app.types.ts new file mode 100644 index 0000000..48ddfe9 --- /dev/null +++ b/web__old/src/types/global-app.types.ts @@ -0,0 +1,8 @@ +export type AppResponse = { + response: S; + rid: string; +}; + +export type AppStoreState = { + drawer: boolean; +}; diff --git a/web__old/src/types/goal-lists.types.ts b/web__old/src/types/goal-lists.types.ts new file mode 100644 index 0000000..8de6d93 --- /dev/null +++ b/web__old/src/types/goal-lists.types.ts @@ -0,0 +1,55 @@ +import type { GoalItem, GoalListItem } from 'taskview-api'; +// import type { GoalItem } from './goals.types'; + +/** @deprecated use GoalListItem[] from taskview-api instead */ +export type GoalListItems = GoalListItem[]; + +export type GoalListsStoreState = { + currentGoalId: GoalItem['id']; + lists: GoalListItem[]; + loading: boolean; +}; + +// /** @deprecated */ +// export type GoalListAddArg = { +// goalId: number; +// name: string; +// }; + +// /** @deprecated */ +// export type GoalListAddResponse = { +// add: boolean; +// component?: GoalListItem; +// }; + +export type GoalListEventMoreMenu = { + activator: HTMLElement; + list: GoalListItem; +}; + +export type GoalListActionsItemsEvents = 'editList' | 'deleteList' | 'archiveGoalList'; + +export type GoalListActionsItems = { + id: number; + name: string; + eventName: GoalListActionsItemsEvents; + icon?: string; +}[]; + +// export type GoalListDeleteResponse = { +// delete: boolean; +// }; + +// export type GoalListUpdateArg = { +// id: number; +// name: string; +// }; + +// export type GoalListUpdateResponse = { +// update: boolean; +// component?: GoalListItem; +// }; + +// export type ArchiveListArg = { goalListId: GoalListItem['id']; archive: GoalListItem['archive'] }; +// export type ArchiveListResp = { archive: 0 | 1 }; +export type GoalSectionsTabs = 'tasks' | 'lists'; diff --git a/web__old/src/types/goals.types.ts b/web__old/src/types/goals.types.ts new file mode 100644 index 0000000..d971753 --- /dev/null +++ b/web__old/src/types/goals.types.ts @@ -0,0 +1,45 @@ +import type { GoalItem } from 'taskview-api'; +import { TvPermissions } from 'taskview-api'; + +export const DEFAULT_GOAL_ITEM: GoalItem = { + id: -1, + name: '', + description: '', + color: '', + owner: -1, + permissions: {}, + archive: 0, + dateCreation: null, +}; + +export const AllGoalPermissions = TvPermissions; + +export type GoalsStoreState = { + loading: boolean; + selectedItemId: GoalItem['id']; + goals: GoalItem[]; +}; + +export type GoalEventMoreMenu = { + activator: HTMLElement; + goal: GoalItem; +}; + +export type GoalActionsItemsEvents = + | 'editGoal' + | 'deleteGoal' + | 'archiveGoal' + | 'callCollaboration' + | 'noItems' + | 'editList' + | 'deleteList' + | 'archiveGoalList' + | 'kanbanView' + | 'graphView'; +export type GoalActionsItems = { + id: number; + name: string; + eventName: GoalActionsItemsEvents; + icon?: string; + disabled?: boolean; +}[]; diff --git a/web__old/src/types/graph.types.ts b/web__old/src/types/graph.types.ts new file mode 100644 index 0000000..1fd91dd --- /dev/null +++ b/web__old/src/types/graph.types.ts @@ -0,0 +1,6 @@ +import type { Edge, Node } from '@vue-flow/core'; + +export type GraphStore = { + nodes: Node[]; + edges: Edge[]; +}; diff --git a/web__old/src/types/kanban.types.ts b/web__old/src/types/kanban.types.ts new file mode 100644 index 0000000..c41c0c0 --- /dev/null +++ b/web__old/src/types/kanban.types.ts @@ -0,0 +1,10 @@ +import type { GoalItem, KanbanColumnItem, KanbanResponseTasksForColumn } from 'taskview-api'; + +export type KanbanStoreState = { + tasksData: { + [key: KanbanColumnItem['id']]: KanbanResponseTasksForColumn | undefined; + }; + // TODO: rename to columns + statuses: KanbanColumnItem[]; + goalId: GoalItem['id']; +}; \ No newline at end of file diff --git a/web__old/src/types/layout.types.ts b/web__old/src/types/layout.types.ts new file mode 100644 index 0000000..6f20f85 --- /dev/null +++ b/web__old/src/types/layout.types.ts @@ -0,0 +1,11 @@ +import type { Component } from 'vue'; +import type { RouteLocationNamedRaw } from 'vue-router'; + +export type TvNavItemT = { + title: string; + icon: string; + expandable?: boolean; + expanded?: boolean; + to?: RouteLocationNamedRaw; + component?: Component; +}; diff --git a/web__old/src/types/router.d.ts b/web__old/src/types/router.d.ts new file mode 100644 index 0000000..2c77c87 --- /dev/null +++ b/web__old/src/types/router.d.ts @@ -0,0 +1,9 @@ +import type { MiddlewareHandlers } from '@/router'; + +import 'vue-router'; + +declare module 'vue-router' { + interface RouteMeta { + middleware?: MiddlewareHandlers; + } +} diff --git a/web__old/src/types/tags.types.ts b/web__old/src/types/tags.types.ts new file mode 100644 index 0000000..67c2804 --- /dev/null +++ b/web__old/src/types/tags.types.ts @@ -0,0 +1,9 @@ +import type { TagItem as TagItemFromApi } from 'taskview-api'; + +export type TagItem = TagItemFromApi; + +export type TagsState = { + tags: TagItem[]; + tagsMap: Map; + loading: boolean; +}; diff --git a/web__old/src/types/task-history.types.ts b/web__old/src/types/task-history.types.ts new file mode 100644 index 0000000..32a43f0 --- /dev/null +++ b/web__old/src/types/task-history.types.ts @@ -0,0 +1,16 @@ +import type { Task } from 'taskview-api'; +import type { TaskItem } from '@/types/tasks.types'; + +export type PriorityValues = 'low' | 'medium' | 'high'; +export type PriorityItem = { id: Task['priorityId']; code: PriorityValues }; + +export const PriorityIdToLocaleString: Record = { + 1: 'priority.low', + 2: 'priority.medium', + 3: 'priority.high', +}; +export type TaskHistoryItem = TaskItem; + +export type TaskHistoryState = { + history: TaskHistoryItem[]; +}; diff --git a/web__old/src/types/tasks.types.ts b/web__old/src/types/tasks.types.ts new file mode 100644 index 0000000..4d619c7 --- /dev/null +++ b/web__old/src/types/tasks.types.ts @@ -0,0 +1,33 @@ +import type { GoalItem, GoalListItem, Task, TaskFilters } from 'taskview-api'; + +export type TaskItem = Task; + +export type TaskItems = TaskItem[]; + +export type PartialSome = Omit & Partial>; + +export type PartialFetchRules = PartialSome; + +export type TasksStoreState = { + selectedTask?: Task | null; + tasks: Task[]; + fetchRules: { + goalId: GoalItem['id']; + showCompleted: 1 | 0; + currentListId: GoalListItem['id']; + endOfTasks: boolean; + currentPage: number; + searchText: string; + firstNew: 1 | 0; + filters: TaskFilters; + }; + loading: boolean; +}; + +export const PRIORITY_COLORS = { + '1': { color: '#38D681', t: 'priority.low' }, + '2': { color: '#FF9100', t: 'priority.medium' }, + '3': { color: '#FF1744', t: 'priority.high' }, +}; + +export const ALL_TASKS_LIST_ID = -1401; diff --git a/web/tailwind.config.js b/web__old/tailwind.config.js similarity index 100% rename from web/tailwind.config.js rename to web__old/tailwind.config.js diff --git a/web__old/tsconfig.app.json b/web__old/tsconfig.app.json new file mode 100644 index 0000000..a601717 --- /dev/null +++ b/web__old/tsconfig.app.json @@ -0,0 +1,36 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": [ + "env.d.ts", + "src/**/*", + "src/**/*.vue", + "src/**/*.d.ts", + "src/types/**/*.d.ts" + ], + "exclude": [ + "src/**/__tests__/*" + ], + "compilerOptions": { + "types": [ + "node" + ], + "module": "esnext", + "composite": true, + // "esModuleInterop": true, + // "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "@/*": [ + "./src/*", + ], + "~/*": [ + "src/*" + ] + }, + "lib": [ + "ESNext", + "DOM", + "esnext.promise" + ] + } +} \ No newline at end of file diff --git a/web/tsconfig.config.json b/web__old/tsconfig.config.json similarity index 100% rename from web/tsconfig.config.json rename to web__old/tsconfig.config.json diff --git a/web__old/tsconfig.json b/web__old/tsconfig.json new file mode 100644 index 0000000..2c5e1b9 --- /dev/null +++ b/web__old/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.app.json", +} \ No newline at end of file diff --git a/web/tsconfig.vitest.json b/web__old/tsconfig.vitest.json similarity index 100% rename from web/tsconfig.vitest.json rename to web__old/tsconfig.vitest.json diff --git a/web__old/vite.config.ts b/web__old/vite.config.ts new file mode 100644 index 0000000..8717d49 --- /dev/null +++ b/web__old/vite.config.ts @@ -0,0 +1,25 @@ +import { fileURLToPath, URL } from 'node:url'; + +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; + +// https://vitejs.dev/config/ +export default defineConfig({ + define: { + APP_VERSION: JSON.stringify(process.env.npm_package_version), + }, + build: { + target: 'ESNext', + }, + server: { + host: 'localhost', + port: 3000, + }, + plugins: [vue(), vueJsx()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, +});