mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-12-01 12:24:28 +01:00
Merge branch 'projects-improvements' into dev
This commit is contained in:
commit
875dc4a3bd
4
Gemfile
4
Gemfile
@ -30,6 +30,7 @@ group :development, :test do
|
|||||||
# comment over to use visual debugger (eg. RubyMine), uncomment to use manual debugging
|
# comment over to use visual debugger (eg. RubyMine), uncomment to use manual debugging
|
||||||
# gem 'byebug'
|
# gem 'byebug'
|
||||||
gem 'dotenv-rails'
|
gem 'dotenv-rails'
|
||||||
|
gem 'pry'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
@ -43,7 +44,6 @@ group :development do
|
|||||||
# Preview mail in the browser
|
# Preview mail in the browser
|
||||||
gem 'listen', '~> 3.0.5'
|
gem 'listen', '~> 3.0.5'
|
||||||
gem 'overcommit'
|
gem 'overcommit'
|
||||||
gem 'pry'
|
|
||||||
gem 'rb-readline'
|
gem 'rb-readline'
|
||||||
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
|
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
|
||||||
gem 'railroady'
|
gem 'railroady'
|
||||||
@ -149,3 +149,5 @@ gem 'acts_as_list'
|
|||||||
# Error reporting
|
# Error reporting
|
||||||
gem 'sentry-rails'
|
gem 'sentry-rails'
|
||||||
gem 'sentry-ruby'
|
gem 'sentry-ruby'
|
||||||
|
|
||||||
|
gem "reverse_markdown"
|
||||||
|
@ -82,7 +82,7 @@ GEM
|
|||||||
rails (>= 4.1)
|
rails (>= 4.1)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
attr_required (1.0.1)
|
attr_required (1.0.1)
|
||||||
awesome_print (1.8.0)
|
awesome_print (1.9.2)
|
||||||
axiom-types (0.1.1)
|
axiom-types (0.1.1)
|
||||||
descendants_tracker (~> 0.0.4)
|
descendants_tracker (~> 0.0.4)
|
||||||
ice_nine (~> 0.11.0)
|
ice_nine (~> 0.11.0)
|
||||||
@ -398,6 +398,8 @@ GEM
|
|||||||
responders (3.1.0)
|
responders (3.1.0)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
|
reverse_markdown (2.1.1)
|
||||||
|
nokogiri
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
rolify (5.3.0)
|
rolify (5.3.0)
|
||||||
rubocop (1.31.2)
|
rubocop (1.31.2)
|
||||||
@ -526,7 +528,7 @@ GEM
|
|||||||
zeitwerk (2.6.7)
|
zeitwerk (2.6.7)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
x86_64-darwin-20
|
x86_64-darwin-21
|
||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
@ -590,6 +592,7 @@ DEPENDENCIES
|
|||||||
redis-session-store
|
redis-session-store
|
||||||
repost
|
repost
|
||||||
responders (~> 3.0)
|
responders (~> 3.0)
|
||||||
|
reverse_markdown
|
||||||
rolify
|
rolify
|
||||||
rubocop (~> 1.31)
|
rubocop (~> 1.31)
|
||||||
rubocop-rails
|
rubocop-rails
|
||||||
|
45
app/controllers/api/project_categories_controller.rb
Normal file
45
app/controllers/api/project_categories_controller.rb
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# API Controller for resources of type ProjectCategory
|
||||||
|
class API::ProjectCategoriesController < ApplicationController
|
||||||
|
before_action :set_project_category, only: %i[update destroy]
|
||||||
|
before_action :authenticate_user!, only: %i[create update destroy]
|
||||||
|
def index
|
||||||
|
@project_categories = ProjectCategory.all
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
authorize ProjectCategory
|
||||||
|
@project_category = ProjectCategory.new(project_category_params)
|
||||||
|
if @project_category.save
|
||||||
|
render json: @project_category, status: :created
|
||||||
|
else
|
||||||
|
render json: @project_category.errors, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
authorize ProjectCategory
|
||||||
|
if @project_category.update(project_category_params)
|
||||||
|
render json: @project_category, status: :ok
|
||||||
|
else
|
||||||
|
render json: @project_category.errors, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
authorize ProjectCategory
|
||||||
|
@project_category.destroy
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_project_category
|
||||||
|
@project_category = ProjectCategory.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def project_category_params
|
||||||
|
params.require(:project_category).permit(:name)
|
||||||
|
end
|
||||||
|
end
|
@ -18,6 +18,12 @@ class API::ProjectsController < API::APIController
|
|||||||
@project = Project.friendly.find(params[:id])
|
@project = Project.friendly.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def markdown
|
||||||
|
@project = Project.friendly.find(params[:id])
|
||||||
|
authorize @project
|
||||||
|
send_data ProjectToMarkdown.new(@project).call, filename: "#{@project.name.parameterize}-#{@project.id}.md", disposition: 'attachment', type: 'text/markdown'
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@project = Project.new(project_params.merge(author_statistic_profile_id: current_user.statistic_profile.id))
|
@project = Project.new(project_params.merge(author_statistic_profile_id: current_user.statistic_profile.id))
|
||||||
if @project.save
|
if @project.save
|
||||||
@ -53,12 +59,23 @@ class API::ProjectsController < API::APIController
|
|||||||
|
|
||||||
def search
|
def search
|
||||||
service = ProjectService.new
|
service = ProjectService.new
|
||||||
res = service.search(params, current_user)
|
paginate = request.format.zip? ? false : true
|
||||||
|
res = service.search(params, current_user, paginate: paginate)
|
||||||
|
|
||||||
render json: res, status: :unprocessable_entity and return if res[:error]
|
render json: res, status: :unprocessable_entity and return if res[:error]
|
||||||
|
|
||||||
@total = res[:total]
|
respond_to do |format|
|
||||||
@projects = res[:projects]
|
format.json do
|
||||||
render :index
|
@total = res[:total]
|
||||||
|
@projects = res[:projects]
|
||||||
|
render :index
|
||||||
|
end
|
||||||
|
format.zip do
|
||||||
|
head :forbidden unless current_user.admin? || current_user.manager?
|
||||||
|
|
||||||
|
send_data ProjectsArchive.new(res[:projects]).call, filename: "projets.zip", disposition: 'attachment', type: 'application/zip'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -69,7 +86,7 @@ class API::ProjectsController < API::APIController
|
|||||||
|
|
||||||
def project_params
|
def project_params
|
||||||
params.require(:project).permit(:name, :description, :tags, :machine_ids, :component_ids, :theme_ids, :licence_id, :status_id, :state,
|
params.require(:project).permit(:name, :description, :tags, :machine_ids, :component_ids, :theme_ids, :licence_id, :status_id, :state,
|
||||||
user_ids: [], machine_ids: [], component_ids: [], theme_ids: [],
|
user_ids: [], machine_ids: [], component_ids: [], theme_ids: [], project_category_ids: [],
|
||||||
project_image_attributes: [:attachment],
|
project_image_attributes: [:attachment],
|
||||||
project_caos_attributes: %i[id attachment _destroy],
|
project_caos_attributes: %i[id attachment _destroy],
|
||||||
project_steps_attributes: [
|
project_steps_attributes: [
|
||||||
|
25
app/frontend/src/javascript/api/project-category.ts
Normal file
25
app/frontend/src/javascript/api/project-category.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import apiClient from './clients/api-client';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { ProjectCategory } from '../models/project-category';
|
||||||
|
|
||||||
|
export default class ProjectCategoryAPI {
|
||||||
|
static async index (): Promise<Array<ProjectCategory>> {
|
||||||
|
const res: AxiosResponse<Array<ProjectCategory>> = await apiClient.get('/api/project_categories');
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create (newProjectCategory: ProjectCategory): Promise<ProjectCategory> {
|
||||||
|
const res: AxiosResponse<ProjectCategory> = await apiClient.post('/api/project_categories', { project_category: newProjectCategory });
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async update (updatedProjectCategory: ProjectCategory): Promise<ProjectCategory> {
|
||||||
|
const res: AxiosResponse<ProjectCategory> = await apiClient.patch(`/api/project_categories/${updatedProjectCategory.id}`, { project_category: updatedProjectCategory });
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async destroy (projectCategoryId: number): Promise<void> {
|
||||||
|
const res: AxiosResponse<void> = await apiClient.delete(`/api/project_categories/${projectCategoryId}`);
|
||||||
|
return res?.data;
|
||||||
|
}
|
||||||
|
}
|
@ -12,8 +12,8 @@
|
|||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
Application.Controllers.controller('AdminProjectsController', ['$scope', '$state', 'Component', 'Licence', 'Theme', 'componentsPromise', 'licencesPromise', 'themesPromise', '_t', 'Member', 'uiTourService', 'settingsPromise', 'growl',
|
Application.Controllers.controller('AdminProjectsController', ['$scope', '$state', 'Component', 'Licence', 'Theme', 'ProjectCategory', 'componentsPromise', 'licencesPromise', 'themesPromise', 'projectCategoriesPromise', '_t', 'Member', 'uiTourService', 'settingsPromise', 'growl',
|
||||||
function ($scope, $state, Component, Licence, Theme, componentsPromise, licencesPromise, themesPromise, _t, Member, uiTourService, settingsPromise, growl) {
|
function ($scope, $state, Component, Licence, Theme, ProjectCategory, componentsPromise, licencesPromise, themesPromise, projectCategoriesPromise, _t, Member, uiTourService, settingsPromise, growl) {
|
||||||
// Materials list (plastic, wood ...)
|
// Materials list (plastic, wood ...)
|
||||||
$scope.components = componentsPromise;
|
$scope.components = componentsPromise;
|
||||||
|
|
||||||
@ -23,6 +23,9 @@ Application.Controllers.controller('AdminProjectsController', ['$scope', '$state
|
|||||||
// Themes list (cooking, sport ...)
|
// Themes list (cooking, sport ...)
|
||||||
$scope.themes = themesPromise;
|
$scope.themes = themesPromise;
|
||||||
|
|
||||||
|
// Project categories list (generic categorization)
|
||||||
|
$scope.projectCategories = projectCategoriesPromise;
|
||||||
|
|
||||||
// Application settings
|
// Application settings
|
||||||
$scope.allSettings = settingsPromise;
|
$scope.allSettings = settingsPromise;
|
||||||
|
|
||||||
@ -115,6 +118,49 @@ Application.Controllers.controller('AdminProjectsController', ['$scope', '$state
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a new project category / Update an existing project category to the server (form validation callback)
|
||||||
|
* @param data {Object} project category name
|
||||||
|
* @param [data] {number} project category id, in case of update
|
||||||
|
*/
|
||||||
|
$scope.saveProjectCategory = function (data, id) {
|
||||||
|
if (id != null) {
|
||||||
|
return ProjectCategory.update({ id }, data);
|
||||||
|
} else {
|
||||||
|
return ProjectCategory.save(data, resp => $scope.projectCategories[$scope.projectCategories.length - 1].id = resp.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the project category at the specified index
|
||||||
|
* @param index {number} project category index in the $scope.projectCategories array
|
||||||
|
*/
|
||||||
|
$scope.removeProjectCategory = function (index) {
|
||||||
|
ProjectCategory.delete($scope.projectCategories[index]);
|
||||||
|
return $scope.projectCategories.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new empty entry in the $scope.projectCategories array
|
||||||
|
*/
|
||||||
|
$scope.addProjectCategory = function () {
|
||||||
|
$scope.inserted = { name: '' };
|
||||||
|
$scope.projectCategories.push($scope.inserted);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the newly inserted but not saved project category / Cancel the current project category modification
|
||||||
|
* @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
|
||||||
|
* @param index {number} project category index in the $scope.projectCategories array
|
||||||
|
*/
|
||||||
|
$scope.cancelProjectCategory = function (rowform, index) {
|
||||||
|
if ($scope.projectCategories[index].id != null) {
|
||||||
|
rowform.$cancel();
|
||||||
|
} else {
|
||||||
|
$scope.projectCategories.splice(index, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a new licence / Update an existing licence to the server (form validation callback)
|
* Saves a new licence / Update an existing licence to the server (form validation callback)
|
||||||
* @param data {Object} licence name and description
|
* @param data {Object} licence name and description
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
* - $scope.themes = [{Theme}]
|
* - $scope.themes = [{Theme}]
|
||||||
* - $scope.licences = [{Licence}]
|
* - $scope.licences = [{Licence}]
|
||||||
* - $scope.allowedExtensions = [{String}]
|
* - $scope.allowedExtensions = [{String}]
|
||||||
|
* - $scope.projectCategoriesWording = [{String}]
|
||||||
* - $scope.submited(content)
|
* - $scope.submited(content)
|
||||||
* - $scope.cancel()
|
* - $scope.cancel()
|
||||||
* - $scope.addFile()
|
* - $scope.addFile()
|
||||||
@ -43,7 +44,7 @@
|
|||||||
* - $state (Ui-Router) [ 'app.public.projects_show', 'app.public.projects_list' ]
|
* - $state (Ui-Router) [ 'app.public.projects_show', 'app.public.projects_list' ]
|
||||||
*/
|
*/
|
||||||
class ProjectsController {
|
class ProjectsController {
|
||||||
constructor ($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, _t) {
|
constructor ($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, ProjectCategory, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t) {
|
||||||
// remove codeview from summernote editor
|
// remove codeview from summernote editor
|
||||||
$scope.summernoteOptsProject = angular.copy($rootScope.summernoteOpts);
|
$scope.summernoteOptsProject = angular.copy($rootScope.summernoteOpts);
|
||||||
$scope.summernoteOptsProject.toolbar[6][1].splice(1, 1);
|
$scope.summernoteOptsProject.toolbar[6][1].splice(1, 1);
|
||||||
@ -78,6 +79,16 @@ class ProjectsController {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Retrieve the list of themes from the server
|
||||||
|
ProjectCategory.query().$promise.then(function (data) {
|
||||||
|
$scope.projectCategories = data.map(function (d) {
|
||||||
|
return ({
|
||||||
|
id: d.id,
|
||||||
|
name: d.name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Retrieve the list of licences from the server
|
// Retrieve the list of licences from the server
|
||||||
Licence.query().$promise.then(function (data) {
|
Licence.query().$promise.then(function (data) {
|
||||||
$scope.licences = data.map(function (d) {
|
$scope.licences = data.map(function (d) {
|
||||||
@ -104,6 +115,8 @@ class ProjectsController {
|
|||||||
// List of extensions allowed for CAD attachements upload
|
// List of extensions allowed for CAD attachements upload
|
||||||
$scope.allowedExtensions = allowedExtensions.setting.value.split(' ');
|
$scope.allowedExtensions = allowedExtensions.setting.value.split(' ');
|
||||||
|
|
||||||
|
$scope.projectCategoriesWording = projectCategoriesWording.setting.value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For use with ngUpload (https://github.com/twilson63/ngUpload).
|
* For use with ngUpload (https://github.com/twilson63/ngUpload).
|
||||||
* Intended to be the callback when an upload is done: any raised error will be stacked in the
|
* Intended to be the callback when an upload is done: any raised error will be stacked in the
|
||||||
@ -281,8 +294,8 @@ class ProjectsController {
|
|||||||
/**
|
/**
|
||||||
* Controller used on projects listing page
|
* Controller used on projects listing page
|
||||||
*/
|
*/
|
||||||
Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'Project', 'machinesPromise', 'themesPromise', 'componentsPromise', 'paginationService', 'OpenlabProject', '$window', 'growl', '_t', '$location', '$timeout', 'settingsPromise', 'openLabActive',
|
Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'Project', 'machinesPromise', 'themesPromise', 'projectCategoriesPromise', 'componentsPromise', 'paginationService', 'OpenlabProject', '$window', 'growl', '_t', '$location', '$timeout', 'settingsPromise', 'openLabActive', 'Member', 'Diacritics',
|
||||||
function ($scope, $state, Project, machinesPromise, themesPromise, componentsPromise, paginationService, OpenlabProject, $window, growl, _t, $location, $timeout, settingsPromise, openLabActive) {
|
function ($scope, $state, Project, machinesPromise, themesPromise, projectCategoriesPromise, componentsPromise, paginationService, OpenlabProject, $window, growl, _t, $location, $timeout, settingsPromise, openLabActive, Member, Diacritics) {
|
||||||
/* PRIVATE STATIC CONSTANTS */
|
/* PRIVATE STATIC CONSTANTS */
|
||||||
|
|
||||||
// Number of projects added to the page when the user clicks on 'load more projects'
|
// Number of projects added to the page when the user clicks on 'load more projects'
|
||||||
@ -294,12 +307,24 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
|||||||
// Fab-manager's instance ID in the openLab network
|
// Fab-manager's instance ID in the openLab network
|
||||||
$scope.openlabAppId = settingsPromise.openlab_app_id;
|
$scope.openlabAppId = settingsPromise.openlab_app_id;
|
||||||
|
|
||||||
|
// settings of optional filters
|
||||||
|
$scope.memberFilterPresence = settingsPromise.projects_list_member_filter_presence !== 'false';
|
||||||
|
$scope.dateFiltersPresence = settingsPromise.projects_list_date_filters_presence !== 'false';
|
||||||
|
$scope.projectCategoriesFilterPlaceholder = settingsPromise.project_categories_filter_placeholder;
|
||||||
|
|
||||||
// Is openLab enabled on the instance?
|
// Is openLab enabled on the instance?
|
||||||
$scope.openlab = {
|
$scope.openlab = {
|
||||||
projectsActive: openLabActive.isPresent,
|
projectsActive: openLabActive.isPresent,
|
||||||
searchOverWholeNetwork: settingsPromise.openlab_default === 'true'
|
searchOverWholeNetwork: settingsPromise.openlab_default === 'true'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!$scope.memberFilterPresence) {
|
||||||
|
$location.$$search.member_id = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
fromDate = $location.$$search.from_date ? new Date($location.$$search.from_date) : undefined;
|
||||||
|
toDate = $location.$$search.to_date ? new Date($location.$$search.to_date) : undefined;
|
||||||
|
|
||||||
// default search parameters
|
// default search parameters
|
||||||
$scope.search = {
|
$scope.search = {
|
||||||
q: ($location.$$search.q || ''),
|
q: ($location.$$search.q || ''),
|
||||||
@ -307,7 +332,27 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
|||||||
machine_id: (parseInt($location.$$search.machine_id) || undefined),
|
machine_id: (parseInt($location.$$search.machine_id) || undefined),
|
||||||
component_id: (parseInt($location.$$search.component_id) || undefined),
|
component_id: (parseInt($location.$$search.component_id) || undefined),
|
||||||
theme_id: (parseInt($location.$$search.theme_id) || undefined),
|
theme_id: (parseInt($location.$$search.theme_id) || undefined),
|
||||||
status_id: (parseInt($location.$$search.status_id) || undefined)
|
status_id: (parseInt($location.$$search.status_id) || undefined),
|
||||||
|
project_category_id: (parseInt($location.$$search.project_category_id) || undefined),
|
||||||
|
member_id: (parseInt($location.$$search.member_id) || undefined),
|
||||||
|
from_date: fromDate,
|
||||||
|
to_date: toDate
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.autoCompleteMemberName = function (nameLookup) {
|
||||||
|
if (!nameLookup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$scope.isLoadingMembers = true;
|
||||||
|
const asciiName = Diacritics.remove(nameLookup);
|
||||||
|
|
||||||
|
const q = { query: asciiName };
|
||||||
|
|
||||||
|
Member.search(q, function (users) {
|
||||||
|
$scope.matchingMembers = users;
|
||||||
|
$scope.isLoadingMembers = false;
|
||||||
|
}
|
||||||
|
, function (error) { console.error(error); });
|
||||||
};
|
};
|
||||||
|
|
||||||
// list of projects to display
|
// list of projects to display
|
||||||
@ -319,6 +364,9 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
|||||||
// list of themes / used for filtering
|
// list of themes / used for filtering
|
||||||
$scope.themes = themesPromise;
|
$scope.themes = themesPromise;
|
||||||
|
|
||||||
|
// list of projectCategories / used for filtering
|
||||||
|
$scope.projectCategories = projectCategoriesPromise;
|
||||||
|
|
||||||
// list of components / used for filtering
|
// list of components / used for filtering
|
||||||
$scope.components = componentsPromise;
|
$scope.components = componentsPromise;
|
||||||
|
|
||||||
@ -332,6 +380,8 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
|||||||
$scope.triggerSearch();
|
$scope.triggerSearch();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.zipUrl = '/api/projects/search.zip';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback triggered when the button "search from the whole network" is toggled
|
* Callback triggered when the button "search from the whole network" is toggled
|
||||||
*/
|
*/
|
||||||
@ -361,6 +411,10 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
|||||||
$scope.search.component_id = undefined;
|
$scope.search.component_id = undefined;
|
||||||
$scope.search.theme_id = undefined;
|
$scope.search.theme_id = undefined;
|
||||||
$scope.search.status_id = undefined;
|
$scope.search.status_id = undefined;
|
||||||
|
$scope.search.member_id = undefined;
|
||||||
|
$scope.search.from_date = undefined;
|
||||||
|
$scope.search.to_date = undefined;
|
||||||
|
$scope.search.project_category_id = undefined;
|
||||||
$scope.$apply();
|
$scope.$apply();
|
||||||
$scope.setUrlQueryParams($scope.search);
|
$scope.setUrlQueryParams($scope.search);
|
||||||
$scope.triggerSearch();
|
$scope.triggerSearch();
|
||||||
@ -389,7 +443,10 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
|||||||
} else {
|
} else {
|
||||||
updateUrlParam('whole_network', 'f');
|
updateUrlParam('whole_network', 'f');
|
||||||
$scope.projectsPagination = new paginationService.Instance(Project, currentPage, PROJECTS_PER_PAGE, null, { }, loadMoreCallback, 'search');
|
$scope.projectsPagination = new paginationService.Instance(Project, currentPage, PROJECTS_PER_PAGE, null, { }, loadMoreCallback, 'search');
|
||||||
Project.search({ search: $scope.search, page: currentPage, per_page: PROJECTS_PER_PAGE }, function (projectsPromise) {
|
const fromDate = $scope.search.from_date ? $scope.search.from_date.toLocaleDateString() : undefined;
|
||||||
|
const toDate = $scope.search.to_date ? $scope.search.to_date.toLocaleDateString() : undefined;
|
||||||
|
const searchParams = Object.assign({}, $scope.search, { from_date: fromDate, to_date: toDate });
|
||||||
|
Project.search({ search: searchParams, page: currentPage, per_page: PROJECTS_PER_PAGE }, function (projectsPromise) {
|
||||||
$scope.projectsPagination.totalCount = projectsPromise.meta.total;
|
$scope.projectsPagination.totalCount = projectsPromise.meta.total;
|
||||||
$scope.projects = projectsPromise.projects;
|
$scope.projects = projectsPromise.projects;
|
||||||
});
|
});
|
||||||
@ -420,6 +477,22 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
|||||||
updateUrlParam('component_id', search.component_id);
|
updateUrlParam('component_id', search.component_id);
|
||||||
updateUrlParam('machine_id', search.machine_id);
|
updateUrlParam('machine_id', search.machine_id);
|
||||||
updateUrlParam('status_id', search.status_id);
|
updateUrlParam('status_id', search.status_id);
|
||||||
|
updateUrlParam('member_id', search.member_id);
|
||||||
|
const fromDate = search.from_date ? search.from_date.toDateString() : undefined;
|
||||||
|
updateUrlParam('from_date', fromDate);
|
||||||
|
const toDate = search.to_date ? search.to_date.toDateString() : undefined;
|
||||||
|
updateUrlParam('to_date', toDate);
|
||||||
|
updateUrlParam('project_category_id', search.project_category_id);
|
||||||
|
$scope.zipUrl = '/api/projects/search.zip?' + new URLSearchParams({ search: JSON.stringify($location.search()) }).toString();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.setSearchMemberId = function (searchMember) {
|
||||||
|
if (searchMember) {
|
||||||
|
$scope.search.member_id = searchMember.id;
|
||||||
|
} else {
|
||||||
|
$scope.search.member_id = undefined;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -450,6 +523,11 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
|||||||
} else {
|
} else {
|
||||||
$scope.openlab.searchOverWholeNetwork = $scope.openlab.projectsActive;
|
$scope.openlab.searchOverWholeNetwork = $scope.openlab.projectsActive;
|
||||||
}
|
}
|
||||||
|
if ($location.$$search.member_id && $scope.memberFilterPresence) {
|
||||||
|
Member.get({ id: $location.$$search.member_id }, function (member) {
|
||||||
|
$scope.searchMember = member;
|
||||||
|
});
|
||||||
|
}
|
||||||
return $scope.triggerSearch();
|
return $scope.triggerSearch();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -496,8 +574,8 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
|
|||||||
/**
|
/**
|
||||||
* Controller used in the project creation page
|
* Controller used in the project creation page
|
||||||
*/
|
*/
|
||||||
Application.Controllers.controller('NewProjectController', ['$rootScope', '$scope', '$state', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', 'Status', '$document', 'CSRF', 'Diacritics', 'dialogs', 'allowedExtensions', '_t',
|
Application.Controllers.controller('NewProjectController', ['$rootScope', '$scope', '$state', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'ProjectCategory', 'Licence', 'Status', '$document', 'CSRF', 'Diacritics', 'dialogs', 'allowedExtensions', 'projectCategoriesWording', '_t',
|
||||||
function ($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, Licence, Status, $document, CSRF, Diacritics, dialogs, allowedExtensions, _t) {
|
function ($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, ProjectCategory, Licence, Status, $document, CSRF, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t) {
|
||||||
CSRF.setMetaTags();
|
CSRF.setMetaTags();
|
||||||
|
|
||||||
// API URL where the form will be posted
|
// API URL where the form will be posted
|
||||||
@ -529,15 +607,15 @@ Application.Controllers.controller('NewProjectController', ['$rootScope', '$scop
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Using the ProjectsController
|
// Using the ProjectsController
|
||||||
return new ProjectsController($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, _t);
|
return new ProjectsController($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, ProjectCategory, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t);
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller used in the project edition page
|
* Controller used in the project edition page
|
||||||
*/
|
*/
|
||||||
Application.Controllers.controller('EditProjectController', ['$rootScope', '$scope', '$state', '$transition$', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', 'Status', '$document', 'CSRF', 'projectPromise', 'Diacritics', 'dialogs', 'allowedExtensions', '_t',
|
Application.Controllers.controller('EditProjectController', ['$rootScope', '$scope', '$state', '$transition$', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'ProjectCategory', 'Licence', 'Status', '$document', 'CSRF', 'projectPromise', 'Diacritics', 'dialogs', 'allowedExtensions', 'projectCategoriesWording', '_t',
|
||||||
function ($rootScope, $scope, $state, $transition$, Project, Machine, Member, Component, Theme, Licence, Status, $document, CSRF, projectPromise, Diacritics, dialogs, allowedExtensions, _t) {
|
function ($rootScope, $scope, $state, $transition$, Project, Machine, Member, Component, Theme, ProjectCategory, Licence, Status, $document, CSRF, projectPromise, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t) {
|
||||||
/* PUBLIC SCOPE */
|
/* PUBLIC SCOPE */
|
||||||
|
|
||||||
// API URL where the form will be posted
|
// API URL where the form will be posted
|
||||||
@ -583,7 +661,7 @@ Application.Controllers.controller('EditProjectController', ['$rootScope', '$sco
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Using the ProjectsController
|
// Using the ProjectsController
|
||||||
return new ProjectsController($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, _t);
|
return new ProjectsController($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, ProjectCategory, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t);
|
||||||
};
|
};
|
||||||
|
|
||||||
// !!! MUST BE CALLED AT THE END of the controller
|
// !!! MUST BE CALLED AT THE END of the controller
|
||||||
@ -594,14 +672,15 @@ Application.Controllers.controller('EditProjectController', ['$rootScope', '$sco
|
|||||||
/**
|
/**
|
||||||
* Controller used in the public project's details page
|
* Controller used in the public project's details page
|
||||||
*/
|
*/
|
||||||
Application.Controllers.controller('ShowProjectController', ['$scope', '$state', 'projectPromise', 'shortnamePromise', '$location', '$uibModal', 'dialogs', '_t',
|
Application.Controllers.controller('ShowProjectController', ['$scope', '$state', 'projectPromise', 'shortnamePromise', 'projectCategoriesWording', '$location', '$uibModal', 'dialogs', '_t',
|
||||||
function ($scope, $state, projectPromise, shortnamePromise, $location, $uibModal, dialogs, _t) {
|
function ($scope, $state, projectPromise, shortnamePromise, projectCategoriesWording, $location, $uibModal, dialogs, _t) {
|
||||||
/* PUBLIC SCOPE */
|
/* PUBLIC SCOPE */
|
||||||
|
|
||||||
// Store the project's details
|
// Store the project's details
|
||||||
$scope.project = projectPromise;
|
$scope.project = projectPromise;
|
||||||
$scope.projectUrl = $location.absUrl();
|
$scope.projectUrl = $location.absUrl();
|
||||||
$scope.disqusShortname = shortnamePromise.setting.value;
|
$scope.disqusShortname = shortnamePromise.setting.value;
|
||||||
|
$scope.projectCategoriesWording = projectCategoriesWording.setting.value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if the provided user has the edition rights on the current project
|
* Test if the provided user has the edition rights on the current project
|
||||||
|
5
app/frontend/src/javascript/models/project-category.ts
Normal file
5
app/frontend/src/javascript/models/project-category.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Type model used in ProjectSettings and its child components
|
||||||
|
export interface ProjectCategory {
|
||||||
|
name: string,
|
||||||
|
id?: number,
|
||||||
|
}
|
@ -198,7 +198,11 @@ export const fabHubSettings = [
|
|||||||
export const projectsSettings = [
|
export const projectsSettings = [
|
||||||
'allowed_cad_extensions',
|
'allowed_cad_extensions',
|
||||||
'allowed_cad_mime_types',
|
'allowed_cad_mime_types',
|
||||||
'disqus_shortname'
|
'disqus_shortname',
|
||||||
|
'projects_list_member_filter_presence',
|
||||||
|
'projects_list_date_filters_presence',
|
||||||
|
'project_categories_filter_placeholder',
|
||||||
|
'project_categories_wording'
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const prepaidPacksSettings = [
|
export const prepaidPacksSettings = [
|
||||||
@ -221,7 +225,7 @@ export const pricingSettings = [
|
|||||||
'extended_prices_in_same_day'
|
'extended_prices_in_same_day'
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const poymentSettings = [
|
export const paymentSettings = [
|
||||||
'payment_gateway'
|
'payment_gateway'
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
@ -291,7 +295,7 @@ export const allSettings = [
|
|||||||
...registrationSettings,
|
...registrationSettings,
|
||||||
...adminSettings,
|
...adminSettings,
|
||||||
...pricingSettings,
|
...pricingSettings,
|
||||||
...poymentSettings,
|
...paymentSettings,
|
||||||
...displaySettings,
|
...displaySettings,
|
||||||
...storeSettings,
|
...storeSettings,
|
||||||
...trainingsSettings,
|
...trainingsSettings,
|
||||||
|
@ -301,8 +301,9 @@ angular.module('application.router', ['ui.router'])
|
|||||||
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }],
|
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }],
|
||||||
componentsPromise: ['Component', function (Component) { return Component.query().$promise; }],
|
componentsPromise: ['Component', function (Component) { return Component.query().$promise; }],
|
||||||
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
|
||||||
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['openlab_app_id', 'openlab_default']" }).$promise; }],
|
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['openlab_app_id', 'openlab_default', 'projects_list_member_filter_presence', 'projects_list_date_filters_presence', 'project_categories_filter_placeholder']" }).$promise; }],
|
||||||
openLabActive: ['Setting', function (Setting) { return Setting.isPresent({ name: 'openlab_app_secret' }).$promise; }]
|
openLabActive: ['Setting', function (Setting) { return Setting.isPresent({ name: 'openlab_app_secret' }).$promise; }],
|
||||||
|
projectCategoriesPromise: ['ProjectCategory', function (ProjectCategory) { return ProjectCategory.query().$promise; }]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('app.logged.projects_new', {
|
.state('app.logged.projects_new', {
|
||||||
@ -314,7 +315,8 @@ angular.module('application.router', ['ui.router'])
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }]
|
allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }],
|
||||||
|
projectCategoriesWording: ['Setting', function (Setting) { return Setting.get({ name: 'project_categories_wording' }).$promise; }]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('app.public.projects_show', {
|
.state('app.public.projects_show', {
|
||||||
@ -327,7 +329,8 @@ angular.module('application.router', ['ui.router'])
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
projectPromise: ['$transition$', 'Project', function ($transition$, Project) { return Project.get({ id: $transition$.params().id }).$promise; }],
|
projectPromise: ['$transition$', 'Project', function ($transition$, Project) { return Project.get({ id: $transition$.params().id }).$promise; }],
|
||||||
shortnamePromise: ['Setting', function (Setting) { return Setting.get({ name: 'disqus_shortname' }).$promise; }]
|
shortnamePromise: ['Setting', function (Setting) { return Setting.get({ name: 'disqus_shortname' }).$promise; }],
|
||||||
|
projectCategoriesWording: ['Setting', function (Setting) { return Setting.get({ name: 'project_categories_wording' }).$promise; }]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('app.logged.projects_edit', {
|
.state('app.logged.projects_edit', {
|
||||||
@ -340,7 +343,8 @@ angular.module('application.router', ['ui.router'])
|
|||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
projectPromise: ['$transition$', 'Project', function ($transition$, Project) { return Project.get({ id: $transition$.params().id }).$promise; }],
|
projectPromise: ['$transition$', 'Project', function ($transition$, Project) { return Project.get({ id: $transition$.params().id }).$promise; }],
|
||||||
allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }]
|
allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }],
|
||||||
|
projectCategoriesWording: ['Setting', function (Setting) { return Setting.get({ name: 'project_categories_wording' }).$promise; }]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -735,10 +739,11 @@ angular.module('application.router', ['ui.router'])
|
|||||||
componentsPromise: ['Component', function (Component) { return Component.query().$promise; }],
|
componentsPromise: ['Component', function (Component) { return Component.query().$promise; }],
|
||||||
licencesPromise: ['Licence', function (Licence) { return Licence.query().$promise; }],
|
licencesPromise: ['Licence', function (Licence) { return Licence.query().$promise; }],
|
||||||
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }],
|
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }],
|
||||||
|
projectCategoriesPromise: ['ProjectCategory', function (ProjectCategory) { return ProjectCategory.query().$promise; }],
|
||||||
settingsPromise: ['Setting', function (Setting) {
|
settingsPromise: ['Setting', function (Setting) {
|
||||||
return Setting.query({
|
return Setting.query({
|
||||||
names: "['feature_tour_display', 'disqus_shortname', 'allowed_cad_extensions', " +
|
names: "['feature_tour_display', 'disqus_shortname', 'allowed_cad_extensions', " +
|
||||||
"'allowed_cad_mime_types', 'openlab_app_id', 'openlab_app_secret', 'openlab_default']"
|
"'allowed_cad_mime_types', 'openlab_app_id', 'openlab_app_secret', 'openlab_default', 'project_categories_filter_placeholder', 'project_categories_wording']"
|
||||||
}).$promise;
|
}).$promise;
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
11
app/frontend/src/javascript/services/project_category.js
Normal file
11
app/frontend/src/javascript/services/project_category.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
Application.Services.factory('ProjectCategory', ['$resource', function ($resource) {
|
||||||
|
return $resource('/api/project_categories/:id',
|
||||||
|
{ id: '@id' }, {
|
||||||
|
update: {
|
||||||
|
method: 'PUT'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}]);
|
@ -42,7 +42,10 @@
|
|||||||
<uib-tab heading="{{ 'app.admin.projects.statuses' | translate }}" index="3">
|
<uib-tab heading="{{ 'app.admin.projects.statuses' | translate }}" index="3">
|
||||||
<status-settings on-error="onError" on-success="onSuccess"/>
|
<status-settings on-error="onError" on-success="onSuccess"/>
|
||||||
</uib-tab>
|
</uib-tab>
|
||||||
<uib-tab heading="{{ 'app.admin.projects.settings.title' | translate }}" index="4" class="settings-tab">
|
<uib-tab heading="{{ 'app.admin.projects.project_categories' | translate }}" index="4">
|
||||||
|
<ng-include src="'/admin/projects/project_categories.html'"></ng-include>
|
||||||
|
</uib-tab>
|
||||||
|
<uib-tab heading="{{ 'app.admin.projects.settings.title' | translate }}" index="5" class="settings-tab">
|
||||||
<ng-include src="'/admin/projects/settings.html'"></ng-include>
|
<ng-include src="'/admin/projects/settings.html'"></ng-include>
|
||||||
</uib-tab>
|
</uib-tab>
|
||||||
</uib-tabset>
|
</uib-tabset>
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
<button type="button" class="btn btn-warning m-t m-b" ng-click="addProjectCategory()" translate>{{ 'app.admin.projects.add_a_new_project_category' }}</button>
|
||||||
|
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:80%" translate>{{ 'app.admin.project_categories.name' }}</th>
|
||||||
|
<th style="width:20%"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="projectCategory in projectCategories">
|
||||||
|
<td>
|
||||||
|
<span editable-text="projectCategory.name" e-name="name" e-form="rowform" e-required>
|
||||||
|
{{ projectCategory.name }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<!-- form -->
|
||||||
|
<form editable-form name="rowform" onbeforesave="saveProjectCategory($data, projectCategory.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == projectCategory">
|
||||||
|
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelProjectCategory(rowform, $index)" class="btn btn-default">
|
||||||
|
<i class="fa fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<div class="buttons" ng-show="!rowform.$visible">
|
||||||
|
<button class="btn btn-default" ng-click="rowform.$show()">
|
||||||
|
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'app.shared.buttons.edit' }}</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger" ng-click="removeProjectCategory($index)">
|
||||||
|
<i class="fa fa-trash-o"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
@ -95,3 +95,49 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="panel panel-default m-t-lg">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<span class="font-sbold" translate>{{ 'app.admin.projects.settings.filters' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="row">
|
||||||
|
<boolean-setting name="'projects_list_member_filter_presence'"
|
||||||
|
label="'app.admin.settings.projects_list_member_filter_presence' | translate"
|
||||||
|
on-success="onSuccess"
|
||||||
|
on-error="onError"
|
||||||
|
class-name="'m-l'"></boolean-setting>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<boolean-setting name="'projects_list_date_filters_presence'"
|
||||||
|
label="'app.admin.settings.projects_list_date_filters_presence' | translate"
|
||||||
|
on-success="onSuccess"
|
||||||
|
on-error="onError"
|
||||||
|
class-name="'m-l'"></boolean-setting>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel panel-default m-t-lg">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<span class="font-sbold" translate>{{ 'app.admin.projects.settings.project_categories' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<text-setting name="project_categories_filter_placeholder"
|
||||||
|
settings="allSettings"
|
||||||
|
label="app.admin.settings.project_categories_filter_placeholder">
|
||||||
|
</text-setting>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row m-t">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<text-setting name="project_categories_wording"
|
||||||
|
settings="allSettings"
|
||||||
|
label="app.admin.settings.project_categories_wording">
|
||||||
|
</text-setting>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@ -279,6 +279,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="widget panel b-a m m-t-lg">
|
||||||
|
<div class="panel-heading b-b small">
|
||||||
|
<h3 translate>{{ projectCategoriesWording }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="widget-content no-bg wrapper">
|
||||||
|
<input type="hidden" name="project[project_category_ids][]" value="" />
|
||||||
|
<ui-select multiple ng-model="project.project_category_ids" class="form-control">
|
||||||
|
<ui-select-match>
|
||||||
|
<span ng-bind="$item.name"></span>
|
||||||
|
<input type="hidden" name="project[project_category_ids][]" value="{{$item.id}}" />
|
||||||
|
</ui-select-match>
|
||||||
|
<ui-select-choices repeat="pc.id as pc in (projectCategories | filter: $select.search)">
|
||||||
|
<span ng-bind-html="pc.name | highlight: $select.search"></span>
|
||||||
|
</ui-select-choices>
|
||||||
|
</ui-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<section class="projects">
|
<section class="projects">
|
||||||
<div class="projects-filters">
|
<div class="projects-filters">
|
||||||
<header>
|
<header>
|
||||||
<h3>Filter</h3>
|
<h3 translate>{{ 'app.public.projects_list.filter' }}</h3>
|
||||||
<a href="javascript:void(0);" class="fab-button is-black" name="button" ng-click="resetFiltersAndTriggerSearch()" ng-show="!openlab.searchOverWholeNetwork">{{ 'app.public.projects_list.reset_all_filters' | translate }}</a>
|
<a href="javascript:void(0);" class="fab-button is-black" name="button" ng-click="resetFiltersAndTriggerSearch()" ng-show="!openlab.searchOverWholeNetwork">{{ 'app.public.projects_list.reset_all_filters' | translate }}</a>
|
||||||
</header>
|
</header>
|
||||||
<span class="switch" ng-if="openlab.projectsActive" uib-tooltip="{{ 'app.public.projects_list.tooltip_openlab_projects_switch' | translate }}" tooltip-trigger="mouseenter">
|
<span class="switch" ng-if="openlab.projectsActive" uib-tooltip="{{ 'app.public.projects_list.tooltip_openlab_projects_switch' | translate }}" tooltip-trigger="mouseenter">
|
||||||
@ -61,12 +61,57 @@
|
|||||||
<option value="" translate>{{ 'app.public.projects_list.all_themes' }}</option>
|
<option value="" translate>{{ 'app.public.projects_list.all_themes' }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select ng-model="search.project_category_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="pc.id as pc.name for pc in projectCategories">
|
||||||
|
<option value="" translate>{{ projectCategoriesFilterPlaceholder }}</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<select ng-model="search.component_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="t.id as t.name for t in components">
|
<select ng-model="search.component_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="t.id as t.name for t in components">
|
||||||
<option value="" translate>{{ 'app.public.projects_list.all_materials' }}</option>
|
<option value="" translate>{{ 'app.public.projects_list.all_materials' }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<ui-select ng-if="currentUser && memberFilterPresence" ng-model="searchMember" on-select="setSearchMemberId(searchMember) && setUrlQueryParams(search) && triggerSearch()">
|
||||||
|
<ui-select-match allow-clear="true" placeholder="{{ 'app.public.projects_list.filter_by_member' | translate }}">
|
||||||
|
<span ng-bind="$select.selected.name"></span>
|
||||||
|
</ui-select-match>
|
||||||
|
<ui-select-choices repeat="m in matchingMembers" refresh="autoCompleteMemberName($select.search)" refresh-delay="300">
|
||||||
|
<span ng-bind-html="m.name | highlight: $select.search"></span>
|
||||||
|
</ui-select-choices>
|
||||||
|
</ui-select>
|
||||||
|
|
||||||
|
<label class="form-group m-n" ng-if="dateFiltersPresence">
|
||||||
|
<div class="form-item-header">
|
||||||
|
<p translate>{{ 'app.public.projects_list.created_from' }}</p>
|
||||||
|
</div>
|
||||||
|
<input class="form-control"
|
||||||
|
ng-model="search.from_date"
|
||||||
|
ng-model-options='{ debounce: 1000 }'
|
||||||
|
ng-change="setUrlQueryParams(search) && triggerSearch()"
|
||||||
|
type="date"
|
||||||
|
min="2000-01-01"
|
||||||
|
max="2060-01-01"/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="form-group m-n" ng-if="dateFiltersPresence">
|
||||||
|
<div class="form-item-header">
|
||||||
|
<p translate>{{ 'app.public.projects_list.created_to' }}</p>
|
||||||
|
</div>
|
||||||
|
<input class="form-control"
|
||||||
|
ng-model="search.to_date"
|
||||||
|
ng-model-options='{ debounce: 1000 }'
|
||||||
|
ng-change="setUrlQueryParams(search) && triggerSearch()"
|
||||||
|
type="date"
|
||||||
|
min="2000-01-01"
|
||||||
|
max="2060-01-01"/>
|
||||||
|
</label>
|
||||||
|
|
||||||
<status-filter on-filter-change="onStatusChange" current-status-index="search.status_id"/>
|
<status-filter on-filter-change="onStatusChange" current-status-index="search.status_id"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center m m-b-lg" ng-if="!openlab.searchOverWholeNetwork && (projects.length != 0) && (isAuthorized('admin') || isAuthorized('manager'))">
|
||||||
|
<a class="btn bg-light text-black" ng-href="{{ zipUrl }}" target="_blank">
|
||||||
|
<i class="fa fa-download"></i> {{ 'app.public.projects_list.download_archive' | translate }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="projects-list">
|
<div class="projects-list">
|
||||||
|
@ -1,187 +1,202 @@
|
|||||||
<div>
|
<div>
|
||||||
|
|
||||||
<section class="heading b-b">
|
<section class="heading b-b">
|
||||||
|
<div class="row no-gutter">
|
||||||
|
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||||
|
<section class="heading-btn">
|
||||||
|
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
||||||
|
<section class="heading-title">
|
||||||
|
<h1>{{ project.name }} <span class="badge" ng-if="project.state == 'draft'" translate>{{ 'app.public.projects_show.rough_draft' }}</span></h1>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
|
||||||
|
<section class="heading-actions wrapper">
|
||||||
|
|
||||||
|
<a ui-sref="app.logged.projects_edit({id: project.id})" ng-if="projectEditableBy(currentUser) || isAuthorized('admin')" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs text-u-c text-sm"><i class="fa fa-edit"></i> {{ 'app.shared.buttons.edit' | translate }}</a>
|
||||||
|
<a ng-click="deleteProject(event)" ng-if="projectDeletableBy(currentUser) || isAuthorized('admin')" class="btn btn-lg btn-danger b-2x rounded no-b m-t-xs"><i class="fa fa-trash-o"></i></a>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<div class="row no-gutter">
|
<div class="row no-gutter">
|
||||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
<div class="col-sm-12 col-md-12 col-lg-9 b-r-lg">
|
||||||
<section class="heading-btn">
|
|
||||||
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
|
|
||||||
<section class="heading-title">
|
|
||||||
<h1>{{ project.name }} <span class="badge" ng-if="project.state == 'draft'" translate>{{ 'app.public.projects_show.rough_draft' }}</span></h1>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
|
|
||||||
<section class="heading-actions wrapper">
|
|
||||||
|
|
||||||
<a ui-sref="app.logged.projects_edit({id: project.id})" ng-if="projectEditableBy(currentUser) || isAuthorized('admin')" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs text-u-c text-sm"><i class="fa fa-edit"></i> {{ 'app.shared.buttons.edit' | translate }}</a>
|
|
||||||
<a ng-click="deleteProject(event)" ng-if="projectDeletableBy(currentUser) || isAuthorized('admin')" class="btn btn-lg btn-danger b-2x rounded no-b m-t-xs"><i class="fa fa-trash-o"></i></a>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="row no-gutter">
|
|
||||||
<div class="col-sm-12 col-md-12 col-lg-9 b-r-lg">
|
|
||||||
|
|
||||||
<div class="article wrapper-lg">
|
|
||||||
|
|
||||||
<div class="article-thumbnail" ng-if="project.project_image">
|
|
||||||
<a href="{{project.project_full_image}}" target="_blank"><img ng-src="{{project.project_image}}" alt="{{project.name}}"></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 translate>{{ 'app.public.projects_show.project_description' }}</h3>
|
|
||||||
<p ng-bind-html="project.description | toTrusted"></p>
|
|
||||||
|
|
||||||
<div class="article-steps">
|
|
||||||
<div class="row article-step m-b-lg" ng-repeat="step in project.project_steps_attributes">
|
|
||||||
<div class="col-md-12 m-b-xs">
|
|
||||||
<h3 class="well well-simple step-title">{{ 'app.public.projects_show.step_N' | translate:{INDEX:step.step_nb} }} : {{step.title}}</h3>
|
|
||||||
</div>
|
|
||||||
<div ng-repeat-start="image in step.project_step_images_attributes" class="clearfix" ng-if="$index % 3 == 0"></div>
|
|
||||||
<div class="col-md-4" ng-repeat-end>
|
|
||||||
<a href="{{image.attachment_full_url}}" target="_blank"><img class="m-b" ng-src="{{image.attachment_url}}" alt="{{image.attachment}}" ></a>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-8" ng-class="{'col-md-12' : step.project_step_images_attributes.length > 1 || step.project_step_images_attributes.length == 0}">
|
|
||||||
|
|
||||||
<p ng-bind-html="step.description | toTrusted"></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="article wrapper-lg">
|
||||||
|
|
||||||
|
<div class="article-thumbnail" ng-if="project.project_image">
|
||||||
|
<a href="{{project.project_full_image}}" target="_blank"><img ng-src="{{project.project_image}}" alt="{{project.name}}"></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3 translate>{{ 'app.public.projects_show.project_description' }}</h3>
|
||||||
|
<p ng-bind-html="project.description | toTrusted"></p>
|
||||||
|
|
||||||
|
<div class="article-steps">
|
||||||
|
<div class="row article-step m-b-lg" ng-repeat="step in project.project_steps_attributes">
|
||||||
|
<div class="col-md-12 m-b-xs">
|
||||||
|
<h3 class="well well-simple step-title">{{ 'app.public.projects_show.step_N' | translate:{INDEX:step.step_nb} }} : {{step.title}}</h3>
|
||||||
|
</div>
|
||||||
|
<div ng-repeat-start="image in step.project_step_images_attributes" class="clearfix" ng-if="$index % 3 == 0"></div>
|
||||||
|
<div class="col-md-4" ng-repeat-end>
|
||||||
|
<a href="{{image.attachment_full_url}}" target="_blank"><img class="m-b" ng-src="{{image.attachment_url}}" alt="{{image.attachment}}" ></a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8" ng-class="{'col-md-12' : step.project_step_images_attributes.length > 1 || step.project_step_images_attributes.length == 0}">
|
||||||
|
|
||||||
|
<p ng-bind-html="step.description | toTrusted"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center" id="social-share">
|
||||||
|
<a ng-href="{{shareOnFacebook()}}" target="_blank" class="btn btn-facebook btn-lg m-t"><i class="fa fa-facebook m-r"></i> {{ 'app.public.projects_show.share_on_facebook' | translate }}</a>
|
||||||
|
<a ng-href="{{shareOnTwitter()}}" target="_blank" class="btn btn-twitter btn-lg m-t"><i class="fa fa-twitter m-r"></i> {{ 'app.public.projects_show.share_on_twitter' | translate }}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wrapper-lg" ng-if="disqusShortname">
|
||||||
|
<dir-disqus disqus-shortname="{{ disqusShortname }}" disqus-identifier="project_{{ project.id }}" disqus-url="{{ projectUrl }}" ready-to-bind="{{ project }}">
|
||||||
|
</dir-disqus>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center" id="social-share">
|
<div class="col-sm-12 col-md-12 col-lg-3">
|
||||||
<a ng-href="{{shareOnFacebook()}}" target="_blank" class="btn btn-facebook btn-lg m-t"><i class="fa fa-facebook m-r"></i> {{ 'app.public.projects_show.share_on_facebook' | translate }}</a>
|
|
||||||
<a ng-href="{{shareOnTwitter()}}" target="_blank" class="btn btn-twitter btn-lg m-t"><i class="fa fa-twitter m-r"></i> {{ 'app.public.projects_show.share_on_twitter' | translate }}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="wrapper-lg" ng-if="disqusShortname">
|
|
||||||
<dir-disqus disqus-shortname="{{ disqusShortname }}" disqus-identifier="project_{{ project.id }}" disqus-url="{{ projectUrl }}" ready-to-bind="{{ project }}">
|
|
||||||
</dir-disqus>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-12 col-md-12 col-lg-3">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="text-center m-t-lg m-v">
|
<div class="text-center m-t-lg m-v">
|
||||||
<div class="thumb-lg m-b-xs">
|
<div class="thumb-lg m-b-xs">
|
||||||
<fab-user-avatar ng-model="project.author.user_avatar" avatar-class="thumb-50"></fab-user-avatar>
|
<fab-user-avatar ng-model="project.author.user_avatar" avatar-class="thumb-50"></fab-user-avatar>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a ng-show="project.author_id" class="text-sm font-sbold project-author" ui-sref="app.logged.members_show({id: project.author.slug})">
|
||||||
|
<i> {{ 'app.public.projects_show.by_name' | translate:{NAME:project.author.first_name} }}</i>
|
||||||
|
</a>
|
||||||
|
<span ng-hide="project.author_id" class="text-sm font-sbold text-gray" translate>{{ 'app.public.projects_show.deleted_user' }}</span>
|
||||||
|
</div>
|
||||||
|
<small class="text-xs m-b"><i>{{ 'app.public.projects_show.posted_on_' | translate }} {{project.created_at | amDateFormat: 'LL'}}</i></small>
|
||||||
|
|
||||||
|
<div class="m" ng-if="project.themes">
|
||||||
|
<span ng-repeat="theme in project.themes" class="badge m-r-sm">
|
||||||
|
{{theme.name}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<a ng-show="project.author_id" class="text-sm font-sbold project-author" ui-sref="app.logged.members_show({id: project.author.slug})">
|
|
||||||
<i> {{ 'app.public.projects_show.by_name' | translate:{NAME:project.author.first_name} }}</i>
|
<section class="widget panel b-a m" ng-if="project.project_caos_attributes">
|
||||||
|
<div class="panel-heading b-b">
|
||||||
|
<span class="badge bg-warning pull-right">{{project.project_caos_attributes.length}}</span>
|
||||||
|
<h3 translate translate-values="{COUNT:project.project_caos_attributes.length}">{{ 'app.public.projects_show.CAD_file_to_download' }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="widget-content list-group list-group-lg no-bg auto">
|
||||||
|
<li ng-repeat="file in project.project_caos_attributes" class="list-group-item no-b clearfix">
|
||||||
|
<a target="_blank" ng-href="{{file.attachment_url}}" download="{{file.attachment_url}}"><i class="fa fa-arrow-circle-o-down"> </i> {{file.attachment | humanize : 25}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="widget panel b-a m" ng-if="project.status">
|
||||||
|
<div class="panel-heading b-b">
|
||||||
|
<h3 translate>{{ 'app.public.projects_show.status' }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{{ project.status.name }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="widget panel b-a m" ng-if="project.machines">
|
||||||
|
<div class="panel-heading b-b">
|
||||||
|
<span class="badge bg-warning pull-right">{{project.machines.length}}</span>
|
||||||
|
<h3 translate>{{ 'app.public.projects_show.machines_and_materials' }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="widget-content list-group list-group-lg no-bg auto">
|
||||||
|
<li ng-repeat="machine in project.machines" class="list-group-item no-b clearfix">
|
||||||
|
<a ui-sref="app.public.machines_show({id: machine.id})">{{machine.name}}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="widget-content list-group list-group-lg no-bg auto">
|
||||||
|
<li ng-repeat="component in project.components" class="list-group-item no-b clearfix">
|
||||||
|
{{component.name}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="widget panel b-a m" ng-if="project.project_users.length > 0">
|
||||||
|
<div class="panel-heading b-b">
|
||||||
|
<span class="badge bg-warning pull-right">{{project.project_users.length}}</span>
|
||||||
|
<h3 translate>{{ 'app.public.projects_show.collaborators' }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="widget-content list-group list-group-lg no-bg auto">
|
||||||
|
<li class="list-group-item no-b clearfix block-link" ng-repeat="collaborator in project.project_users" ui-sref="app.logged.members_show({id: collaborator.slug})">
|
||||||
|
<span class="pull-left thumb-sm avatar m-r">
|
||||||
|
<fab-user-avatar ng-model="collaborator.user_avatar" avatar-class="thumb-38"></fab-user-avatar>
|
||||||
|
|
||||||
|
<i class="on b-white bottom" ng-if="collaborator.is_valid"></i>
|
||||||
|
<i class="off b-white bottom" ng-if="!collaborator.is_valid"></i>
|
||||||
|
</span>
|
||||||
|
<span class="clear"><span>{{collaborator.full_name}}</span>
|
||||||
|
<small class="text-muted clear text-ellipsis text-c">{{collaborator.username}}</small>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="widget panel b-a m" ng-if="project.licence">
|
||||||
|
<div class="panel-heading b-b">
|
||||||
|
<h3 translate>{{ 'app.public.projects_show.licence' }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{{ project.licence.name }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="widget panel b-a m" ng-if="project.tags">
|
||||||
|
<div class="panel-heading b-b">
|
||||||
|
<h3 translate>{{ 'app.shared.project.tags' }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<pre>{{ project.tags }}</pre>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="widget panel b-a m" ng-if="project.project_categories">
|
||||||
|
<div class="panel-heading b-b">
|
||||||
|
<h3 translate>{{ projectCategoriesWording }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="widget-content list-group list-group-lg no-bg auto">
|
||||||
|
<li ng-repeat="projectCategory in project.project_categories" class="list-group-item no-b clearfix">
|
||||||
|
{{projectCategory.name}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="text-center m m-b-lg" ng-if="projectEditableBy(currentUser) || isAuthorized('admin') || isAuthorized('manager')">
|
||||||
|
<a class="btn bg-light text-black" ng-href="api/projects/{{ project.id}}/markdown" target="_blank">
|
||||||
|
<i class="fa fa-download"></i> {{ 'app.public.projects_show.markdown_file' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<span ng-hide="project.author_id" class="text-sm font-sbold text-gray" translate>{{ 'app.public.projects_show.deleted_user' }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<small class="text-xs m-b"><i>{{ 'app.public.projects_show.posted_on_' | translate }} {{project.created_at | amDateFormat: 'LL'}}</i></small>
|
|
||||||
|
|
||||||
<div class="m" ng-if="project.themes">
|
|
||||||
<span ng-repeat="theme in project.themes" class="badge m-r-sm">
|
|
||||||
{{theme.name}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<section class="widget panel b-a m" ng-if="project.project_caos_attributes">
|
|
||||||
<div class="panel-heading b-b">
|
|
||||||
<span class="badge bg-warning pull-right">{{project.project_caos_attributes.length}}</span>
|
|
||||||
<h3 translate translate-values="{COUNT:project.project_caos_attributes.length}">{{ 'app.public.projects_show.CAD_file_to_download' }}</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="widget-content list-group list-group-lg no-bg auto">
|
|
||||||
<li ng-repeat="file in project.project_caos_attributes" class="list-group-item no-b clearfix">
|
|
||||||
<a target="_blank" ng-href="{{file.attachment_url}}" download="{{file.attachment_url}}"><i class="fa fa-arrow-circle-o-down"> </i> {{file.attachment | humanize : 25}}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="widget panel b-a m" ng-if="project.status">
|
|
||||||
<div class="panel-heading b-b">
|
|
||||||
<h3 translate>{{ 'app.public.projects_show.status' }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
{{ project.status.name }}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="widget panel b-a m" ng-if="project.machines">
|
|
||||||
<div class="panel-heading b-b">
|
|
||||||
<span class="badge bg-warning pull-right">{{project.machines.length}}</span>
|
|
||||||
<h3 translate>{{ 'app.public.projects_show.machines_and_materials' }}</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="widget-content list-group list-group-lg no-bg auto">
|
|
||||||
<li ng-repeat="machine in project.machines" class="list-group-item no-b clearfix">
|
|
||||||
<a ui-sref="app.public.machines_show({id: machine.id})">{{machine.name}}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul class="widget-content list-group list-group-lg no-bg auto">
|
|
||||||
<li ng-repeat="component in project.components" class="list-group-item no-b clearfix">
|
|
||||||
{{component.name}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="widget panel b-a m" ng-if="project.project_users.length > 0">
|
|
||||||
<div class="panel-heading b-b">
|
|
||||||
<span class="badge bg-warning pull-right">{{project.project_users.length}}</span>
|
|
||||||
<h3 translate>{{ 'app.public.projects_show.collaborators' }}</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="widget-content list-group list-group-lg no-bg auto">
|
|
||||||
<li class="list-group-item no-b clearfix block-link" ng-repeat="collaborator in project.project_users" ui-sref="app.logged.members_show({id: collaborator.slug})">
|
|
||||||
<span class="pull-left thumb-sm avatar m-r">
|
|
||||||
<fab-user-avatar ng-model="collaborator.user_avatar" avatar-class="thumb-38"></fab-user-avatar>
|
|
||||||
|
|
||||||
<i class="on b-white bottom" ng-if="collaborator.is_valid"></i>
|
|
||||||
<i class="off b-white bottom" ng-if="!collaborator.is_valid"></i>
|
|
||||||
</span>
|
|
||||||
<span class="clear"><span>{{collaborator.full_name}}</span>
|
|
||||||
<small class="text-muted clear text-ellipsis text-c">{{collaborator.username}}</small>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="widget panel b-a m" ng-if="project.licence">
|
|
||||||
<div class="panel-heading b-b">
|
|
||||||
<h3 translate>{{ 'app.public.projects_show.licence' }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
{{ project.licence.name }}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="widget panel b-a m" ng-if="project.tags">
|
|
||||||
<div class="panel-heading b-b">
|
|
||||||
<h3 translate>{{ 'app.shared.project.tags' }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<pre>{{ project.tags }}</pre>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="widget b-t">
|
<section class="widget b-t">
|
||||||
|
<div class="widget-content text-center m-t">
|
||||||
<div class="widget-content text-center m-t">
|
<a ng-click="signalAbuse($event)"><i class="fa fa-warning"></i> {{ 'app.public.projects_show.report_an_abuse' | translate }}</a>
|
||||||
<a ng-click="signalAbuse($event)"><i class="fa fa-warning"></i> {{ 'app.public.projects_show.report_an_abuse' | translate }}</a>
|
</div>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
@ -196,6 +196,10 @@ module SettingsHelper
|
|||||||
events_banner_cta_active
|
events_banner_cta_active
|
||||||
events_banner_cta_label
|
events_banner_cta_label
|
||||||
events_banner_cta_url
|
events_banner_cta_url
|
||||||
|
projects_list_member_filter_presence
|
||||||
|
projects_list_date_filters_presence
|
||||||
|
project_categories_filter_placeholder
|
||||||
|
project_categories_wording
|
||||||
].freeze
|
].freeze
|
||||||
end
|
end
|
||||||
# rubocop:enable Metrics/ModuleLength
|
# rubocop:enable Metrics/ModuleLength
|
||||||
|
@ -41,6 +41,8 @@ class Project < ApplicationRecord
|
|||||||
accepts_nested_attributes_for :project_steps, allow_destroy: true
|
accepts_nested_attributes_for :project_steps, allow_destroy: true
|
||||||
|
|
||||||
has_many :abuses, as: :signaled, dependent: :destroy, class_name: 'Abuse'
|
has_many :abuses, as: :signaled, dependent: :destroy, class_name: 'Abuse'
|
||||||
|
has_many :projects_project_categories, dependent: :destroy
|
||||||
|
has_many :project_categories, through: :projects_project_categories
|
||||||
|
|
||||||
# validations
|
# validations
|
||||||
validates :author, :name, presence: true
|
validates :author, :name, presence: true
|
||||||
@ -68,6 +70,7 @@ class Project < ApplicationRecord
|
|||||||
scope :with_component, ->(component_ids) { joins(:projects_components).where(projects_components: { component_id: component_ids }) }
|
scope :with_component, ->(component_ids) { joins(:projects_components).where(projects_components: { component_id: component_ids }) }
|
||||||
scope :with_space, ->(spaces_ids) { joins(:projects_spaces).where(projects_spaces: { space_id: spaces_ids }) }
|
scope :with_space, ->(spaces_ids) { joins(:projects_spaces).where(projects_spaces: { space_id: spaces_ids }) }
|
||||||
scope :with_status, ->(statuses_ids) { where(status_id: statuses_ids) }
|
scope :with_status, ->(statuses_ids) { where(status_id: statuses_ids) }
|
||||||
|
scope :with_project_category, ->(project_category_ids) { joins(:projects_project_categories).where(projects_project_categories: { project_category_id: project_category_ids }) }
|
||||||
pg_search_scope :search,
|
pg_search_scope :search,
|
||||||
against: :search_vector,
|
against: :search_vector,
|
||||||
using: {
|
using: {
|
||||||
|
6
app/models/project_category.rb
Normal file
6
app/models/project_category.rb
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
class ProjectCategory < ApplicationRecord
|
||||||
|
validates :name, presence: true
|
||||||
|
|
||||||
|
has_many :projects_project_categories, dependent: :destroy
|
||||||
|
has_many :projects, through: :projects_project_categories
|
||||||
|
end
|
@ -5,4 +5,6 @@ class ProjectStep < ApplicationRecord
|
|||||||
belongs_to :project, touch: true
|
belongs_to :project, touch: true
|
||||||
has_many :project_step_images, as: :viewable, dependent: :destroy
|
has_many :project_step_images, as: :viewable, dependent: :destroy
|
||||||
accepts_nested_attributes_for :project_step_images, allow_destroy: true, reject_if: :all_blank
|
accepts_nested_attributes_for :project_step_images, allow_destroy: true, reject_if: :all_blank
|
||||||
|
|
||||||
|
default_scope -> { order(:step_nb) }
|
||||||
end
|
end
|
||||||
|
4
app/models/projects_project_category.rb
Normal file
4
app/models/projects_project_category.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
class ProjectsProjectCategory < ApplicationRecord
|
||||||
|
belongs_to :project
|
||||||
|
belongs_to :project_category
|
||||||
|
end
|
16
app/policies/project_category_policy.rb
Normal file
16
app/policies/project_category_policy.rb
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Check if user is an admin to allow create, update and destroy project_category
|
||||||
|
class ProjectCategoryPolicy < ApplicationPolicy
|
||||||
|
def create?
|
||||||
|
user.admin?
|
||||||
|
end
|
||||||
|
|
||||||
|
def update?
|
||||||
|
create?
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy?
|
||||||
|
create?
|
||||||
|
end
|
||||||
|
end
|
@ -16,10 +16,14 @@ class ProjectPolicy < ApplicationPolicy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update?
|
def update?
|
||||||
user.admin? or record.author.user_id == user.id or record.users.include?(user)
|
user.admin? || record.author.user_id == user.id || record.users.include?(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def markdown?
|
||||||
|
user.admin? || user.manager? || record.author.user_id == user.id || record.users.include?(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy?
|
def destroy?
|
||||||
user.admin? or record.author.user_id == user.id
|
user.admin? || record.author.user_id == user.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -46,7 +46,8 @@ class SettingPolicy < ApplicationPolicy
|
|||||||
external_id machines_banner_active machines_banner_text machines_banner_cta_active machines_banner_cta_label
|
external_id machines_banner_active machines_banner_text machines_banner_cta_active machines_banner_cta_label
|
||||||
machines_banner_cta_url trainings_banner_active trainings_banner_text trainings_banner_cta_active trainings_banner_cta_label
|
machines_banner_cta_url trainings_banner_active trainings_banner_text trainings_banner_cta_active trainings_banner_cta_label
|
||||||
trainings_banner_cta_url events_banner_active events_banner_text events_banner_cta_active events_banner_cta_label
|
trainings_banner_cta_url events_banner_active events_banner_text events_banner_cta_active events_banner_cta_label
|
||||||
events_banner_cta_url]
|
events_banner_cta_url projects_list_member_filter_presence projects_list_date_filters_presence
|
||||||
|
project_categories_filter_placeholder project_categories_wording]
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
# Provides methods for Project
|
# Provides methods for Project
|
||||||
class ProjectService
|
class ProjectService
|
||||||
def search(params, current_user)
|
def search(params, current_user, paginate: true)
|
||||||
connection = ActiveRecord::Base.connection
|
connection = ActiveRecord::Base.connection
|
||||||
return { error: 'invalid adapter' } unless connection.instance_values['config'][:adapter] == 'postgresql'
|
return { error: 'invalid adapter' } unless connection.instance_values['config'][:adapter] == 'postgresql'
|
||||||
|
|
||||||
search_from_postgre(params, current_user)
|
search_from_postgre(params, current_user, paginate: paginate)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def search_from_postgre(params, current_user)
|
def search_from_postgre(params, current_user, paginate: true)
|
||||||
query_params = JSON.parse(params[:search])
|
query_params = JSON.parse(params[:search] || "{}")
|
||||||
|
|
||||||
records = Project.published_or_drafts(current_user&.statistic_profile&.id)
|
records = Project.published_or_drafts(current_user&.statistic_profile&.id)
|
||||||
records = Project.user_projects(current_user&.statistic_profile&.id) if query_params['from'] == 'mine'
|
records = Project.user_projects(current_user&.statistic_profile&.id) if query_params['from'] == 'mine'
|
||||||
@ -21,14 +21,32 @@ class ProjectService
|
|||||||
records = records.with_machine(query_params['machine_id']) if query_params['machine_id'].present?
|
records = records.with_machine(query_params['machine_id']) if query_params['machine_id'].present?
|
||||||
records = records.with_component(query_params['component_id']) if query_params['component_id'].present?
|
records = records.with_component(query_params['component_id']) if query_params['component_id'].present?
|
||||||
records = records.with_theme(query_params['theme_id']) if query_params['theme_id'].present?
|
records = records.with_theme(query_params['theme_id']) if query_params['theme_id'].present?
|
||||||
|
records = records.with_project_category(query_params['project_category_id']) if query_params['project_category_id'].present?
|
||||||
records = records.with_space(query_params['space_id']) if query_params['space_id'].present?
|
records = records.with_space(query_params['space_id']) if query_params['space_id'].present?
|
||||||
records = records.with_status(query_params['status_id']) if query_params['status_id'].present?
|
records = records.with_status(query_params['status_id']) if query_params['status_id'].present?
|
||||||
|
|
||||||
|
if query_params['member_id'].present?
|
||||||
|
member = User.find(query_params['member_id'])
|
||||||
|
if member
|
||||||
|
records = records.where(id: Project.user_projects(member.statistic_profile.id)).or(Project.where(id: Project.collaborations(member.id)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
created_from = Time.zone.parse(query_params['from_date']).beginning_of_day if query_params['from_date'].present?
|
||||||
|
created_to = Time.zone.parse(query_params['to_date']).end_of_day if query_params['to_date'].present?
|
||||||
|
if created_from || created_to
|
||||||
|
records = records.where(created_at: created_from..created_to)
|
||||||
|
end
|
||||||
|
|
||||||
records = if query_params['q'].present?
|
records = if query_params['q'].present?
|
||||||
records.search(query_params['q'])
|
records.search(query_params['q'])
|
||||||
else
|
else
|
||||||
records.order(created_at: :desc)
|
records.order(created_at: :desc)
|
||||||
end
|
end
|
||||||
|
|
||||||
{ total: records.count, projects: records.includes(:users, :project_image).page(params[:page]) }
|
records = records.includes(:users, :project_image)
|
||||||
|
records = records.page(params[:page]) if paginate
|
||||||
|
|
||||||
|
{ total: records.count, projects: records }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
88
app/services/project_to_markdown.rb
Normal file
88
app/services/project_to_markdown.rb
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
class ProjectToMarkdown
|
||||||
|
attr_reader :project
|
||||||
|
|
||||||
|
def initialize(project)
|
||||||
|
@project = project
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
md = []
|
||||||
|
|
||||||
|
md << "# #{project.name}"
|
||||||
|
|
||||||
|
md << "![#{I18n.t('app.shared.project.illustration')}](#{full_url(project.project_image.attachment.url)})" if project.project_image
|
||||||
|
|
||||||
|
md << ReverseMarkdown.convert(project.description.to_s)
|
||||||
|
|
||||||
|
project_steps = project.project_steps
|
||||||
|
|
||||||
|
if project_steps.present?
|
||||||
|
md << "## #{I18n.t('app.shared.project.steps')}"
|
||||||
|
|
||||||
|
project_steps.each do |project_step|
|
||||||
|
md << "### #{I18n.t('app.shared.project.step_N').gsub('{INDEX}', project_step.step_nb.to_s)} : #{project_step.title}"
|
||||||
|
md << ReverseMarkdown.convert(project_step.description.to_s)
|
||||||
|
|
||||||
|
project_step.project_step_images.each_with_index do |image, i|
|
||||||
|
md << "![#{I18n.t('app.shared.project.step_image')} #{i+1}](#{full_url(project.project_image.attachment.url)})"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
md << "## #{I18n.t('app.shared.project.author')}"
|
||||||
|
md << project.author&.user&.profile&.full_name
|
||||||
|
|
||||||
|
if project.themes.present?
|
||||||
|
md << "## #{I18n.t('app.shared.project.themes')}"
|
||||||
|
md << project.themes.map(&:name).join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
|
if project.project_caos.present?
|
||||||
|
md << "## #{I18n.t('app.shared.project.CAD_files')}"
|
||||||
|
project.project_caos.each do |cao|
|
||||||
|
md << "![#{cao.attachment_identifier}](#{full_url(cao.attachment_url)})"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if project.status
|
||||||
|
md << "## #{I18n.t('app.shared.project.status')}"
|
||||||
|
md << project.status.name
|
||||||
|
end
|
||||||
|
|
||||||
|
if project.machines.present?
|
||||||
|
md << "## #{I18n.t('app.shared.project.employed_machines')}"
|
||||||
|
md << project.machines.map(&:name).join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
|
if project.components.present?
|
||||||
|
md << "## #{I18n.t('app.shared.project.employed_materials')}"
|
||||||
|
md << project.components.map(&:name).join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
|
if project.users.present?
|
||||||
|
md << "## #{I18n.t('app.shared.project.collaborators')}"
|
||||||
|
md << project.users.map { |u| u.profile.full_name }.join(', ')
|
||||||
|
end
|
||||||
|
|
||||||
|
if project.licence.present?
|
||||||
|
md << "## #{I18n.t('app.shared.project.licence')}"
|
||||||
|
md << project.licence.name
|
||||||
|
end
|
||||||
|
|
||||||
|
if project.tags.present?
|
||||||
|
md << "## #{I18n.t('app.shared.project.tags')}"
|
||||||
|
md << project.tags
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
md = md.reject { |line| line.blank? }
|
||||||
|
|
||||||
|
md.join("\n\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def full_url(path)
|
||||||
|
"#{Rails.application.routes.url_helpers.root_url[...-1]}#{path}"
|
||||||
|
end
|
||||||
|
end
|
22
app/services/projects_archive.rb
Normal file
22
app/services/projects_archive.rb
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
class ProjectsArchive
|
||||||
|
attr_reader :projects
|
||||||
|
|
||||||
|
def initialize(projects)
|
||||||
|
@projects = projects
|
||||||
|
end
|
||||||
|
|
||||||
|
def call
|
||||||
|
stringio = Zip::OutputStream.write_buffer do |zio|
|
||||||
|
projects.includes(:project_image, :themes,
|
||||||
|
:project_caos, :status, :machines,
|
||||||
|
:components, :licence,
|
||||||
|
project_steps: :project_step_images,
|
||||||
|
author: { user: :profile },
|
||||||
|
users: :profile).find_each do |project|
|
||||||
|
zio.put_next_entry("#{project.name.parameterize}-#{project.id}.md")
|
||||||
|
zio.write ProjectToMarkdown.new(project).call
|
||||||
|
end
|
||||||
|
end
|
||||||
|
stringio.string
|
||||||
|
end
|
||||||
|
end
|
5
app/views/api/project_categories/index.json.jbuilder
Normal file
5
app/views/api/project_categories/index.json.jbuilder
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
json.array!(@project_categories) do |project_category|
|
||||||
|
json.extract! project_category, :id, :name
|
||||||
|
end
|
@ -7,6 +7,7 @@ json.user_ids project.user_ids
|
|||||||
json.machine_ids project.machine_ids
|
json.machine_ids project.machine_ids
|
||||||
json.theme_ids project.theme_ids
|
json.theme_ids project.theme_ids
|
||||||
json.component_ids project.component_ids
|
json.component_ids project.component_ids
|
||||||
|
json.project_category_ids project.project_category_ids
|
||||||
json.tags project.tags
|
json.tags project.tags
|
||||||
json.name project.name
|
json.name project.name
|
||||||
json.description project.description
|
json.description project.description
|
||||||
|
@ -39,6 +39,11 @@ json.themes @project.themes do |t|
|
|||||||
json.id t.id
|
json.id t.id
|
||||||
json.name t.name
|
json.name t.name
|
||||||
end
|
end
|
||||||
|
json.project_category_ids @project.project_category_ids
|
||||||
|
json.project_categories @project.project_categories do |t|
|
||||||
|
json.id t.id
|
||||||
|
json.name t.name
|
||||||
|
end
|
||||||
json.user_ids @project.user_ids
|
json.user_ids @project.user_ids
|
||||||
json.project_users @project.project_users do |pu|
|
json.project_users @project.project_users do |pu|
|
||||||
json.id pu.user.id
|
json.id pu.user.id
|
||||||
|
@ -415,6 +415,8 @@ en:
|
|||||||
add_a_material: "Add a material"
|
add_a_material: "Add a material"
|
||||||
themes: "Themes"
|
themes: "Themes"
|
||||||
add_a_new_theme: "Add a new theme"
|
add_a_new_theme: "Add a new theme"
|
||||||
|
project_categories: "Categories"
|
||||||
|
add_a_new_project_category: "Add a new category"
|
||||||
licences: "Licences"
|
licences: "Licences"
|
||||||
statuses: "Statuses"
|
statuses: "Statuses"
|
||||||
description: "Description"
|
description: "Description"
|
||||||
@ -445,6 +447,10 @@ en:
|
|||||||
open_lab_app_secret: "Secret"
|
open_lab_app_secret: "Secret"
|
||||||
openlab_default_info_html: "In the projects gallery, visitors can switch between two views: all shared projects from the whole OpenLab network, or only the projects documented in your Fab Lab.<br/>Here, you can choose which view is shown by default."
|
openlab_default_info_html: "In the projects gallery, visitors can switch between two views: all shared projects from the whole OpenLab network, or only the projects documented in your Fab Lab.<br/>Here, you can choose which view is shown by default."
|
||||||
default_to_openlab: "Display OpenLab by default"
|
default_to_openlab: "Display OpenLab by default"
|
||||||
|
filters: Projects list filters
|
||||||
|
project_categories: Categories
|
||||||
|
project_categories:
|
||||||
|
name: "Name"
|
||||||
projects_setting:
|
projects_setting:
|
||||||
add: "Add"
|
add: "Add"
|
||||||
actions_controls: "Actions"
|
actions_controls: "Actions"
|
||||||
@ -1773,6 +1779,10 @@ en:
|
|||||||
extended_prices_in_same_day: "Extended prices in the same day"
|
extended_prices_in_same_day: "Extended prices in the same day"
|
||||||
public_registrations: "Public registrations"
|
public_registrations: "Public registrations"
|
||||||
show_username_in_admin_list: "Show the username in the list"
|
show_username_in_admin_list: "Show the username in the list"
|
||||||
|
projects_list_member_filter_presence: "Presence of member filter on projects list"
|
||||||
|
projects_list_date_filters_presence: "Presence of date filters on projects list"
|
||||||
|
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
|
||||||
|
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
|
||||||
overlapping_options:
|
overlapping_options:
|
||||||
training_reservations: "Trainings"
|
training_reservations: "Trainings"
|
||||||
machine_reservations: "Machines"
|
machine_reservations: "Machines"
|
||||||
|
@ -415,6 +415,8 @@ fr:
|
|||||||
add_a_material: "Ajouter un matériau"
|
add_a_material: "Ajouter un matériau"
|
||||||
themes: "Thématiques"
|
themes: "Thématiques"
|
||||||
add_a_new_theme: "Ajouter une nouvelle thématique"
|
add_a_new_theme: "Ajouter une nouvelle thématique"
|
||||||
|
project_categories: "Catégories"
|
||||||
|
add_a_new_project_category: "Ajouter une nouvelle catégorie"
|
||||||
licences: "Licences"
|
licences: "Licences"
|
||||||
statuses: "Statuts"
|
statuses: "Statuts"
|
||||||
description: "Description"
|
description: "Description"
|
||||||
@ -445,6 +447,10 @@ fr:
|
|||||||
open_lab_app_secret: "Secret"
|
open_lab_app_secret: "Secret"
|
||||||
openlab_default_info_html: "Dans la galerie de projets, les visiteurs peuvent choisir entre deux vues : tous les projets de l'ensemble du réseau OpenLab, ou uniquement les projets documentés dans votre Fab Lab.<br/>Ici, vous pouvez choisir quelle vue est affichée par défaut."
|
openlab_default_info_html: "Dans la galerie de projets, les visiteurs peuvent choisir entre deux vues : tous les projets de l'ensemble du réseau OpenLab, ou uniquement les projets documentés dans votre Fab Lab.<br/>Ici, vous pouvez choisir quelle vue est affichée par défaut."
|
||||||
default_to_openlab: "Afficher OpenLab par défaut"
|
default_to_openlab: "Afficher OpenLab par défaut"
|
||||||
|
filters: Filtres de la vue liste
|
||||||
|
project_categories: Catégories
|
||||||
|
project_categories:
|
||||||
|
name: "Nom"
|
||||||
projects_setting:
|
projects_setting:
|
||||||
add: "Ajouter"
|
add: "Ajouter"
|
||||||
actions_controls: "Actions"
|
actions_controls: "Actions"
|
||||||
@ -1773,6 +1779,10 @@ fr:
|
|||||||
extended_prices_in_same_day: "Prix étendus le même jour"
|
extended_prices_in_same_day: "Prix étendus le même jour"
|
||||||
public_registrations: "Inscriptions publiques"
|
public_registrations: "Inscriptions publiques"
|
||||||
show_username_in_admin_list: "Afficher le nom d'utilisateur dans la liste"
|
show_username_in_admin_list: "Afficher le nom d'utilisateur dans la liste"
|
||||||
|
projects_list_member_filter_presence: "Présence du filtre par membre dans la vue liste des projets"
|
||||||
|
projects_list_date_filters_presence: "Présence des filtres par date dans la vue liste des projets"
|
||||||
|
project_categories_filter_placeholder: "Texte du filtre par catégories de la galerie de projets"
|
||||||
|
project_categories_wording: "Mot utilisé en remplacement du mot \"Catégories\" sur les pages publiques"
|
||||||
overlapping_options:
|
overlapping_options:
|
||||||
training_reservations: "Formations"
|
training_reservations: "Formations"
|
||||||
machine_reservations: "Machines"
|
machine_reservations: "Machines"
|
||||||
|
@ -167,6 +167,7 @@ en:
|
|||||||
full_price: "Full price: "
|
full_price: "Full price: "
|
||||||
#projects gallery
|
#projects gallery
|
||||||
projects_list:
|
projects_list:
|
||||||
|
filter: Filter
|
||||||
the_fablab_projects: "The projects"
|
the_fablab_projects: "The projects"
|
||||||
add_a_project: "Add a project"
|
add_a_project: "Add a project"
|
||||||
network_search: "Fab-manager network"
|
network_search: "Fab-manager network"
|
||||||
@ -183,6 +184,10 @@ en:
|
|||||||
all_materials: "All materials"
|
all_materials: "All materials"
|
||||||
load_next_projects: "Load next projects"
|
load_next_projects: "Load next projects"
|
||||||
rough_draft: "Rough draft"
|
rough_draft: "Rough draft"
|
||||||
|
filter_by_member: "Filter by member"
|
||||||
|
created_from: Created from
|
||||||
|
created_to: Created to
|
||||||
|
download_archive: Download
|
||||||
status_filter:
|
status_filter:
|
||||||
all_statuses: "All statuses"
|
all_statuses: "All statuses"
|
||||||
select_status: "Select a status"
|
select_status: "Select a status"
|
||||||
@ -216,6 +221,7 @@ en:
|
|||||||
report: "Report"
|
report: "Report"
|
||||||
do_you_really_want_to_delete_this_project: "Do you really want to delete this project?"
|
do_you_really_want_to_delete_this_project: "Do you really want to delete this project?"
|
||||||
status: "Status"
|
status: "Status"
|
||||||
|
markdown_file: "Markdown file"
|
||||||
#list of machines
|
#list of machines
|
||||||
machines_list:
|
machines_list:
|
||||||
the_fablab_s_machines: "The machines"
|
the_fablab_s_machines: "The machines"
|
||||||
|
@ -167,6 +167,7 @@ fr:
|
|||||||
full_price: "Plein tarif : "
|
full_price: "Plein tarif : "
|
||||||
#projects gallery
|
#projects gallery
|
||||||
projects_list:
|
projects_list:
|
||||||
|
filter: Filtrer
|
||||||
the_fablab_projects: "Les projets"
|
the_fablab_projects: "Les projets"
|
||||||
add_a_project: "Ajouter un projet"
|
add_a_project: "Ajouter un projet"
|
||||||
network_search: "Réseau Fab-Manager"
|
network_search: "Réseau Fab-Manager"
|
||||||
@ -183,6 +184,10 @@ fr:
|
|||||||
all_materials: "Tous les matériaux"
|
all_materials: "Tous les matériaux"
|
||||||
load_next_projects: "Charger les projets suivants"
|
load_next_projects: "Charger les projets suivants"
|
||||||
rough_draft: "Brouillon"
|
rough_draft: "Brouillon"
|
||||||
|
filter_by_member: "Filter par membre"
|
||||||
|
created_from: Créés à partir du
|
||||||
|
created_to: Créés jusqu'au
|
||||||
|
download_archive: Télécharger
|
||||||
status_filter:
|
status_filter:
|
||||||
all_statuses: "Tous les statuts"
|
all_statuses: "Tous les statuts"
|
||||||
select_status: "Sélectionnez un statut"
|
select_status: "Sélectionnez un statut"
|
||||||
@ -216,6 +221,7 @@ fr:
|
|||||||
report: "Signaler"
|
report: "Signaler"
|
||||||
do_you_really_want_to_delete_this_project: "Êtes-vous sur de vouloir supprimer ce projet ?"
|
do_you_really_want_to_delete_this_project: "Êtes-vous sur de vouloir supprimer ce projet ?"
|
||||||
status: "Statut"
|
status: "Statut"
|
||||||
|
markdown_file: "Fichier Markdown"
|
||||||
#list of machines
|
#list of machines
|
||||||
machines_list:
|
machines_list:
|
||||||
the_fablab_s_machines: "Les machines"
|
the_fablab_s_machines: "Les machines"
|
||||||
|
@ -131,6 +131,7 @@ en:
|
|||||||
illustration: "Visual"
|
illustration: "Visual"
|
||||||
add_an_illustration: "Add an illustration"
|
add_an_illustration: "Add an illustration"
|
||||||
CAD_file: "CAD file"
|
CAD_file: "CAD file"
|
||||||
|
CAD_files: "CAD files"
|
||||||
allowed_extensions: "Allowed extensions:"
|
allowed_extensions: "Allowed extensions:"
|
||||||
add_a_new_file: "Add a new file"
|
add_a_new_file: "Add a new file"
|
||||||
description: "Description"
|
description: "Description"
|
||||||
@ -138,6 +139,7 @@ en:
|
|||||||
steps: "Steps"
|
steps: "Steps"
|
||||||
step_N: "Step {INDEX}"
|
step_N: "Step {INDEX}"
|
||||||
step_title: "Step title"
|
step_title: "Step title"
|
||||||
|
step_image: "Image"
|
||||||
add_a_picture: "Add a picture"
|
add_a_picture: "Add a picture"
|
||||||
change_the_picture: "Change the picture"
|
change_the_picture: "Change the picture"
|
||||||
delete_the_step: "Delete the step"
|
delete_the_step: "Delete the step"
|
||||||
@ -149,7 +151,9 @@ en:
|
|||||||
employed_materials: "Employed materials"
|
employed_materials: "Employed materials"
|
||||||
employed_machines: "Employed machines"
|
employed_machines: "Employed machines"
|
||||||
collaborators: "Collaborators"
|
collaborators: "Collaborators"
|
||||||
|
author: Author
|
||||||
creative_commons_licences: "Creative Commons licences"
|
creative_commons_licences: "Creative Commons licences"
|
||||||
|
licence: "Licence"
|
||||||
themes: "Themes"
|
themes: "Themes"
|
||||||
tags: "Tags"
|
tags: "Tags"
|
||||||
save_as_draft: "Save as draft"
|
save_as_draft: "Save as draft"
|
||||||
|
@ -131,6 +131,7 @@ fr:
|
|||||||
illustration: "Illustration"
|
illustration: "Illustration"
|
||||||
add_an_illustration: "Ajouter un visuel"
|
add_an_illustration: "Ajouter un visuel"
|
||||||
CAD_file: "Fichier CAO"
|
CAD_file: "Fichier CAO"
|
||||||
|
CAD_files: "Fichiers CAO"
|
||||||
allowed_extensions: "Extensions autorisées :"
|
allowed_extensions: "Extensions autorisées :"
|
||||||
add_a_new_file: "Ajouter un nouveau fichier"
|
add_a_new_file: "Ajouter un nouveau fichier"
|
||||||
description: "Description"
|
description: "Description"
|
||||||
@ -138,6 +139,7 @@ fr:
|
|||||||
steps: "Étapes"
|
steps: "Étapes"
|
||||||
step_N: "Étape {INDEX}"
|
step_N: "Étape {INDEX}"
|
||||||
step_title: "Titre de l'étape"
|
step_title: "Titre de l'étape"
|
||||||
|
step_image: "Image"
|
||||||
add_a_picture: "Ajouter une image"
|
add_a_picture: "Ajouter une image"
|
||||||
change_the_picture: "Modifier l'image"
|
change_the_picture: "Modifier l'image"
|
||||||
delete_the_step: "Supprimer l'étape"
|
delete_the_step: "Supprimer l'étape"
|
||||||
@ -149,7 +151,9 @@ fr:
|
|||||||
employed_materials: "Matériaux utilisés"
|
employed_materials: "Matériaux utilisés"
|
||||||
employed_machines: "Machines utilisées"
|
employed_machines: "Machines utilisées"
|
||||||
collaborators: "Les collaborateurs"
|
collaborators: "Les collaborateurs"
|
||||||
|
author: Auteur
|
||||||
creative_commons_licences: "Licences Creative Commons"
|
creative_commons_licences: "Licences Creative Commons"
|
||||||
|
licence: "Licence"
|
||||||
themes: "Thématiques"
|
themes: "Thématiques"
|
||||||
tags: "Étiquettes"
|
tags: "Étiquettes"
|
||||||
save_as_draft: "Enregistrer comme brouillon"
|
save_as_draft: "Enregistrer comme brouillon"
|
||||||
|
@ -697,6 +697,10 @@ en:
|
|||||||
trainings_authorization_validity_duration: "Trainings validity period duration"
|
trainings_authorization_validity_duration: "Trainings validity period duration"
|
||||||
trainings_invalidation_rule: "Trainings automatic invalidation"
|
trainings_invalidation_rule: "Trainings automatic invalidation"
|
||||||
trainings_invalidation_rule_period: "Grace period before invalidating a training"
|
trainings_invalidation_rule_period: "Grace period before invalidating a training"
|
||||||
|
projects_list_member_filter_presence: "Presence of member filter on projects list"
|
||||||
|
projects_list_date_filters_presence: "Presence of dates filter on projects list"
|
||||||
|
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
|
||||||
|
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
|
||||||
#statuses of projects
|
#statuses of projects
|
||||||
statuses:
|
statuses:
|
||||||
new: "New"
|
new: "New"
|
||||||
|
@ -697,6 +697,10 @@ fr:
|
|||||||
trainings_authorization_validity_duration: "Durée de la période de validité des formations"
|
trainings_authorization_validity_duration: "Durée de la période de validité des formations"
|
||||||
trainings_invalidation_rule: "Invalidation automatique des formations"
|
trainings_invalidation_rule: "Invalidation automatique des formations"
|
||||||
trainings_invalidation_rule_period: "Période de grâce avant d'invalider une formation"
|
trainings_invalidation_rule_period: "Période de grâce avant d'invalider une formation"
|
||||||
|
projects_list_member_filter_presence: "Présence du filtre par membre dans la vue liste des projets"
|
||||||
|
projects_list_date_filters_presence: "Présence des filtres par date dans la vue liste des projets"
|
||||||
|
project_categories_filter_placeholder: "Texte du filtre par catégories de la galerie de projets"
|
||||||
|
project_categories_wording: "Mot utilisé en remplacement du mot \"Catégories\" sur les pages publiques"
|
||||||
#statuses of projects
|
#statuses of projects
|
||||||
statuses:
|
statuses:
|
||||||
new: "Nouveau"
|
new: "Nouveau"
|
||||||
|
@ -38,6 +38,7 @@ Rails.application.routes.draw do
|
|||||||
get :last_published
|
get :last_published
|
||||||
get :search
|
get :search
|
||||||
end
|
end
|
||||||
|
get :markdown, on: :member
|
||||||
end
|
end
|
||||||
resources :openlab_projects, only: :index
|
resources :openlab_projects, only: :index
|
||||||
resources :machines
|
resources :machines
|
||||||
@ -46,6 +47,7 @@ Rails.application.routes.draw do
|
|||||||
resources :themes
|
resources :themes
|
||||||
resources :licences
|
resources :licences
|
||||||
resources :statuses
|
resources :statuses
|
||||||
|
resources :project_categories
|
||||||
resources :admins, only: %i[index create destroy]
|
resources :admins, only: %i[index create destroy]
|
||||||
resources :settings, only: %i[show update index], param: :name do
|
resources :settings, only: %i[show update index], param: :name do
|
||||||
patch '/bulk_update', action: 'bulk_update', on: :collection
|
patch '/bulk_update', action: 'bulk_update', on: :collection
|
||||||
|
9
db/migrate/20230626122844_create_project_categories.rb
Normal file
9
db/migrate/20230626122844_create_project_categories.rb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
class CreateProjectCategories < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
create_table :project_categories do |t|
|
||||||
|
t.string :name
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,12 @@
|
|||||||
|
class CreateProjectsProjectCategories < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
create_table :projects_project_categories do |t|
|
||||||
|
t.belongs_to :project, foreign_key: true, null: false
|
||||||
|
t.belongs_to :project_category, foreign_key: true, null: false
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :projects_project_categories, [:project_id, :project_category_id], unique: true, name: :idx_projects_project_categories
|
||||||
|
end
|
||||||
|
end
|
@ -728,3 +728,8 @@ Setting.set('accounting_Error_code', 'ERROR') unless Setting.find_by(name: 'acco
|
|||||||
Setting.set('accounting_Error_label', 'Erroneous invoices to refund') unless Setting.find_by(name: 'accounting_Error_label').try(:value)
|
Setting.set('accounting_Error_label', 'Erroneous invoices to refund') unless Setting.find_by(name: 'accounting_Error_label').try(:value)
|
||||||
|
|
||||||
Setting.set('external_id', false) unless Setting.find_by(name: 'external_id').try(:value)
|
Setting.set('external_id', false) unless Setting.find_by(name: 'external_id').try(:value)
|
||||||
|
|
||||||
|
Setting.set('projects_list_member_filter_presence', false) unless Setting.find_by(name: 'projects_list_member_filter_presence')
|
||||||
|
Setting.set('projects_list_date_filters_presence', false) unless Setting.find_by(name: 'projects_list_date_filters_presence')
|
||||||
|
Setting.set('project_categories_filter_placeholder', 'Toutes les catégories') unless Setting.find_by(name: 'project_categories_filter_placeholder').try(:value)
|
||||||
|
Setting.set('project_categories_wording', 'Catégories') unless Setting.find_by(name: 'project_categories_wording').try(:value)
|
||||||
|
141
db/structure.sql
141
db/structure.sql
@ -9,13 +9,6 @@ SET xmloption = content;
|
|||||||
SET client_min_messages = warning;
|
SET client_min_messages = warning;
|
||||||
SET row_security = off;
|
SET row_security = off;
|
||||||
|
|
||||||
--
|
|
||||||
-- Name: public; Type: SCHEMA; Schema: -; Owner: -
|
|
||||||
--
|
|
||||||
|
|
||||||
-- *not* creating schema, since initdb creates it
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: fuzzystrmatch; Type: EXTENSION; Schema: -; Owner: -
|
-- Name: fuzzystrmatch; Type: EXTENSION; Schema: -; Owner: -
|
||||||
--
|
--
|
||||||
@ -2725,6 +2718,37 @@ CREATE SEQUENCE public.profiles_id_seq
|
|||||||
ALTER SEQUENCE public.profiles_id_seq OWNED BY public.profiles.id;
|
ALTER SEQUENCE public.profiles_id_seq OWNED BY public.profiles.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: project_categories; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.project_categories (
|
||||||
|
id bigint NOT NULL,
|
||||||
|
name character varying,
|
||||||
|
created_at timestamp(6) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(6) without time zone NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: project_categories_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.project_categories_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: project_categories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER SEQUENCE public.project_categories_id_seq OWNED BY public.project_categories.id;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: project_steps; Type: TABLE; Schema: public; Owner: -
|
-- Name: project_steps; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -2893,6 +2917,38 @@ CREATE SEQUENCE public.projects_machines_id_seq
|
|||||||
ALTER SEQUENCE public.projects_machines_id_seq OWNED BY public.projects_machines.id;
|
ALTER SEQUENCE public.projects_machines_id_seq OWNED BY public.projects_machines.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: projects_project_categories; Type: TABLE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE public.projects_project_categories (
|
||||||
|
id bigint NOT NULL,
|
||||||
|
project_id bigint NOT NULL,
|
||||||
|
project_category_id bigint NOT NULL,
|
||||||
|
created_at timestamp(6) without time zone NOT NULL,
|
||||||
|
updated_at timestamp(6) without time zone NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: projects_project_categories_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.projects_project_categories_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: projects_project_categories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER SEQUENCE public.projects_project_categories_id_seq OWNED BY public.projects_project_categories.id;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: projects_spaces; Type: TABLE; Schema: public; Owner: -
|
-- Name: projects_spaces; Type: TABLE; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -4746,6 +4802,13 @@ ALTER TABLE ONLY public.profile_custom_fields ALTER COLUMN id SET DEFAULT nextva
|
|||||||
ALTER TABLE ONLY public.profiles ALTER COLUMN id SET DEFAULT nextval('public.profiles_id_seq'::regclass);
|
ALTER TABLE ONLY public.profiles ALTER COLUMN id SET DEFAULT nextval('public.profiles_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: project_categories id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.project_categories ALTER COLUMN id SET DEFAULT nextval('public.project_categories_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: project_steps id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: project_steps id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -4781,6 +4844,13 @@ ALTER TABLE ONLY public.projects_components ALTER COLUMN id SET DEFAULT nextval(
|
|||||||
ALTER TABLE ONLY public.projects_machines ALTER COLUMN id SET DEFAULT nextval('public.projects_machines_id_seq'::regclass);
|
ALTER TABLE ONLY public.projects_machines ALTER COLUMN id SET DEFAULT nextval('public.projects_machines_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: projects_project_categories id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.projects_project_categories ALTER COLUMN id SET DEFAULT nextval('public.projects_project_categories_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: projects_spaces id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: projects_spaces id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -5646,6 +5716,14 @@ ALTER TABLE ONLY public.profiles
|
|||||||
ADD CONSTRAINT profiles_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT profiles_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: project_categories project_categories_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.project_categories
|
||||||
|
ADD CONSTRAINT project_categories_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: project_steps project_steps_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: project_steps project_steps_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -5686,6 +5764,14 @@ ALTER TABLE ONLY public.projects
|
|||||||
ADD CONSTRAINT projects_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT projects_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: projects_project_categories projects_project_categories_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.projects_project_categories
|
||||||
|
ADD CONSTRAINT projects_project_categories_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: projects_spaces projects_spaces_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
-- Name: projects_spaces projects_spaces_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -5998,6 +6084,13 @@ ALTER TABLE ONLY public.wallets
|
|||||||
ADD CONSTRAINT wallets_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT wallets_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: idx_projects_project_categories; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_projects_project_categories ON public.projects_project_categories USING btree (project_id, project_category_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_abuses_on_signaled_type_and_signaled_id; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_abuses_on_signaled_type_and_signaled_id; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -6894,6 +6987,20 @@ CREATE UNIQUE INDEX index_projects_on_slug ON public.projects USING btree (slug)
|
|||||||
CREATE INDEX index_projects_on_status_id ON public.projects USING btree (status_id);
|
CREATE INDEX index_projects_on_status_id ON public.projects USING btree (status_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_projects_project_categories_on_project_category_id; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_projects_project_categories_on_project_category_id ON public.projects_project_categories USING btree (project_category_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_projects_project_categories_on_project_id; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_projects_project_categories_on_project_id ON public.projects_project_categories USING btree (project_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_projects_spaces_on_project_id; Type: INDEX; Schema: public; Owner: -
|
-- Name: index_projects_spaces_on_project_id; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -8057,6 +8164,14 @@ ALTER TABLE ONLY public.projects
|
|||||||
ADD CONSTRAINT fk_rails_b4a83cd9b3 FOREIGN KEY (status_id) REFERENCES public.statuses(id);
|
ADD CONSTRAINT fk_rails_b4a83cd9b3 FOREIGN KEY (status_id) REFERENCES public.statuses(id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: projects_project_categories fk_rails_ba4a985e85; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.projects_project_categories
|
||||||
|
ADD CONSTRAINT fk_rails_ba4a985e85 FOREIGN KEY (project_id) REFERENCES public.projects(id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: statistic_profiles fk_rails_bba64e5eb9; Type: FK CONSTRAINT; Schema: public; Owner: -
|
-- Name: statistic_profiles fk_rails_bba64e5eb9; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -8209,6 +8324,14 @@ ALTER TABLE ONLY public.event_price_categories
|
|||||||
ADD CONSTRAINT fk_rails_dcd2787d07 FOREIGN KEY (event_id) REFERENCES public.events(id);
|
ADD CONSTRAINT fk_rails_dcd2787d07 FOREIGN KEY (event_id) REFERENCES public.events(id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: projects_project_categories fk_rails_de9f22810e; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.projects_project_categories
|
||||||
|
ADD CONSTRAINT fk_rails_de9f22810e FOREIGN KEY (project_category_id) REFERENCES public.project_categories(id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: cart_item_coupons fk_rails_e1cb402fac; Type: FK CONSTRAINT; Schema: public; Owner: -
|
-- Name: cart_item_coupons fk_rails_e1cb402fac; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@ -8693,6 +8816,8 @@ INSERT INTO "schema_migrations" (version) VALUES
|
|||||||
('20230324095639'),
|
('20230324095639'),
|
||||||
('20230328094807'),
|
('20230328094807'),
|
||||||
('20230328094808'),
|
('20230328094808'),
|
||||||
('20230328094809');
|
('20230328094809'),
|
||||||
|
('20230626122844'),
|
||||||
|
('20230626122947');
|
||||||
|
|
||||||
|
|
||||||
|
29
test/fixtures/history_values.yml
vendored
29
test/fixtures/history_values.yml
vendored
@ -852,3 +852,32 @@ history_value_100:
|
|||||||
updated_at: 2023-04-05 09:16:08.000511500 Z
|
updated_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
invoicing_profile_id: 1
|
invoicing_profile_id: 1
|
||||||
|
|
||||||
|
history_value_101:
|
||||||
|
setting_id: 100
|
||||||
|
value: 'Toutes les catégories'
|
||||||
|
created_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
updated_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
invoicing_profile_id: 1
|
||||||
|
|
||||||
|
history_value_102:
|
||||||
|
setting_id: 101
|
||||||
|
value: 'Catégories'
|
||||||
|
created_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
updated_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
invoicing_profile_id: 1
|
||||||
|
|
||||||
|
history_value_103:
|
||||||
|
id: 103
|
||||||
|
setting_id: 102
|
||||||
|
value: 'false'
|
||||||
|
created_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
updated_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
invoicing_profile_id: 1
|
||||||
|
|
||||||
|
history_value_104:
|
||||||
|
id: 104
|
||||||
|
setting_id: 103
|
||||||
|
value: 'false'
|
||||||
|
created_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
updated_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
invoicing_profile_id: 1
|
||||||
|
12
test/fixtures/project_categories.yml
vendored
Normal file
12
test/fixtures/project_categories.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
project_category_1:
|
||||||
|
id: 1
|
||||||
|
name: Module 1
|
||||||
|
created_at: 2023-06-26 15:39:08.259759000 Z
|
||||||
|
updated_at: 2016-06-26 15:39:08.259759000 Z
|
||||||
|
|
||||||
|
project_category_2:
|
||||||
|
id: 2
|
||||||
|
name: Module 2
|
||||||
|
created_at: 2016-06-26 15:39:08.265840000 Z
|
||||||
|
updated_at: 2016-06-26 15:39:08.265840000 Z
|
2
test/fixtures/project_steps.yml
vendored
2
test/fixtures/project_steps.yml
vendored
@ -7,6 +7,7 @@ project_step_1:
|
|||||||
created_at: 2016-04-04 15:39:08.259759000 Z
|
created_at: 2016-04-04 15:39:08.259759000 Z
|
||||||
updated_at: 2016-04-04 15:39:08.259759000 Z
|
updated_at: 2016-04-04 15:39:08.259759000 Z
|
||||||
title: Le manche
|
title: Le manche
|
||||||
|
step_nb: 1
|
||||||
|
|
||||||
project_step_2:
|
project_step_2:
|
||||||
id: 2
|
id: 2
|
||||||
@ -16,3 +17,4 @@ project_step_2:
|
|||||||
created_at: 2016-04-04 15:39:08.265840000 Z
|
created_at: 2016-04-04 15:39:08.265840000 Z
|
||||||
updated_at: 2016-04-04 15:39:08.265840000 Z
|
updated_at: 2016-04-04 15:39:08.265840000 Z
|
||||||
title: La presse
|
title: La presse
|
||||||
|
step_nb: 2
|
1
test/fixtures/projects.yml
vendored
1
test/fixtures/projects.yml
vendored
@ -12,3 +12,4 @@ project_1:
|
|||||||
state: published
|
state: published
|
||||||
slug: presse-puree
|
slug: presse-puree
|
||||||
published_at: 2016-04-04 15:39:08.267614000 Z
|
published_at: 2016-04-04 15:39:08.267614000 Z
|
||||||
|
status_id: 1
|
7
test/fixtures/projects_project_categories.yml
vendored
Normal file
7
test/fixtures/projects_project_categories.yml
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
projects_project_category_1:
|
||||||
|
id: 1
|
||||||
|
project_id: 1
|
||||||
|
project_category_id: 1
|
||||||
|
created_at: 2023-06-26 15:39:08.259759000 Z
|
||||||
|
updated_at: 2016-06-26 15:39:08.259759000 Z
|
24
test/fixtures/settings.yml
vendored
24
test/fixtures/settings.yml
vendored
@ -586,3 +586,27 @@ setting_99:
|
|||||||
name: home_css
|
name: home_css
|
||||||
created_at: 2023-04-05 09:16:08.000511500 Z
|
created_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
updated_at: 2023-04-05 09:16:08.000511500 Z
|
updated_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
|
||||||
|
setting_100:
|
||||||
|
id: 100
|
||||||
|
name: project_categories_filter_placeholder
|
||||||
|
created_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
updated_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
|
||||||
|
setting_101:
|
||||||
|
id: 101
|
||||||
|
name: project_categories_wording
|
||||||
|
created_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
updated_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
|
||||||
|
setting_102:
|
||||||
|
id: 102
|
||||||
|
name: projects_list_member_filter_presence
|
||||||
|
created_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
updated_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
|
||||||
|
setting_103:
|
||||||
|
id: 103
|
||||||
|
name: projects_list_date_filters_presence
|
||||||
|
created_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
updated_at: 2023-04-05 09:16:08.000511500 Z
|
||||||
|
@ -825,6 +825,30 @@ export const settings: Array<Setting> = [
|
|||||||
value: 'https://www.sleede.com/',
|
value: 'https://www.sleede.com/',
|
||||||
last_update: '2022-12-23T14:39:12+0100',
|
last_update: '2022-12-23T14:39:12+0100',
|
||||||
localized: 'Url'
|
localized: 'Url'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'projects_list_member_filter_presence',
|
||||||
|
value: 'false',
|
||||||
|
last_update: '2022-12-23T14:39:12+0100',
|
||||||
|
localized: 'Projects list member filter presence'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'projects_list_date_filters_presence',
|
||||||
|
value: 'false',
|
||||||
|
last_update: '2022-12-23T14:39:12+0100',
|
||||||
|
localized: 'Projects list date filters presence'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'project_categories_filter_placeholder',
|
||||||
|
value: 'Toutes les catégories',
|
||||||
|
last_update: '2022-12-23T14:39:12+0100',
|
||||||
|
localized: 'Placeholder for categories filter in project gallery'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'project_categories_wording',
|
||||||
|
value: 'Catégories',
|
||||||
|
last_update: '2022-12-23T14:39:12+0100',
|
||||||
|
localized: 'Project categories overridden name'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
68
test/integration/project_categories_test.rb
Normal file
68
test/integration/project_categories_test.rb
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ProjectCategoriesTest < ActionDispatch::IntegrationTest
|
||||||
|
def setup
|
||||||
|
@admin = User.find_by(username: 'admin')
|
||||||
|
login_as(@admin, scope: :user)
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'create a project_category' do
|
||||||
|
post '/api/project_categories',
|
||||||
|
params: {
|
||||||
|
name: 'Module de fou'
|
||||||
|
}.to_json,
|
||||||
|
headers: default_headers
|
||||||
|
|
||||||
|
# Check response format & project_category
|
||||||
|
assert_equal 201, response.status, response.body
|
||||||
|
assert_match Mime[:json].to_s, response.content_type
|
||||||
|
|
||||||
|
# Check the correct project_category was created
|
||||||
|
res = json_response(response.body)
|
||||||
|
project_category = ProjectCategory.where(id: res[:id]).first
|
||||||
|
assert_not_nil project_category, 'project_category was not created in database'
|
||||||
|
|
||||||
|
assert_equal 'Module de fou', res[:name]
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'update a project_category' do
|
||||||
|
patch '/api/project_categories/1',
|
||||||
|
params: {
|
||||||
|
name: 'Nouveau nom'
|
||||||
|
}.to_json,
|
||||||
|
headers: default_headers
|
||||||
|
|
||||||
|
# Check response format & project_category
|
||||||
|
assert_equal 200, response.status, response.body
|
||||||
|
assert_match Mime[:json].to_s, response.content_type
|
||||||
|
|
||||||
|
# Check the project_category was updated
|
||||||
|
res = json_response(response.body)
|
||||||
|
assert_equal 1, res[:id]
|
||||||
|
assert_equal 'Nouveau nom', res[:name]
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'list all project_categories' do
|
||||||
|
get '/api/project_categories'
|
||||||
|
|
||||||
|
# Check response format & project_category
|
||||||
|
assert_equal 200, response.status, response.body
|
||||||
|
assert_match Mime[:json].to_s, response.content_type
|
||||||
|
|
||||||
|
# Check the list items are ok
|
||||||
|
project_categories = json_response(response.body)
|
||||||
|
assert_equal ProjectCategory.count, project_categories.count
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'delete a project_category' do
|
||||||
|
project_category = ProjectCategory.create!(name: 'Gone too soon')
|
||||||
|
delete "/api/project_categories/#{project_category.id}"
|
||||||
|
assert_response :success
|
||||||
|
assert_empty response.body
|
||||||
|
assert_raise ActiveRecord::RecordNotFound do
|
||||||
|
project_category.reload
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
17
test/integration/projects_test.rb
Normal file
17
test/integration/projects_test.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ProjectsTest < ActionDispatch::IntegrationTest
|
||||||
|
def setup
|
||||||
|
@admin = User.find_by(username: 'admin')
|
||||||
|
login_as(@admin, scope: :user)
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'download markdown file' do
|
||||||
|
get "/api/projects/1/markdown"
|
||||||
|
|
||||||
|
assert_response :success
|
||||||
|
assert_equal "text/markdown", response.content_type
|
||||||
|
end
|
||||||
|
end
|
11
test/models/project_category_test.rb
Normal file
11
test/models/project_category_test.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ProjectCategoryTest < ActiveSupport::TestCase
|
||||||
|
test 'fixtures are valid' do
|
||||||
|
ProjectCategory.find_each do |project_category|
|
||||||
|
assert project_category.valid?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
test/models/project_test.rb
Normal file
15
test/models/project_test.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ProjectTest < ActiveSupport::TestCase
|
||||||
|
test 'fixtures are valid' do
|
||||||
|
Project.find_each do |project|
|
||||||
|
assert project.valid?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'relation project_categories' do
|
||||||
|
assert_equal [project_categories(:project_category_1)], projects(:project_1).project_categories
|
||||||
|
end
|
||||||
|
end
|
21
test/services/project_to_markdown_test.rb
Normal file
21
test/services/project_to_markdown_test.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class ProjectToMarkdownTest < ActiveSupport::TestCase
|
||||||
|
test "ProjectToMarkdown is working" do
|
||||||
|
project = projects(:project_1)
|
||||||
|
service = ProjectToMarkdown.new(project)
|
||||||
|
|
||||||
|
markdown_str = nil
|
||||||
|
|
||||||
|
assert_nothing_raised do
|
||||||
|
markdown_str = service.call
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_includes markdown_str, project.name
|
||||||
|
project.project_steps.each do |project_step|
|
||||||
|
assert_includes markdown_str, project_step.title
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user