diff --git a/.eslintrc b/.eslintrc index fdb4550..c84ca56 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,17 +2,20 @@ "env": { "browser": true, "node": true, - "es2022": true + "es2020": true }, "parserOptions": { "sourceType": "module", "ecmaVersion": 2022 }, - "extends": "eslint:recommended", + "extends": ["eslint:recommended", "plugin:vue/vue3-recommended"], + "plugins": ["vue"], "rules": { "no-unused-vars": "off", "no-console": "warn", - "no-undef": "off" + "no-undef": "off", + "vue/multi-word-component-names": "off", + "vue/no-reserved-component-names": "off" }, "root": true, "globals": { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1821b8a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,112 @@ +name: CI + +on: + push: + branches: + - version-16 + + pull_request: + branches: + - version-16 + +env: + BRANCH: ${{ github.base_ref || github.ref_name }} + +concurrency: + group: develop-projectit-${{ github.event.number || github.sha }} + cancel-in-progress: true + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + name: Server + + services: + redis-cache: + image: redis:alpine + ports: + - 13000:6379 + redis-queue: + image: redis:alpine + ports: + - 11000:6379 + mariadb: + image: mariadb:10.6 + env: + MYSQL_ROOT_PASSWORD: root + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + + steps: + - name: Clone + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.14" + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 24 + check-latest: true + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py', '**/setup.cfg') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v4 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install MariaDB Client + run: | + sudo apt update + sudo apt-get install mariadb-client + + - name: Setup + run: | + pip install frappe-bench + bench init --skip-redis-config-generation --skip-assets --python "$(which python)" --frappe-branch "$BRANCH" ~/frappe-bench + mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'" + mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" + + - name: Install + working-directory: /home/runner/frappe-bench + run: | + bench get-app erpnext --branch "$BRANCH" + bench get-app hrms --branch "$BRANCH" + bench get-app projectit $GITHUB_WORKSPACE + bench setup requirements --dev + bench new-site --db-root-password root --admin-password admin test_site + bench --site test_site install-app erpnext + bench --site test_site install-app hrms + bench --site test_site install-app projectit + bench build + env: + CI: "Yes" + + - name: Run Tests + working-directory: /home/runner/frappe-bench + run: | + bench --site test_site set-config allow_tests true + bench --site test_site run-tests --app projectit --skip-test-records + env: + TYPE: server diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 0000000..82379b5 --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,62 @@ +name: Linters + +on: + pull_request: + branches: + - version-16 + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + linter: + name: "Frappe Linter" + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: "3.14" + cache: pip + - uses: pre-commit/action@v3.0.0 + + - name: Download Semgrep rules + run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules + + - name: Run Semgrep rules + run: | + pip install semgrep + semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness + + deps-vulnerable-check: + name: "Vulnerable Dependency Check" + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-python@v6 + with: + python-version: "3.14" + + - uses: actions/checkout@v4 + + - name: Cache pip + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Install and run pip-audit + run: | + pip install pip-audit + cd ${GITHUB_WORKSPACE} + pip-audit --desc on . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 53ad7c8..55d475e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -exclude: 'node_modules|.git' +exclude: "node_modules|.git" default_stages: [pre-commit] fail_fast: false @@ -28,22 +28,25 @@ repos: - id: prettier types_or: [javascript, vue, scss] exclude: | - (?x)^( - .*/dist/.*| - .*node_modules.* - )$ + (?x)^( + .*/dist/.*| + .*node_modules.* + )$ - repo: https://github.com/pre-commit/mirrors-eslint rev: v8.44.0 hooks: - id: eslint types_or: [javascript] - args: ['--quiet'] + args: ["--quiet"] + additional_dependencies: + - eslint@8.44.0 + - eslint-plugin-vue@9 exclude: | - (?x)^( - .*/dist/.*| - .*node_modules.* - )$ + (?x)^( + .*/dist/.*| + .*node_modules.* + )$ ci: - autoupdate_schedule: weekly + autoupdate_schedule: weekly diff --git a/mobile/package.json b/mobile/package.json index 70fb80c..0bfa2c2 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -6,15 +6,15 @@ "dev": "vite", "build": "vite build && yarn copy-icon", "preview": "vite preview", - "copy-html-entry": "cp ../projectit/public/projectit/index.html ../projectit/www/projectit.html", - "copy-icon": "cp -r ../mobile/src/images/ ../projectit/public/projectit/images/" + "copy-icon": "cp -r ../mobile/src/images/ ../projectit/public/projectit/images/", + "lint": "eslint 'src/**/*.{js,vue}' --quiet" }, "dependencies": { "@internationalized/date": "^3.7.0", "dayjs": "^1.11.13", "feather-icons": "^4.29.2", "firebase": "^11.4.0", - "frappe-ui": "^0.1.72", + "frappe-ui": "0.1.278", "vite-plugin-pwa": "^0.21.1", "vue": "^3.5.12", "vue-router": "^4.4.5", @@ -30,6 +30,8 @@ "devDependencies": { "@vitejs/plugin-vue": "^5.1.4", "autoprefixer": "^10.4.2", + "eslint": "^8.57.0", + "eslint-plugin-vue": "^9.30.0", "postcss": "^8.4.5", "tailwindcss": "^3.4.14", "vite": "^6.0.0" diff --git a/mobile/src/App.vue b/mobile/src/App.vue index 01d657b..1e00e9b 100644 --- a/mobile/src/App.vue +++ b/mobile/src/App.vue @@ -1,23 +1,24 @@ diff --git a/mobile/src/composables/index.js b/mobile/src/composables/index.js index 661c015..40c24ce 100644 --- a/mobile/src/composables/index.js +++ b/mobile/src/composables/index.js @@ -19,15 +19,11 @@ export class FileAttachment { url: 'projectit.api.upload_base64_file', onSuccess: (fileDoc) => resolve(fileDoc), onError: (error) => { - toast({ - title: 'Error', - text: `File upload failed for ${this.fileName}. ${ + toast.error( + `File upload failed for ${this.fileName}. ${ error.messages?.[0] || '' - }`, - icon: 'alert-circle', - position: 'bottom-center', - iconClasses: 'text-red-500', - }) + }` + ) reject(error) }, }) diff --git a/mobile/src/data/session.js b/mobile/src/data/session.js index 64d53c1..9c54664 100644 --- a/mobile/src/data/session.js +++ b/mobile/src/data/session.js @@ -29,9 +29,11 @@ export const session = reactive({ router.replace(data.default_route || '/home') }, onError(error) { - if (error.exc_type == 'AuthenticationError') { - showAuthenticationError.value = true - } + showAuthenticationError.value = true + loginError.value = + error.exc_type == 'AuthenticationError' + ? 'Invalid credentials. Please try again.' + : error.message || 'Login failed. Please try again.' }, }), logout: createResource({ @@ -47,3 +49,4 @@ export const session = reactive({ }) export const showAuthenticationError = ref(false) +export const loginError = ref('') diff --git a/mobile/src/pages/AllocateEmployees.vue b/mobile/src/pages/AllocateEmployees.vue index ce55ab8..8355743 100644 --- a/mobile/src/pages/AllocateEmployees.vue +++ b/mobile/src/pages/AllocateEmployees.vue @@ -3,10 +3,15 @@

{{ route.params.project_name }}

-
+
- +
@@ -20,17 +25,27 @@
- +
- + actions: [ + { + label: 'Confirm', + variant: 'solid', + onClick: () => { + employeeInstructionListResource.delete.submit(employeeInstructionId) + showRemove = false + }, + }, + ], + }" + v-model="showRemove" + />