Skip to content

Commit

Permalink
Bring back user profile (#599)
Browse files Browse the repository at this point in the history
* add first tables to display profile data

* add language and profile pic to view and add a new section in the view respectively. make roles display correctly

* style headers of user profile data table and move edit link

* display languages in a very basic way

* make account overview display country name instead of alpha2 code and implement most of the edit form to edit a user-profile

* fix displaying of values in dropdowns, to represent actual state of object and add fields for percentage and level

* label role form fields and correctly display role in overview

* replace @entry with @person after rebase and remove nested_models and permitted_relationships from people controller

* move edit form to edit route and add turbo frame that loads edit form in overview when edit is clicked and remove absolute width from account overview tables

* fix alignment of tables, set width of input fields to match table width, add save button and fix saving of form

* style edit form to use bootstrap form fields

* adjust font-weights of edit form

* create controller method to load correct picture path, add uploads to assets path, move cancel button on edit view and fix view not loading when department name missing

* add default avatar to public directory

* make view display nil properties of person as -, create picture controller and remove picture_path method from people_controller, use new picture controller to load pictures correctly in view

* add picture to permitted attrs

* add dummy role adding button in person edit view and make marital status dropdown have correct value selected on edit

* start working on updating roles by renaming the params in the form and overriding the update method in the controller

* rewrite profile to use fields_for instead of a normal for loop, for saving new roles

* add person_roles_attributes to permitted attributes

* make edit view conditionally display the second nationality dropdown depending on checkbox state

* make person overview and edit view responsive

* remove overflow scroll from person overview and edit view

* move fields of role form to partial and start writing logic to create new roles

* add remove button to roles in edit view and fix rendering of form

* add missing class to person role fields partial, add js method to make remove roles work and update eventlistener of add role to correctly fire and trigger

* refactor too long lines, add comments, move whole edit form to form partial and create a turbostream that is triggered when edit fails, refactor the has_nationality2 property in the edit view

* fix name attribute of contries in form

* replace pzsh container with div to prevent moving whole page to center

* style the show view of the person

* fix styling of edit view

* change w-100 to mw-100 everywhere in edit view

* make image instantly update on file upload by using a stimulus controller

* fix nationality2 checkbox

* add picture cache for profile picture, style picture upload button fix error in person edit turbostream

* adjust color of buttons in person edit view

* fix loading of profile picture after saving and style profile picture upload button

* fix template error in show view of person, when person role level or percent are nil, add classes to person templates for testing purposes and write first test to test if person is editable

* fix rubocop offenses

* fix last rubocop offenses

* write test that tests if editing a person is possible, assign id to save and edit buttons for testing, make editing shortname possible

* remove unwanted header from person profile

* change dateformat in equal check of feature spec to make test work across all systems

* change _ to - in ids of save cancel and edit buttons and write test that tests if canceling really doesnt save the person

* revert formating of date in test

* bring back formatting of date in test

* Change filling of date input in test to default html

* Fix date formating for filling form in test

* make test also upload new image and check if avatar upload field exists

* rename method of has_nationality2 checkbox to checked and start writing people_controller spec

* Add param converter concerns to check if param is false, use new false method to check if person has nationality2, write test to test if nationality2 is set according to checkbox

* Remove rubocop offense and check if nationality2 is not null initially in person controller test

* style person show view to wrap text and replace some unless statements with ternary operators

* reimplement whole picture controller from api namespace, cleanup code

* move picture test from api namespace to people namespace and xdescribe old test

* fix rubocop offense

* Dont display role percentage if there is no role percentage in person overview

* revert unnecessary file change

* remove unneeded picture helpers and specs

* add translations for marital statuses

* fix loading of avatar because it vanished in specific cases

* fix translation yml files and translate marital status and nationality

* fix tests after localization and test if avatar is saved in feature spec

* replace alpha2 code with actual country name in people show view

* make picture controller inherit from new crud controller instead of api crud controller

* make people controller test also test if nationality is saved correctly

* fix specs after rebase

* move functions to add and remove roles to stimulus controller

* Fix picture controller spec and rename feature spec

* make show all link not display in edit view

* resolve styling conversations

* Rename ms translations to marital_statuses and refactor all its usages, interpolate bobs id in picture controller test

* Bring back api route to un-xdescribe api test

* move some stuff from view to helper methods in new person_helper

* make attribute headers in view look like in old skills

* fix loading of picture on form validation failure

* Move code to check if picture is cached to helper method

* rename method from avatar_cached to avatar_cached?

* add paddings to information of person for improved distinguishability

* fix deleting of roles when edit form fails
  • Loading branch information
RandomTannenbaum authored Mar 15, 2024
1 parent c49643b commit 69f4f08
Show file tree
Hide file tree
Showing 28 changed files with 633 additions and 64 deletions.
21 changes: 21 additions & 0 deletions app/assets/stylesheets/styles.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
$skills-blue: #3b7bbe;
$skills-text-gray: #8e8e8e;
$skills-gray: #f5f5f5;

@import "bootstrap";

Expand Down Expand Up @@ -42,4 +43,24 @@ pzsh-topbar {

.text-gray {
color: $skills-text-gray;
}

.person-profile-header {
background: $skills-gray;
height: 3.25rem;
padding: 12px 20px 12px 20px;
font-size: 18px;
}

.bg-skills-blue {
background-color: $skills-blue;
}

.bg-skills-blue:hover {
background-color: #3268a1;
}

.fixed-table {
table-layout: fixed;
width: 100%;
}
18 changes: 18 additions & 0 deletions app/controllers/concerns/param_converters.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module ParamConverters

private

def true?(value)
%w[1 yes true].include?(value.to_s.downcase)
end

def false?(value)
%w[0 no false].include?(value.to_s.downcase)
end

def nil_param?(value)
value == 'null' ? nil : value
end
end
9 changes: 9 additions & 0 deletions app/controllers/crud_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,15 @@ def error_messages
# rubocop:enable Rails/OutputSafety
end

def get_asset_path(filename)
manifest_file = Rails.application.assets_manifest.assets[filename]
if manifest_file
File.join(Rails.application.assets_manifest.directory, manifest_file)
else
Rails.application.assets&.[](filename)&.filename
end
end

# Class methods for CrudActions.
class << self
# Convenience callback to apply a callback on both form actions
Expand Down
32 changes: 32 additions & 0 deletions app/controllers/people/picture_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

module People
class PictureController < CrudController

self.permitted_attrs = %i[picture]

def update
person.update!(picture: params[:picture])
render json: { data: { picture_path: picture_person_path(params[:id]) } }
end

def show
picture_url = person.picture.file.nil? ? default_avatar_path : person.picture.url
send_file(picture_url, disposition: 'inline')
end

private

def model_class
Person
end

def person
@person ||= Person.find(params[:id])
end

def default_avatar_path
get_asset_path 'default_avatar.png'
end
end
end
20 changes: 12 additions & 8 deletions app/controllers/people_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@

class PeopleController < CrudController
include ExportController
include ParamConverters

# self.permitted_attrs = %i[birthdate location
# marital_status updated_by name nationality nationality2 title
# competence_notes company_id email department_id shortname]
self.permitted_attrs = [:birthdate, :location, :marital_status, :updated_by, :name, :nationality,
:nationality2, :title, :competence_notes, :company_id, :email,
:department_id, :shortname, :picture, :picture_cache,
{ person_roles_attributes: [:role_id, :person_role_level_id,
:percent, :id, :_destroy] }]

# self.nested_models = %i[advanced_trainings activities projects
# educations language_skills person_roles
# people_skills categories]

# self.permitted_relationships = %i[person_roles people_skills]
def update
if false?(params['has_nationality2']['checked'])
params['person']['nationality2'] = nil
end
super
end

def show
if format_odt?
Expand Down
35 changes: 35 additions & 0 deletions app/helpers/person_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module PersonHelper
def nationality_string(nationality, nationality2)
if nationality2.blank?
ISO3166::Country[nationality].translations[I18n.locale]
else
"#{ISO3166::Country[nationality].translations[I18n.locale]},
#{ISO3166::Country[nationality2].translations[I18n.locale]}"
end
end

def person_role_string(person_role)
"#{person_role.role.name} #{person_role.person_role_level&.level}
#{person_role.percent.nil? ? '' : "#{person_role.percent.to_i}%"}"
end

def marital_status_translation_map
Person.marital_statuses.keys.map do |marital_status|
[marital_status, t("marital_statuses.#{marital_status}")]
end
end

def country_alpha2_translation_map
ISO3166::Country.all.sort.map do |country|
[country.alpha2, country.translations[I18n.locale]]
end
end

# If the path of the avatar includes tmp, the picture is cached
# and we can load it directly without the picture controller
def avatar_cached?(picture)
picture&.file&.file&.include? 'tmp'
end
end
29 changes: 29 additions & 0 deletions app/helpers/role_form_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module RoleFormHelper
# This method creates a link with `data-id` `data-fields` attributes. These attributes are used
# to create new instances of the nested fields through Javascript.
def link_to_add_role(name, form, association)
# Takes an object and creates a new instance of its associated model
new_role = form.object.send(association).klass.new
# Saves the unique ID of the object into a variable.
# This is needed to ensure the key of the associated array is unique. This makes parsing the
# content in the `data-fields` attribute easier through Javascript.
id = new_role.object_id
# child_index` is used to ensure the key of the associated array is unique, and that it matched
# the value in the `data-id` attribute.
fields = form.fields_for(association, new_role, child_index: id) do |builder|
render("#{association.to_s.singularize}_fields", person_role: builder)
end


# This renders a simple link, but passes information into `data` attributes.
# This info can be named anything we want, but in this case
# we chose `data-id:` and `data-fields:`.
# We use `gsub("\n", "")` to remove anywhite space from the rendered partial.
# The `id:` value needs to match the value used in `child_index: id`.
link_to(name, '#',
{ class: 'add_fields', 'data-action' => 'person-roles#addField',
data: { id: id, fields: fields.gsub("\n", '') } })
end
end
9 changes: 9 additions & 0 deletions app/javascript/controllers/image_upload_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
changeImage() {
const avatarUploader = document.getElementById("avatar-uploader");
const avatar = document.getElementById("avatar");
avatar.src = URL.createObjectURL(avatarUploader.files[0])
}
}
13 changes: 11 additions & 2 deletions app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,17 @@

import { application } from "./application"

import DropdownLinksController from "./dropdown_controller"
application.register("dropdown", DropdownLinksController)
import DropdownController from "./dropdown_controller"
application.register("dropdown", DropdownController)

import ImageUploadController from "./image_upload_controller"
application.register("image-upload", ImageUploadController)

import NationalityTwoController from "./nationality_two_controller"
application.register("nationality-two", NationalityTwoController)

import PersonRolesController from "./person_roles_controller"
application.register("person-roles", PersonRolesController)

import RemoteModalController from "./remote_modal_controller"
application.register("remote-modal", RemoteModalController)
18 changes: 18 additions & 0 deletions app/javascript/controllers/nationality_two_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
setVisibility() {
const natTwoElements = document.getElementsByClassName('nationality-two')
const natTwoCheckbox = document.getElementById('nat-two-checkbox')
for(const element of natTwoElements) {
!natTwoCheckbox.checked ? element.classList.add('d-none') : element.classList.remove('d-none')
}
}

connect() {
this.setVisibility();
}
nationalityTwoVisible() {
this.setVisibility()
}
}
47 changes: 47 additions & 0 deletions app/javascript/controllers/person_roles_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
connect() {
for(let nested_field of document.getElementsByClassName("nested-fields")) {
if(nested_field.querySelector('input[type="hidden"]').value === "true") {
nested_field.style.display = "none";
}
}
}
addField(e) {
let link = e.target
// Stop the function from executing if a link or event were not passed into the function.
if (!link || !e) return;
// Prevent the browser from following the URL.
e.preventDefault();
// Save a unique timestamp to ensure the key of the associated array is unique.
let time = new Date().getTime();
// Save the data id attribute into a variable. This corresponds to `new_object.object_id`.
let linkId = link.dataset.id;
// Create a new regular expression needed to find any instance of the `new_object.object_id` used in the fields data attribute if there's a value in `linkId`.
let regexp = linkId ? new RegExp(linkId, "g") : null;
// Replace all instances of the `new_object.object_id` with `time`, and save markup into a variable if there's a value in `regexp`.
let newFields = regexp ? link.dataset.fields.replace(regexp, time) : null;
// Add the new markup to the form if there are fields to add.
newFields ? link.insertAdjacentHTML("beforebegin", newFields) : null;
}

removeField(e) {
let link = e.target
// Stop the function from executing if a link or event were not passed into the function.
if (!link || !e) return;
// Prevent the browser from following the URL.
e.preventDefault();
// Find the parent wrapper for the set of nested fields.
let fieldParent = link.closest(".nested-fields");
// If there is a parent wrapper, find the hidden delete field.
let deleteField = fieldParent
? fieldParent.querySelector('input[type="hidden"]')
: null;
// If there is a delete field, update the value to `1` and hide the corresponding nested fields.
if (deleteField) {
deleteField.value = 1;
fieldParent.style.display = "none";
}
}
}
1 change: 1 addition & 0 deletions app/models/person.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Person < ApplicationRecord
has_many :expertise_topics, through: :expertise_topic_skill_values
has_many :language_skills, dependent: :delete_all
has_many :person_roles, dependent: :destroy
accepts_nested_attributes_for :person_roles, allow_destroy: true
has_many :people_skills, dependent: :destroy
has_many :skills, through: :people_skills
has_many :roles, through: :person_roles
Expand Down
2 changes: 1 addition & 1 deletion app/views/layouts/application.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
= javascript_include_tag "application", "data-turbo-track": "reload", type: "module"
= favicon_link_tag "favicon.png"
%body.d-flex.justify-content-center
%pzsh-container.content
%div.content
%div.position-sticky.top-0.z-3
%div.d-flex.justify-content-between.bg-white
%div.d-flex
Expand Down
70 changes: 70 additions & 0 deletions app/views/people/_form.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
= form_with model: @person do |form|
%div.d-flex.flex-xl-row.flex-column
%div.col-xl-3.col-12
- if avatar_cached?(@person.picture)
%img.rounded-circle#avatar{src: "#{@person.picture}?#{Time.now.to_f}", width: '141', height: '141'}
- else
%img.rounded-circle#avatar{src: "#{@person.id}/picture?#{Time.now.to_f}", width: '141', height: '141'}
%br
%label.btn.btn-link{for: "avatar-uploader"} Bild ändern
%div.visually-hidden{"data-controller"=>"image-upload"}= form.file_field :picture, { accept: "image/", "data-action" => "image-upload#changeImage", id: "avatar-uploader" }
= form.hidden_field :picture_cache
%div.pe-5.col-xl-3.col-12
%table
%tbody
%th.fw-normal Name
%tr
%td= form.text_field :name, class: "mw-100 form-control"
%th.fw-normal Email
%tr
%td= form.text_field :email, class: "mw-100, form-control"
%th.fw-normal Abschluss
%tr
%td= form.text_field :title, class: "mw-100, form-control"
%th.fw-normal Funktionen
%div
= form.fields_for :person_roles do |person_role|
%tr
%td= render "person_role_fields", person_role: person_role
%tr
%td{"data-controller"=>"person-roles"}
= link_to_add_role "Neue Funktion", form, :person_roles
%th.fw-normal Organisationseinheit
%tr
%td= form.collection_select :department_id, Department.order(:name), :id, :name, {}, class: "form-select mw-100"
%th.fw-normal Firma
%tr
%td= form.collection_select :company_id, Company.order(:name), :id, :name, {}, class: "form-select mw-100"
%th.fw-normal Wohnort (Stadt)
%tr
%td= form.text_field :location, class: "form-control mw-100"
%div.pe-5.col-xl-3.col-12
%table
%tbody
%th.fw-normal Geburtsdatum
%tr
%td= form.date_field :birthdate, class: "form-control mw-100"
%th.fw-normal Doppelbürger
%tr
%td{"data-controller"=>"nationality-two"}= check_box :has_nationality2, "checked", {"checked" => @person.nationality2?, "data-action" => "nationality-two#nationalityTwoVisible", "id" => "nat-two-checkbox"}
%th.fw-normal Erste Nationalität
%tr
%td= form.collection_select :nationality, country_alpha2_translation_map, :first, :last, {}, class: "form-select mw-100"
%th.fw-normal.nationality-two Zweite Nationalität
%tr.nationality-two
%td= form.collection_select :nationality2, country_alpha2_translation_map, :first, :last, {}, class: "form-select mw-100"
%th.fw-normal Zivilstand
%tr
%td= form.collection_select :marital_status, marital_status_translation_map, :first, :last, { selected: @person.marital_status }, class: "form-select mw-100"
%th.fw-normal Kürzel
%tr
%td= form.text_field :shortname, class: "mw-100 form-control"
%div.col-xl-3.col-12.mw-100
%div.fw-normal Sprachen
%div.border.border-dark-subtle.mt-1.p-2.rounded
- @person.language_skills.each do |language|
%div.mb-1= "#{language.language}: #{language.level} - #{language.certificate}"
%div.mt-3
= form.submit :Speichern, { class: "btn btn-primary me-3 bg-skills-blue", id: "save-button" }
= link_to "Abbrechen", person_path, { id: "cancel-button" }
10 changes: 10 additions & 0 deletions app/views/people/_person_role_fields.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
%div.border.border-dark-subtle.rounded.p-1.fw-light.nested-fields
= person_role.hidden_field :_destroy
Rolle
= person_role.collection_select :role_id, Role.order(:name), :id, :name, {}, class: "form-select w-100 role-select"
%div
Stufe
%div.d-flex.fw-light
= person_role.collection_select :person_role_level_id, PersonRoleLevel.order(:level), :id, :level, {}, class: "form-select w-50 me-1 role-level-select"
= person_role.number_field :percent, in: 0..200, step: 1, class: "form-control w-50 person-role-percent"
%div{"data-controller"=>"person-roles"}= link_to "Remove", "#", { class: "remove_fields", 'data-action' => 'person-roles#removeField' }
Loading

0 comments on commit 69f4f08

Please sign in to comment.