diff --git a/app/controllers/department_skill_snapshots_controller.rb b/app/controllers/department_skill_snapshots_controller.rb new file mode 100644 index 000000000..ff52f0b27 --- /dev/null +++ b/app/controllers/department_skill_snapshots_controller.rb @@ -0,0 +1,28 @@ +class DepartmentSkillSnapshotsController < CrudController + def index + @data = chart_data.to_json + super + end + + private + + def chart_data + { + labels: Date::MONTHNAMES.compact, + datasets: dataset_values.map { |value| build_dataset(value) } + } + end + + def dataset_values + %w[Azubi Junior Senior Professional Expert] + end + + def build_dataset(value) + { + label: value, + data: Array.new(12) { rand(10) }, + fill: false, + tension: 0.1 + } + end +end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 23ecd85a2..a03db27e9 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -12,15 +12,18 @@ def person_tabs(person) ] end + # rubocop:disable Metrics/LineLength def global_tabs [ { title: ti('navbar.profile'), path: people_path, admin_only: false }, { title: ti('navbar.skill_search'), path: people_skills_path, admin_only: false }, { title: ti('navbar.cv_search'), path: cv_search_index_path, admin_only: false }, { title: ti('navbar.skillset'), path: skills_path, admin_only: false }, - { title: ti('navbar.certificates'), path: certificates_path, admin_only: true } + { title: ti('navbar.certificates'), path: certificates_path, admin_only: true }, + { title: ti('navbar.skills_tracking'), path: department_skill_snapshots_path, admin_only: false } ] end + # rubocop:enable Metrics/LineLength def extract_path(regex) request.path.match(regex)&.captures&.join diff --git a/app/javascript/controllers/chart_controller.js b/app/javascript/controllers/chart_controller.js new file mode 100644 index 000000000..d3873274b --- /dev/null +++ b/app/javascript/controllers/chart_controller.js @@ -0,0 +1,35 @@ +import { Controller } from "@hotwired/stimulus" +import Chart from "chart.js/auto" +import { Colors } from 'chart.js'; + +export default class extends Controller { + static targets = ["canvas"] + static values = { + dataset: String + } + + connect() { + Chart.register(Colors); + + const ctx = this.canvasTarget.getContext("2d") + + const chartData = JSON.parse(this.datasetValue) + + this.chart = new Chart(ctx, { + type: 'line', + data: chartData, + options: { + responsive: true, + scales: { + y: { + beginAtZero: true + } + } + } + }) + } + + disconnect() { + this.chart?.destroy() + } +} diff --git a/app/models/department_skill_snapshot.rb b/app/models/department_skill_snapshot.rb new file mode 100644 index 000000000..40536c9ad --- /dev/null +++ b/app/models/department_skill_snapshot.rb @@ -0,0 +1,5 @@ +class DepartmentSkillSnapshot < ApplicationRecord + belongs_to :department + + serialize :department_skill_levels, type: Hash, coder: JSON +end diff --git a/app/views/department_skill_snapshots/index.html.haml b/app/views/department_skill_snapshots/index.html.haml new file mode 100644 index 000000000..a307220da --- /dev/null +++ b/app/views/department_skill_snapshots/index.html.haml @@ -0,0 +1,18 @@ += form_with url: department_skill_snapshots_path, method: :get, data: { turbo_frame: "team-skill-chart" } do |f| + .d-flex.mt-4.mb-4.gap-4 + %div + = f.label :department_id, t('activerecord.models.department.one'), class: "text-secondary" + = f.select :department_id, + options_from_collection_for_select(Department.all, :id, :name, params[:department_id]), {}, { onchange: "this.form.requestSubmit()", class: "form-select"} + %div + = f.label :skill_id, t('activerecord.models.skill.one'), class: "text-secondary" + = f.select :skill_id, + options_from_collection_for_select(Skill.all, :id, :title, params[:skill_id]), {}, { onchange: "this.form.requestSubmit()", class: "form-select" } + %div + = f.label :year, t('global.date.year'), class: "text-secondary" + = f.select :year, + options_for_select((2025..Date.today.year).to_a.reverse, params[:year]), {}, { onchange: "this.form.requestSubmit()", class: "form-select"} + +%turbo-frame#team-skill-chart + %div.chart-container{data: {"chart-dataset-value": @data, controller: "chart" }} + %canvas{ "data-chart-target": "canvas", width: "3", height: "1" } diff --git a/config/locales/de-CH.yml b/config/locales/de-CH.yml index 3ecebd76a..f095c4aea 100644 --- a/config/locales/de-CH.yml +++ b/config/locales/de-CH.yml @@ -231,6 +231,7 @@ de-CH: cv_search: CV Suechi profile: Profiu skill_search: Skill Suechi + skills_tracking: Skills tracking skillset: Skillset new: Neu people_skills: diff --git a/config/locales/de.yml b/config/locales/de.yml index ba39c1e87..28fc5751d 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -216,6 +216,7 @@ de: cv_search: CV Suche profile: Profil skill_search: Skill Suche + skills_tracking: Skills tracking skillset: Skillset new: Neu people_skills: diff --git a/config/locales/en.yml b/config/locales/en.yml index d78dde159..fc2637de0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -212,6 +212,7 @@ en: cv_search: CV search profile: Profile skill_search: Skill search + skills_tracking: Skills tracking skillset: Skillset new: New people_skills: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 10d145aa9..3b793f090 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -212,6 +212,7 @@ fr: cv_search: Recherche de CV profile: Profil skill_search: Recherche de compétences + skills_tracking: Suivi des compétences skillset: Kit de compétences new: Nouveau people_skills: diff --git a/config/locales/it.yml b/config/locales/it.yml index 9fdd053fd..145539004 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -212,6 +212,7 @@ it: cv_search: Ricerca di CV profile: Profilo skill_search: Ricerca di competenze + skills_tracking: Tracciamento delle competenze skillset: Competenze new: Nuovo people_skills: diff --git a/config/routes.rb b/config/routes.rb index 339512201..a082b3891 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -67,6 +67,8 @@ end resources :certificates + + resources :department_skill_snapshots, only: [:index] end diff --git a/package.json b/package.json index 57ad35f53..56d7da914 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "autoprefixer": "^10.4.17", "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.3", + "chart.js": "^4.4.9", "esbuild": "^0.25.0", "esbuild-rails": "^1.0.7", "nodemon": "^3.0.3", diff --git a/spec/controllers/department_skill_snapshots_controller_spec.rb b/spec/controllers/department_skill_snapshots_controller_spec.rb new file mode 100644 index 000000000..6497fdc41 --- /dev/null +++ b/spec/controllers/department_skill_snapshots_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +describe DepartmentSkillSnapshotsController do + +end \ No newline at end of file diff --git a/spec/features/department_skill_snapshots_spec.rb b/spec/features/department_skill_snapshots_spec.rb new file mode 100644 index 000000000..1cdaa9a2f --- /dev/null +++ b/spec/features/department_skill_snapshots_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +describe 'Department Skill Snapshots', type: :feature, js: true do + before(:each) do + admin = auth_users(:admin) + login_as(admin, scope: :auth_user) + visit department_skill_snapshots_path + end + + it 'Should display all selects with the corresponding labels and the the canvas chart' do + expect(page).to have_content('Organisationseinheit') + expect(page).to have_content('Skill') + expect(page).to have_content('Jahr') + + expect(page).to have_select('department_id') + expect(page).to have_select('skill_id') + expect(page).to have_select('year') + + expect(page).to have_selector("canvas") + end +end diff --git a/spec/features/edit_people_skills_spec.rb b/spec/features/edit_people_skills_spec.rb index ef1124a44..d7c28612f 100644 --- a/spec/features/edit_people_skills_spec.rb +++ b/spec/features/edit_people_skills_spec.rb @@ -12,7 +12,7 @@ bob = people(:bob) visit person_path(bob) - expect(page).to have_css('.nav-link', text: 'Skills', count: 2) + expect(page).to have_css('.nav-link', text: 'Skills', count: 3) page.all('.nav-link', text: 'Skills')[1].click end diff --git a/spec/features/tabbar_spec.rb b/spec/features/tabbar_spec.rb index 2266ba211..8f0a8d29a 100644 --- a/spec/features/tabbar_spec.rb +++ b/spec/features/tabbar_spec.rb @@ -9,7 +9,8 @@ { title: 'global.navbar.skill_search', path_helper: "people_skills_path", admin_only: false }, { title: 'global.navbar.cv_search', path_helper: "cv_search_index_path", admin_only: false }, { title: 'global.navbar.skillset', path_helper: "skills_path", admin_only: false }, - { title: 'global.navbar.certificates', path_helper: "certificates_path", admin_only: true } + { title: 'global.navbar.certificates', path_helper: "certificates_path", admin_only: true }, + { title: 'global.navbar.skills_tracking', path_helper: "department_skill_snapshots_path", admin_only: false } ] PERSON_TABS = @@ -27,7 +28,7 @@ end after(:each) do - expect(current_path).to start_with("/#{locale}") + expect(current_path).to start_with("/#{locale}/") end describe 'Global' do diff --git a/yarn.lock b/yarn.lock index c9622ee1b..10045ee6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -145,6 +145,11 @@ resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-8.0.12.tgz#50aa8345d7f62402680c6d2d9814660761837001" integrity sha512-l3BiQRkD7qrnQv6ms6sqPLczvwbQpXt5iAVwjDvX0iumrz6yEonQkNAzNjeDX25/OJMFDTxpHjkJZHGpM9ikWw== +"@kurkle/color@^0.3.0": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.4.tgz#4d4ff677e1609214fc71c580125ddddd86abcabf" + integrity sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -263,6 +268,13 @@ caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001688: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz#a102cf330d153bf8c92bfb5be3cd44c0a89c8c12" integrity sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w== +chart.js@^4.4.9: + version "4.4.9" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.9.tgz#602e2fc2462f0f7bb7b255eaa1b51f56a43a1362" + integrity sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg== + dependencies: + "@kurkle/color" "^0.3.0" + "chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.0, chokidar@^3.5.2: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"