mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge remote-tracking branch 'origin/issue466_file_upload' into workflow/fixes-2022-02
This commit is contained in:
commit
c1f5730c4d
@ -11,6 +11,10 @@ and this project adheres to
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
<!-- write down unreleased development here -->
|
<!-- write down unreleased development here -->
|
||||||
|
* [docstore] Add an API entrypoint for StoredObject (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
|
||||||
|
* [person] Add the possibility of uploading existing documents to AccPeriodWorkEvaluationDocument (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
|
||||||
|
* [person] Add title to AccPeriodWorkEvaluationDocument (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
|
||||||
|
|
||||||
|
|
||||||
## Test releases
|
## Test releases
|
||||||
|
|
||||||
@ -33,7 +37,7 @@ and this project adheres to
|
|||||||
* [Household]: Add end date in HouseholdMember form for 'enfant hors menage' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/434)
|
* [Household]: Add end date in HouseholdMember form for 'enfant hors menage' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/434)
|
||||||
* [homepage_widget]: If no sender then display as 'notification automatique' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/435)
|
* [homepage_widget]: If no sender then display as 'notification automatique' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/435)
|
||||||
* [parcours]: Order social activities and only display most recent three in parcours resumé (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/481)
|
* [parcours]: Order social activities and only display most recent three in parcours resumé (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/481)
|
||||||
* [3party]: 3party: redirect to parent when contact (child) is opened in view page
|
* [3party]: 3party: redirect to parent when contact (child) is opened in view page
|
||||||
* [parcours / addresses]: launch an event when a person change address (either through changing household or because the household is associated to a new address). If the person is localising a course, the course location go back to a temporarily address.
|
* [parcours / addresses]: launch an event when a person change address (either through changing household or because the household is associated to a new address). If the person is localising a course, the course location go back to a temporarily address.
|
||||||
* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType
|
* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<select class="form-select" v-model="template">
|
<select class="form-select" v-model="template">
|
||||||
<option disabled selected value="">{{ $t('choose_a_template') }}</option>
|
<option disabled selected value="">{{ $t('choose_a_template') }}</option>
|
||||||
<template v-for="t in templates">
|
<template v-for="t in templates" :key="t.id">
|
||||||
<option v-bind:value="t.id">{{ t.name.fr || 'Aucun nom défini' }}</option>
|
<option :value="t.id" >{{ t.name.fr || 'Aucun nom défini' }}</option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
<a v-if="canGenerate" class="btn btn-update btn-sm change-icon" :href="buildUrlGenerate" @click.prevent="clickGenerate($event, buildUrlGenerate)"><i class="fa fa-fw fa-cog"></i></a>
|
<a v-if="canGenerate" class="btn btn-update btn-sm change-icon" :href="buildUrlGenerate" @click.prevent="clickGenerate($event, buildUrlGenerate)"><i class="fa fa-fw fa-cog"></i></a>
|
||||||
|
@ -17,6 +17,7 @@ use Symfony\Component\Config\FileLocator;
|
|||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
|
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
|
||||||
use Symfony\Component\DependencyInjection\Loader;
|
use Symfony\Component\DependencyInjection\Loader;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,6 +47,28 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
|
|||||||
$this->prependRoute($container);
|
$this->prependRoute($container);
|
||||||
$this->prependAuthorization($container);
|
$this->prependAuthorization($container);
|
||||||
$this->prependTwig($container);
|
$this->prependTwig($container);
|
||||||
|
$this->prependApis($container);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prependApis(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
$container->prependExtensionConfig('chill_main', [
|
||||||
|
'apis' => [
|
||||||
|
[
|
||||||
|
'class' => \Chill\DocStoreBundle\Entity\StoredObject::class,
|
||||||
|
'name' => 'stored_object',
|
||||||
|
'base_path' => '/api/1.0/docstore/stored-object',
|
||||||
|
'base_role' => 'ROLE_USER',
|
||||||
|
'actions' => [
|
||||||
|
'_entity' => [
|
||||||
|
'methods' => [
|
||||||
|
Request::METHOD_POST => true,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function prependAuthorization(ContainerBuilder $container)
|
protected function prependAuthorization(ContainerBuilder $container)
|
||||||
|
@ -34,19 +34,19 @@ class StoredObject implements AsyncFileInterface, Document
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="datetime", name="creation_date")
|
* @ORM\Column(type="datetime", name="creation_date")
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read", "write"})
|
||||||
*/
|
*/
|
||||||
private DateTimeInterface $creationDate;
|
private DateTimeInterface $creationDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="json", name="datas")
|
* @ORM\Column(type="json", name="datas")
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read", "write"})
|
||||||
*/
|
*/
|
||||||
private array $datas = [];
|
private array $datas = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="text")
|
* @ORM\Column(type="text")
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read", "write"})
|
||||||
*/
|
*/
|
||||||
private $filename;
|
private $filename;
|
||||||
|
|
||||||
@ -54,30 +54,32 @@ class StoredObject implements AsyncFileInterface, Document
|
|||||||
* @ORM\Id
|
* @ORM\Id
|
||||||
* @ORM\GeneratedValue
|
* @ORM\GeneratedValue
|
||||||
* @ORM\Column(type="integer")
|
* @ORM\Column(type="integer")
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read", "write"})
|
||||||
*/
|
*/
|
||||||
private $id;
|
private $id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int[]
|
* @var int[]
|
||||||
* @ORM\Column(type="json", name="iv")
|
* @ORM\Column(type="json", name="iv")
|
||||||
|
* @Serializer\Groups({"write"})
|
||||||
*/
|
*/
|
||||||
private array $iv = [];
|
private array $iv = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="json", name="key")
|
* @ORM\Column(type="json", name="key")
|
||||||
|
* @Serializer\Groups({"write"})
|
||||||
*/
|
*/
|
||||||
private array $keyInfos = [];
|
private array $keyInfos = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="text", name="type")
|
* @ORM\Column(type="text", name="type")
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read", "write"})
|
||||||
*/
|
*/
|
||||||
private string $type = '';
|
private string $type = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="uuid", unique=true)
|
* @ORM\Column(type="uuid", unique=true)
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read", "write"})
|
||||||
*/
|
*/
|
||||||
private UuidInterface $uuid;
|
private UuidInterface $uuid;
|
||||||
|
|
||||||
|
@ -4,12 +4,12 @@ var initializeDownload = require('./downloader.js');
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* define a dropzone for chill usage
|
* define a dropzone for chill usage
|
||||||
*
|
*
|
||||||
* An event is launched when dropzone is initialize, allowing to customize events
|
* An event is launched when dropzone is initialize, allowing to customize events
|
||||||
* on dropzone :
|
* on dropzone :
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* window.addEventListener("chill_dropzone_initialized", (e) => {
|
* window.addEventListener("chill_dropzone_initialized", (e) => {
|
||||||
* // do something with dropzone:
|
* // do something with dropzone:
|
||||||
@ -18,7 +18,7 @@ var initializeDownload = require('./downloader.js');
|
|||||||
* });
|
* });
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// load css
|
// load css
|
||||||
@ -37,7 +37,6 @@ var keyDefinition = {
|
|||||||
|
|
||||||
var searchForZones = function(root) {
|
var searchForZones = function(root) {
|
||||||
var zones = root.querySelectorAll('div[data-stored-object]');
|
var zones = root.querySelectorAll('div[data-stored-object]');
|
||||||
|
|
||||||
for(let i=0; i < zones.length; i++) {
|
for(let i=0; i < zones.length; i++) {
|
||||||
initialize(zones[i]);
|
initialize(zones[i]);
|
||||||
}
|
}
|
||||||
@ -48,32 +47,32 @@ var getUploadUrl = function(zoneData, files) {
|
|||||||
generateTempUrlPost = zoneData.zone.querySelector('input[data-async-file-upload]').dataset.generateTempUrlPost,
|
generateTempUrlPost = zoneData.zone.querySelector('input[data-async-file-upload]').dataset.generateTempUrlPost,
|
||||||
oReq = new XMLHttpRequest()
|
oReq = new XMLHttpRequest()
|
||||||
;
|
;
|
||||||
|
|
||||||
// arg, dropzone, you cannot handle async upload...
|
// arg, dropzone, you cannot handle async upload...
|
||||||
oReq.open("GET", generateTempUrlPost, false);
|
oReq.open("GET", generateTempUrlPost, false);
|
||||||
oReq.send();
|
oReq.send();
|
||||||
|
|
||||||
if (oReq.readyState !== XMLHttpRequest.DONE) {
|
if (oReq.readyState !== XMLHttpRequest.DONE) {
|
||||||
throw new Error("Error while fetching url to upload");
|
throw new Error("Error while fetching url to upload");
|
||||||
}
|
}
|
||||||
|
|
||||||
zoneData.params = JSON.parse(oReq.responseText);
|
zoneData.params = JSON.parse(oReq.responseText);
|
||||||
|
|
||||||
return zoneData.params.url;
|
return zoneData.params.url;
|
||||||
};
|
};
|
||||||
|
|
||||||
var encryptFile = function(originalFile, zoneData, done) {
|
var encryptFile = function(originalFile, zoneData, done) {
|
||||||
var
|
var
|
||||||
iv = crypto.getRandomValues(new Uint8Array(16)),
|
iv = crypto.getRandomValues(new Uint8Array(16)),
|
||||||
reader = new FileReader(),
|
reader = new FileReader(),
|
||||||
jsKey, rawKey
|
jsKey, rawKey
|
||||||
;
|
;
|
||||||
|
|
||||||
zoneData.originalType = originalFile.type;
|
zoneData.originalType = originalFile.type;
|
||||||
|
|
||||||
reader.onload = e => {
|
reader.onload = e => {
|
||||||
window.crypto.subtle.generateKey(keyDefinition, true, [ "encrypt", "decrypt" ])
|
window.crypto.subtle.generateKey(keyDefinition, true, [ "encrypt", "decrypt" ])
|
||||||
.then(key => {
|
.then(key => {
|
||||||
jsKey = key;
|
jsKey = key;
|
||||||
|
|
||||||
// we register the key somwhere
|
// we register the key somwhere
|
||||||
@ -90,34 +89,34 @@ var encryptFile = function(originalFile, zoneData, done) {
|
|||||||
rawKey: rawKey,
|
rawKey: rawKey,
|
||||||
iv: iv
|
iv: iv
|
||||||
};
|
};
|
||||||
|
|
||||||
done(new File( [ encrypted ], zoneData.suffix));
|
done(new File( [ encrypted ], zoneData.suffix));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
reader.readAsArrayBuffer(originalFile);
|
reader.readAsArrayBuffer(originalFile);
|
||||||
};
|
};
|
||||||
|
|
||||||
var addBelowButton = (btn, zone, zoneData) => {
|
var addBelowButton = (btn, zone, zoneData) => {
|
||||||
let
|
let
|
||||||
belowZone = zone.querySelector('.chill-dropzone__below-zone');
|
belowZone = zone.querySelector('.chill-dropzone__below-zone');
|
||||||
|
|
||||||
if (belowZone === null) {
|
if (belowZone === null) {
|
||||||
belowZone = document.createElement('div');
|
belowZone = document.createElement('div');
|
||||||
belowZone.classList.add('chill-dropzone__below-zone');
|
belowZone.classList.add('chill-dropzone__below-zone');
|
||||||
zone.appendChild(belowZone);
|
zone.appendChild(belowZone);
|
||||||
}
|
}
|
||||||
|
|
||||||
belowZone.appendChild(btn);
|
belowZone.appendChild(btn);
|
||||||
};
|
};
|
||||||
|
|
||||||
var createZone = (zone, zoneData) => {
|
var createZone = (zone, zoneData) => {
|
||||||
var
|
var
|
||||||
created = document.createElement('div'),
|
created = document.createElement('div'),
|
||||||
initMessage = document.createElement('div'),
|
initMessage = document.createElement('div'),
|
||||||
initContent = zone.dataset.labelInitMessage,
|
initContent = zone.dataset.labelInitMessage,
|
||||||
dropzoneI;
|
dropzoneI;
|
||||||
|
|
||||||
created.classList.add('dropzone');
|
created.classList.add('dropzone');
|
||||||
initMessage.classList.add('dz-message');
|
initMessage.classList.add('dz-message');
|
||||||
initMessage.appendChild(document.createTextNode(initContent));
|
initMessage.appendChild(document.createTextNode(initContent));
|
||||||
@ -142,7 +141,7 @@ var createZone = (zone, zoneData) => {
|
|||||||
return zoneData.suffix;
|
return zoneData.suffix;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dropzoneI.on("sending", function(file, xhr, formData) {
|
dropzoneI.on("sending", function(file, xhr, formData) {
|
||||||
formData.append("redirect", zoneData.params.redirect);
|
formData.append("redirect", zoneData.params.redirect);
|
||||||
formData.append("max_file_size", zoneData.params.max_file_size);
|
formData.append("max_file_size", zoneData.params.max_file_size);
|
||||||
@ -150,24 +149,24 @@ var createZone = (zone, zoneData) => {
|
|||||||
formData.append("expires", zoneData.params.expires);
|
formData.append("expires", zoneData.params.expires);
|
||||||
formData.append("signature", zoneData.params.signature);
|
formData.append("signature", zoneData.params.signature);
|
||||||
});
|
});
|
||||||
|
|
||||||
dropzoneI.on("success", function(file, response) {
|
dropzoneI.on("success", function(file, response) {
|
||||||
zoneData.currentFile = file;
|
zoneData.currentFile = file;
|
||||||
storeDataInForm(zone, zoneData);
|
storeDataInForm(zone, zoneData);
|
||||||
});
|
});
|
||||||
|
|
||||||
dropzoneI.on("addedfile", function(file) {
|
dropzoneI.on("addedfile", function(file) {
|
||||||
if (zoneData.hasOwnProperty('currentFile')) {
|
if (zoneData.hasOwnProperty('currentFile')) {
|
||||||
dropzoneI.removeFile(zoneData.currentFile);
|
dropzoneI.removeFile(zoneData.currentFile);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
dropzoneI.on("removedfile", function(file) {
|
dropzoneI.on("removedfile", function(file) {
|
||||||
removeDataInForm(zone, zoneData);
|
removeDataInForm(zone, zoneData);
|
||||||
});
|
});
|
||||||
|
|
||||||
zone.insertBefore(created, zone.firstChild);
|
zone.insertBefore(created, zone.firstChild);
|
||||||
|
|
||||||
let event = new CustomEvent("chill_dropzone_initialized", {
|
let event = new CustomEvent("chill_dropzone_initialized", {
|
||||||
detail: {
|
detail: {
|
||||||
dropzone: dropzoneI,
|
dropzone: dropzoneI,
|
||||||
@ -179,7 +178,7 @@ var createZone = (zone, zoneData) => {
|
|||||||
|
|
||||||
|
|
||||||
var initialize = function(zone) {
|
var initialize = function(zone) {
|
||||||
var
|
var
|
||||||
allowRemove = zone.dataset.allowRemove,
|
allowRemove = zone.dataset.allowRemove,
|
||||||
zoneData = { zone: zone, suffix: createFilename(), allowRemove: allowRemove, old: null }
|
zoneData = { zone: zone, suffix: createFilename(), allowRemove: allowRemove, old: null }
|
||||||
;
|
;
|
||||||
@ -204,13 +203,13 @@ var createFilename = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var storeDataInForm = (zone, zoneData) => {
|
var storeDataInForm = (zone, zoneData) => {
|
||||||
var
|
var
|
||||||
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
||||||
inputIv = zone.querySelector('input[data-stored-object-iv]'),
|
inputIv = zone.querySelector('input[data-stored-object-iv]'),
|
||||||
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
||||||
inputType = zone.querySelector('input[data-async-file-type]')
|
inputType = zone.querySelector('input[data-async-file-type]')
|
||||||
;
|
;
|
||||||
|
|
||||||
inputKey.value = JSON.stringify(zoneData.crypto.rawKey);
|
inputKey.value = JSON.stringify(zoneData.crypto.rawKey);
|
||||||
inputIv.value = JSON.stringify(Array.from(zoneData.crypto.iv));
|
inputIv.value = JSON.stringify(Array.from(zoneData.crypto.iv));
|
||||||
inputType.value = zoneData.originalType;
|
inputType.value = zoneData.originalType;
|
||||||
@ -220,18 +219,18 @@ var storeDataInForm = (zone, zoneData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const restoreDataInForm = (zone, zoneData) => {
|
const restoreDataInForm = (zone, zoneData) => {
|
||||||
var
|
var
|
||||||
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
||||||
inputIv = zone.querySelector('input[data-stored-object-iv]'),
|
inputIv = zone.querySelector('input[data-stored-object-iv]'),
|
||||||
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
||||||
inputType = zone.querySelector('input[data-async-file-type]')
|
inputType = zone.querySelector('input[data-async-file-type]')
|
||||||
;
|
;
|
||||||
|
|
||||||
if (zoneData.old === null) {
|
if (zoneData.old === null) {
|
||||||
console.log('should not have restored data');
|
console.log('should not have restored data');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
inputKey.value = zoneData.old.key;
|
inputKey.value = zoneData.old.key;
|
||||||
inputIv.value = zoneData.old.iv;
|
inputIv.value = zoneData.old.iv;
|
||||||
inputType.value = zoneData.old.type;
|
inputType.value = zoneData.old.type;
|
||||||
@ -241,21 +240,21 @@ const restoreDataInForm = (zone, zoneData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const hasDataInForm = (zone, zoneData) => {
|
const hasDataInForm = (zone, zoneData) => {
|
||||||
var
|
var
|
||||||
inputObject = zone.querySelector('input[data-async-file-upload]')
|
inputObject = zone.querySelector('input[data-async-file-upload]')
|
||||||
;
|
;
|
||||||
|
|
||||||
return inputObject.value.length > 0;
|
return inputObject.value.length > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
var removeDataInForm = (zone, zoneData) => {
|
var removeDataInForm = (zone, zoneData) => {
|
||||||
var
|
var
|
||||||
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
||||||
inputIv = zone.querySelector('input[data-stored-object-iv]'),
|
inputIv = zone.querySelector('input[data-stored-object-iv]'),
|
||||||
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
inputObject = zone.querySelector('input[data-async-file-upload]'),
|
||||||
inputType = zone.querySelector('input[data-async-file-type]')
|
inputType = zone.querySelector('input[data-async-file-type]')
|
||||||
;
|
;
|
||||||
|
|
||||||
// store data for future usage
|
// store data for future usage
|
||||||
zoneData.old = {
|
zoneData.old = {
|
||||||
key: inputKey.value,
|
key: inputKey.value,
|
||||||
@ -268,7 +267,7 @@ var removeDataInForm = (zone, zoneData) => {
|
|||||||
inputIv.value = "";
|
inputIv.value = "";
|
||||||
inputType.value = "";
|
inputType.value = "";
|
||||||
inputObject.value = "";
|
inputObject.value = "";
|
||||||
|
|
||||||
insertDownloadButton(zone);
|
insertDownloadButton(zone);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -279,25 +278,25 @@ var insertRemoveButton = (zone, zoneData) => {
|
|||||||
labelRemove = zone.dataset.dictRemove,
|
labelRemove = zone.dataset.dictRemove,
|
||||||
labelCancel = 'Restaurer'
|
labelCancel = 'Restaurer'
|
||||||
;
|
;
|
||||||
|
|
||||||
removeButton.classList.add('btn', 'btn-delete');
|
removeButton.classList.add('btn', 'btn-delete');
|
||||||
removeButton.textContent = labelRemove;
|
removeButton.textContent = labelRemove;
|
||||||
|
|
||||||
cancelButton.classList.add('btn', 'btn-cancel');
|
cancelButton.classList.add('btn', 'btn-cancel');
|
||||||
cancelButton.textContent = labelCancel;
|
cancelButton.textContent = labelCancel;
|
||||||
|
|
||||||
removeButton.addEventListener('click', (e) => {
|
removeButton.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (zoneData.allowRemove === 'true') {
|
if (zoneData.allowRemove === 'true') {
|
||||||
removeDataInForm(zone, zoneData);
|
removeDataInForm(zone, zoneData);
|
||||||
cancelButton.addEventListener('click', (e) => {
|
cancelButton.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
restoreDataInForm(zone, zoneData);
|
restoreDataInForm(zone, zoneData);
|
||||||
|
|
||||||
cancelButton.remove();
|
cancelButton.remove();
|
||||||
zone.querySelector('.dropzone').remove();
|
zone.querySelector('.dropzone').remove();
|
||||||
|
|
||||||
initialize(zone);
|
initialize(zone);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -306,16 +305,16 @@ var insertRemoveButton = (zone, zoneData) => {
|
|||||||
removeButton.remove();
|
removeButton.remove();
|
||||||
createZone(zone, zoneData);
|
createZone(zone, zoneData);
|
||||||
});
|
});
|
||||||
|
|
||||||
addBelowButton(removeButton, zone, zoneData);
|
addBelowButton(removeButton, zone, zoneData);
|
||||||
// zone.appendChild(removeButton);
|
// zone.appendChild(removeButton);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeDownloadButton = (zone, zoneData) => {
|
const removeDownloadButton = (zone, zoneData) => {
|
||||||
var
|
var
|
||||||
existingButtons = zone.querySelectorAll('a[data-download-button]')
|
existingButtons = zone.querySelectorAll('a[data-download-button]')
|
||||||
;
|
;
|
||||||
|
|
||||||
// remove existing
|
// remove existing
|
||||||
existingButtons.forEach(function(b) {
|
existingButtons.forEach(function(b) {
|
||||||
b.remove();
|
b.remove();
|
||||||
@ -323,7 +322,7 @@ const removeDownloadButton = (zone, zoneData) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var insertDownloadButton = (zone, zoneData) => {
|
var insertDownloadButton = (zone, zoneData) => {
|
||||||
var
|
var
|
||||||
existingButtons = zone.querySelectorAll('a[data-download-button]'),
|
existingButtons = zone.querySelectorAll('a[data-download-button]'),
|
||||||
newButton = document.createElement('a'),
|
newButton = document.createElement('a'),
|
||||||
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
inputKey = zone.querySelector('input[data-stored-object-key]'),
|
||||||
@ -336,18 +335,18 @@ var insertDownloadButton = (zone, zoneData) => {
|
|||||||
tempUrlGenerator = zone.dataset.tempUrlGenerator,
|
tempUrlGenerator = zone.dataset.tempUrlGenerator,
|
||||||
tempUrlGeneratorParams = new URLSearchParams()
|
tempUrlGeneratorParams = new URLSearchParams()
|
||||||
;
|
;
|
||||||
|
|
||||||
// remove existing
|
// remove existing
|
||||||
existingButtons.forEach(function(b) {
|
existingButtons.forEach(function(b) {
|
||||||
b.remove();
|
b.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (inputObject.value === '') {
|
if (inputObject.value === '') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tempUrlGeneratorParams.append('object_name', inputObject.value);
|
tempUrlGeneratorParams.append('object_name', inputObject.value);
|
||||||
|
|
||||||
newButton.dataset.downloadButton = true;
|
newButton.dataset.downloadButton = true;
|
||||||
newButton.dataset.key = inputKey.value;
|
newButton.dataset.key = inputKey.value;
|
||||||
newButton.dataset.iv = inputIv.value;
|
newButton.dataset.iv = inputIv.value;
|
||||||
@ -357,7 +356,7 @@ var insertDownloadButton = (zone, zoneData) => {
|
|||||||
newButton.dataset.tempUrlGetGenerator = tempUrlGenerator + '?' + tempUrlGeneratorParams.toString();
|
newButton.dataset.tempUrlGetGenerator = tempUrlGenerator + '?' + tempUrlGeneratorParams.toString();
|
||||||
newButton.classList.add('btn', 'btn-download', 'dz-bt-below-dropzone');
|
newButton.classList.add('btn', 'btn-download', 'dz-bt-below-dropzone');
|
||||||
newButton.textContent = labelQuietButton;
|
newButton.textContent = labelQuietButton;
|
||||||
|
|
||||||
addBelowButton(newButton, zone, zoneData);
|
addBelowButton(newButton, zone, zoneData);
|
||||||
//zone.appendChild(newButton);
|
//zone.appendChild(newButton);
|
||||||
initializeDownload(zone);
|
initializeDownload(zone);
|
||||||
@ -370,3 +369,5 @@ window.addEventListener('load', function(e) {
|
|||||||
window.addEventListener('collection-add-entry', function(e) {
|
window.addEventListener('collection-add-entry', function(e) {
|
||||||
searchForZones(e.detail.entry);
|
searchForZones(e.detail.entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export { searchForZones };
|
||||||
|
@ -0,0 +1,156 @@
|
|||||||
|
<template>
|
||||||
|
<a class="btn btn-create" :title="$t(buttonTitle)" @click="openModal">
|
||||||
|
<span>{{ $t(buttonTitle) }}</span>
|
||||||
|
</a>
|
||||||
|
<teleport to="body">
|
||||||
|
<div>
|
||||||
|
<modal v-if="modal.showModal"
|
||||||
|
:modalDialogClass="modal.modalDialogClass"
|
||||||
|
@close="modal.showModal = false">
|
||||||
|
|
||||||
|
<template v-slot:header>
|
||||||
|
{{ $t('upload_a_document') }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body>
|
||||||
|
<div id="dropZoneWrapper" ref="dropZoneWrapper">
|
||||||
|
<div
|
||||||
|
data-stored-object="data-stored-object"
|
||||||
|
:data-label-preparing="$t('data_label_preparing')"
|
||||||
|
:data-label-quiet-button="$t('data_label_quiet_button')"
|
||||||
|
:data-label-ready="$t('data_label_ready')"
|
||||||
|
:data-dict-file-too-big="$t('data_dict_file_too_big')"
|
||||||
|
:data-dict-default-message="$t('data_dict_default_message')"
|
||||||
|
:data-dict-remove-file="$t('data_dict_remove_file')"
|
||||||
|
:data-dict-max-files-exceeded="$t('data_dict_max_files_exceeded')"
|
||||||
|
:data-dict-cancel-upload="$t('data_dict_cancel_upload')"
|
||||||
|
:data-dict-cancel-upload-confirm="$t('data_dict_cancel_upload_confirm')"
|
||||||
|
:data-dict-upload-canceled="$t('data_dict_upload_canceled')"
|
||||||
|
:data-dict-remove="$t('data_dict_remove')"
|
||||||
|
:data-allow-remove="!options.required"
|
||||||
|
data-temp-url-generator="/asyncupload/temp_url/generate/GET">
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
data-async-file-upload="data-async-file-upload"
|
||||||
|
data-generate-temp-url-post="/asyncupload/temp_url/generate/post?expires_delay=180&submit_delay=3600"
|
||||||
|
data-temp-url-get="/asyncupload/temp_url/generate/GET"
|
||||||
|
:data-max-files="options.maxFiles"
|
||||||
|
:data-max-post-size="options.maxPostSize"
|
||||||
|
:v-model="dataAsyncFileUpload"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
data-stored-object-key="1"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
data-stored-object-iv="1"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
data-async-file-type="1"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:footer>
|
||||||
|
<button class="btn btn-create"
|
||||||
|
@click.prevent="saveDocument">
|
||||||
|
{{ $t('action.add')}}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</modal>
|
||||||
|
</div>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||||
|
import { searchForZones } from '../../module/async_upload/uploader';
|
||||||
|
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||||
|
|
||||||
|
const i18n = {
|
||||||
|
messages: {
|
||||||
|
fr: {
|
||||||
|
upload_a_document: "Téléversez un document",
|
||||||
|
data_label_preparing: "Chargement...",
|
||||||
|
data_label_quiet_button: "Téléchargez le fichier existant",
|
||||||
|
data_label_ready: "Prêt à montrer",
|
||||||
|
data_dict_file_too_big: "Fichier trop volumineux",
|
||||||
|
data_dict_default_message: "Glissez votre fichier ou cliquez ici",
|
||||||
|
data_dict_remove_file: "Enlevez votre fichier pour en téléversez un autre",
|
||||||
|
data_dict_max_files_exceeded: "Nombre maximum de fichiers atteint. Enlevez les fichiers précédents",
|
||||||
|
data_dict_cancel_upload: "Annulez le téléversement",
|
||||||
|
data_dict_cancel_upload_confirm: "Êtes-vous sûr·e de vouloir annuler ce téléversement?",
|
||||||
|
data_dict_upload_canceled: "Téléversement annulé",
|
||||||
|
data_dict_remove: "Enlevez le fichier existant",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "AddAsyncUpload",
|
||||||
|
components: {
|
||||||
|
Modal
|
||||||
|
},
|
||||||
|
i18n,
|
||||||
|
props: [
|
||||||
|
'buttonTitle',
|
||||||
|
'options'
|
||||||
|
],
|
||||||
|
emits: ['addDocument'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
modal: {
|
||||||
|
showModal: false,
|
||||||
|
modalDialogClass: "modal-dialog-centered modal-md"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
if (this.modal.showModal){
|
||||||
|
searchForZones(this.$refs.dropZoneWrapper);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openModal() {
|
||||||
|
this.modal.showModal = true;
|
||||||
|
},
|
||||||
|
saveDocument() {
|
||||||
|
const dropzone = this.$refs.dropZoneWrapper;
|
||||||
|
if (dropzone) {
|
||||||
|
const inputKey = dropzone.querySelector('input[data-stored-object-key]');
|
||||||
|
const inputIv = dropzone.querySelector('input[data-stored-object-iv]');
|
||||||
|
const inputObject = dropzone.querySelector('input[data-async-file-upload]');
|
||||||
|
const inputType = dropzone.querySelector('input[data-async-file-type]');
|
||||||
|
|
||||||
|
const url = '/api/1.0/docstore/stored-object.json';
|
||||||
|
const body = {
|
||||||
|
filename: inputObject.value,
|
||||||
|
keyInfos: JSON.parse(inputKey.value),
|
||||||
|
iv: JSON.parse(inputIv.value),
|
||||||
|
type: inputType.value,
|
||||||
|
};
|
||||||
|
makeFetch('POST', url, body)
|
||||||
|
.then(r => {
|
||||||
|
this.$emit("addDocument", r);
|
||||||
|
this.modal.showModal = false;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error.name === 'ValidationException') {
|
||||||
|
for (let v of error.violations) {
|
||||||
|
this.$toast.open({message: v });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$toast.open({message: 'An error occurred'});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$toast.open({message: 'An error occurred - drop zone not found'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\DocStoreBundle\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||||
|
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||||
|
use function array_key_exists;
|
||||||
|
use function is_array;
|
||||||
|
|
||||||
|
class StoredObjectDenormalizer implements DenormalizerInterface
|
||||||
|
{
|
||||||
|
private StoredObjectRepository $storedObjectRepository;
|
||||||
|
|
||||||
|
public function __construct(StoredObjectRepository $storedObjectRepository)
|
||||||
|
{
|
||||||
|
$this->storedObjectRepository = $storedObjectRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function denormalize($data, $type, $format = null, array $context = [])
|
||||||
|
{
|
||||||
|
if (array_key_exists(AbstractNormalizer::OBJECT_TO_POPULATE, $context)) {
|
||||||
|
return $context[AbstractNormalizer::OBJECT_TO_POPULATE];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->storedObjectRepository->find($data['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsDenormalization($data, $type, $format = null)
|
||||||
|
{
|
||||||
|
if (false === is_array($data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === array_key_exists('id', $data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StoredObject::class === $type;
|
||||||
|
}
|
||||||
|
}
|
46
src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml
Normal file
46
src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
openapi: "3.0.0"
|
||||||
|
info:
|
||||||
|
version: "1.0.0"
|
||||||
|
title: "Chill api"
|
||||||
|
description: "Api documentation for chill. Currently, work in progress"
|
||||||
|
servers:
|
||||||
|
- url: "/api"
|
||||||
|
description: "Your current dev server"
|
||||||
|
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
StoredObject:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
filename:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/1.0/docstore/stored-object.json:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- storedobject
|
||||||
|
summary: Create a stored object
|
||||||
|
requestBody:
|
||||||
|
description: "A stored object"
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/StoredObject"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "OK"
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/StoredObject"
|
||||||
|
403:
|
||||||
|
description: "Unauthorized"
|
||||||
|
422:
|
||||||
|
description: "Invalid data"
|
@ -1,4 +1,7 @@
|
|||||||
module.exports = function(encore)
|
module.exports = function(encore)
|
||||||
{
|
{
|
||||||
|
encore.addAliases({
|
||||||
|
ChillDocStoreAssets: __dirname + '/Resources/public'
|
||||||
|
});
|
||||||
encore.addEntry('mod_async_upload', __dirname + '/Resources/public/module/async_upload/index.js');
|
encore.addEntry('mod_async_upload', __dirname + '/Resources/public/module/async_upload/index.js');
|
||||||
};
|
};
|
||||||
|
@ -33,3 +33,10 @@ services:
|
|||||||
resource: './../Workflow/'
|
resource: './../Workflow/'
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
autowire: true
|
autowire: true
|
||||||
|
|
||||||
|
Chill\DocStoreBundle\Serializer\Normalizer\:
|
||||||
|
autowire: true
|
||||||
|
resource: '../Serializer/Normalizer/'
|
||||||
|
tags:
|
||||||
|
- { name: 'serializer.normalizer', priority: 16 }
|
||||||
|
|
||||||
|
@ -70,9 +70,10 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
|
|||||||
* @ORM\OneToMany(
|
* @ORM\OneToMany(
|
||||||
* targetEntity=AccompanyingPeriodWorkEvaluationDocument::class,
|
* targetEntity=AccompanyingPeriodWorkEvaluationDocument::class,
|
||||||
* mappedBy="accompanyingPeriodWorkEvaluation",
|
* mappedBy="accompanyingPeriodWorkEvaluation",
|
||||||
* cascade={"remove"}
|
* cascade={"remove", "persist"}
|
||||||
* )
|
* )
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read", "write"})
|
||||||
|
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||||
*/
|
*/
|
||||||
private Collection $documents;
|
private Collection $documents;
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
|||||||
* @ORM\SequenceGenerator(sequenceName="chill_person_social_work_eval_doc_id_seq", allocationSize=1, initialValue=1000)
|
* @ORM\SequenceGenerator(sequenceName="chill_person_social_work_eval_doc_id_seq", allocationSize=1, initialValue=1000)
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read"})
|
||||||
*/
|
*/
|
||||||
private ?int $id;
|
private ?int $id = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\ManyToOne(
|
* @ORM\ManyToOne(
|
||||||
@ -69,6 +69,8 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
|||||||
* cascade={"remove"},
|
* cascade={"remove"},
|
||||||
* )
|
* )
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read"})
|
||||||
|
* @Serializer\Groups({"write"})
|
||||||
|
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||||
*/
|
*/
|
||||||
private ?StoredObject $storedObject = null;
|
private ?StoredObject $storedObject = null;
|
||||||
|
|
||||||
@ -80,6 +82,14 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
|||||||
*/
|
*/
|
||||||
private ?DocGeneratorTemplate $template = null;
|
private ?DocGeneratorTemplate $template = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="text", nullable=true)
|
||||||
|
* @Serializer\Groups({"read"})
|
||||||
|
* @Serializer\Groups({"write"})
|
||||||
|
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||||
|
*/
|
||||||
|
private ?string $title = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
|
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
|
||||||
* @Serializer\Groups({"read"})
|
* @Serializer\Groups({"read"})
|
||||||
@ -127,6 +137,11 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
|||||||
return $this->template;
|
return $this->template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTitle(): ?string
|
||||||
|
{
|
||||||
|
return $this->title;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return DateTimeImmutable|null
|
* @return DateTimeImmutable|null
|
||||||
*/
|
*/
|
||||||
@ -185,6 +200,13 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setTitle(?string $title): AccompanyingPeriodWorkEvaluationDocument
|
||||||
|
{
|
||||||
|
$this->title = $title;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function setUpdatedAt(DateTimeInterface $datetime): TrackUpdateInterface
|
public function setUpdatedAt(DateTimeInterface $datetime): TrackUpdateInterface
|
||||||
{
|
{
|
||||||
$this->updatedAt = $datetime;
|
$this->updatedAt = $datetime;
|
||||||
|
@ -65,14 +65,31 @@
|
|||||||
<h5>{{ $t('Documents') }} :</h5>
|
<h5>{{ $t('Documents') }} :</h5>
|
||||||
|
|
||||||
<div class="flex-table">
|
<div class="flex-table">
|
||||||
<div class="item-bloc" v-for="d in evaluation.documents">
|
<div class="item-bloc" v-for="(d, i) in evaluation.documents" :key="i">
|
||||||
<div class="item-row">
|
<div class="item-row">
|
||||||
<div class="item-col"><h6>{{ d.template.name.fr }}</h6></div>
|
<div class="item-col" style="margin-right: 6px;">
|
||||||
<div class="item-col">
|
<label class="col-form-label">
|
||||||
<p>Créé par {{ d.createdBy.text }}<br/>
|
{{ $t('document_title') }}
|
||||||
Le {{ $d(ISOToDatetime(d.createdAt.datetime), 'long') }}</p>
|
</label>
|
||||||
|
<div>
|
||||||
</div>
|
<input
|
||||||
|
class="form-control form-control-sm"
|
||||||
|
type="string"
|
||||||
|
:value=d.title
|
||||||
|
:id=d.id
|
||||||
|
@input="onInputDocumentTitle"/>
|
||||||
|
</div>
|
||||||
|
<div v-if="d.template">
|
||||||
|
<label class="col-form-label">
|
||||||
|
{{ $t('template_title') }}
|
||||||
|
</label>
|
||||||
|
<div>{{ d.template.name.fr }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-col">
|
||||||
|
<p v-if="d.createdBy">Créé par {{ d.createdBy.text }}<br/>
|
||||||
|
Le {{ $d(ISOToDatetime(d.createdAt.datetime), 'long') }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-row">
|
<div class="item-row">
|
||||||
<ul class="record_actions" >
|
<ul class="record_actions" >
|
||||||
@ -81,6 +98,10 @@
|
|||||||
<i class="fa fa-edit"></i>
|
<i class="fa fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-sm btn-delete" @click="removeDocument(d)">
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -88,6 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
|
<h6>{{ $t('document_add') }} :</h6>
|
||||||
<pick-template
|
<pick-template
|
||||||
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
|
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
|
||||||
:id="evaluation.id"
|
:id="evaluation.id"
|
||||||
@ -99,6 +121,20 @@
|
|||||||
<label class="col-sm-4 col-form-label">{{ $t('evaluation_generate_a_document') }}</label>
|
<label class="col-sm-4 col-form-label">{{ $t('evaluation_generate_a_document') }}</label>
|
||||||
</template>
|
</template>
|
||||||
</pick-template>
|
</pick-template>
|
||||||
|
<div>
|
||||||
|
<label class="col-sm-4 col-form-label">{{ $t('document_upload') }}</label>
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<add-async-upload
|
||||||
|
:buttonTitle="$t('browse')"
|
||||||
|
:options="asyncUploadOptions"
|
||||||
|
@addDocument="addDocument"
|
||||||
|
>
|
||||||
|
</add-async-upload>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -111,6 +147,7 @@ import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
|
|||||||
import { mapGetters, mapState } from 'vuex';
|
import { mapGetters, mapState } from 'vuex';
|
||||||
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
||||||
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
|
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
|
||||||
|
import AddAsyncUpload from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUpload.vue';
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
messages: {
|
messages: {
|
||||||
@ -129,6 +166,11 @@ const i18n = {
|
|||||||
evaluation_add_a_document: "Ajouter un document",
|
evaluation_add_a_document: "Ajouter un document",
|
||||||
evaluation_add: "Ajouter une évaluation",
|
evaluation_add: "Ajouter une évaluation",
|
||||||
Documents: "Documents",
|
Documents: "Documents",
|
||||||
|
document_add: "Générer ou téléverser un document",
|
||||||
|
document_upload: "Téléverser un document",
|
||||||
|
document_title: "Titre du document",
|
||||||
|
template_title: "Nom du template",
|
||||||
|
browse: "Ajouter un document"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -139,14 +181,23 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
ckeditor: CKEditor.component,
|
ckeditor: CKEditor.component,
|
||||||
PickTemplate,
|
PickTemplate,
|
||||||
|
AddAsyncUpload
|
||||||
},
|
},
|
||||||
i18n,
|
i18n,
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
editor: ClassicEditor,
|
editor: ClassicEditor,
|
||||||
template: null,
|
template: null,
|
||||||
|
asyncUploadOptions: {
|
||||||
|
maxFiles: 1,
|
||||||
|
maxPostSize: 15000000,
|
||||||
|
required: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log(this.evaluation)
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([
|
...mapState([
|
||||||
'isPosting'
|
'isPosting'
|
||||||
@ -162,7 +213,6 @@ export default {
|
|||||||
return dateToISO(this.evaluation.startDate);
|
return dateToISO(this.evaluation.startDate);
|
||||||
},
|
},
|
||||||
set(v) {
|
set(v) {
|
||||||
console.log(v);
|
|
||||||
this.$store.commit('setEvaluationStartDate', { key: this.evaluation.key, date: ISOToDate(v) });
|
this.$store.commit('setEvaluationStartDate', { key: this.evaluation.key, date: ISOToDate(v) });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -205,11 +255,11 @@ export default {
|
|||||||
})
|
})
|
||||||
;
|
;
|
||||||
},
|
},
|
||||||
buildEditLink(storedObject) {
|
buildEditLink(storedObject) {
|
||||||
return `/wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent(
|
return `/wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent(
|
||||||
window.location.pathname + window.location.search + window.location.hash);
|
window.location.pathname + window.location.search + window.location.hash);
|
||||||
},
|
},
|
||||||
submitBeforeGenerate({template}) {
|
submitBeforeGenerate({template}) {
|
||||||
const callback = (data) => {
|
const callback = (data) => {
|
||||||
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id;
|
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id;
|
||||||
|
|
||||||
@ -217,6 +267,21 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; });
|
return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; });
|
||||||
|
},
|
||||||
|
onInputDocumentTitle(event) {
|
||||||
|
const id = Number(event.target.id);
|
||||||
|
const title = event.target.value;
|
||||||
|
this.$store.commit('updateDocumentTitle', {id: id, evaluationKey: this.evaluation.key, title: title});
|
||||||
|
},
|
||||||
|
addDocument(storedObject) {
|
||||||
|
let document = {
|
||||||
|
type: 'accompanying_period_work_evaluation_document',
|
||||||
|
storedObject: storedObject
|
||||||
|
};
|
||||||
|
this.$store.commit('addDocument', {key: this.evaluation.key, document: document});
|
||||||
|
},
|
||||||
|
removeDocument(document) {
|
||||||
|
this.$store.commit('removeDocument', {key: this.evaluation.key, document: document});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,7 @@ const store = createStore({
|
|||||||
maxDate: e.maxDate !== null ? { datetime: datetimeToISO(e.maxDate) } : null,
|
maxDate: e.maxDate !== null ? { datetime: datetimeToISO(e.maxDate) } : null,
|
||||||
warningInterval: intervalDaysToISO(e.warningInterval),
|
warningInterval: intervalDaysToISO(e.warningInterval),
|
||||||
comment: e.comment,
|
comment: e.comment,
|
||||||
|
documents: e.documents
|
||||||
};
|
};
|
||||||
if (e.id !== undefined) {
|
if (e.id !== undefined) {
|
||||||
o.id = e.id;
|
o.id = e.id;
|
||||||
@ -197,6 +198,18 @@ const store = createStore({
|
|||||||
|
|
||||||
found.results = found.results.filter(r => r.id !== result.id);
|
found.results = found.results.filter(r => r.id !== result.id);
|
||||||
},
|
},
|
||||||
|
addDocument(state, payload) {
|
||||||
|
state.evaluationsPicked.find(e => e.key === payload.key).documents.push(payload.document);
|
||||||
|
},
|
||||||
|
removeDocument(state, payload) {
|
||||||
|
let evaluations = state.evaluationsPicked.find(e => e.key === payload.key);
|
||||||
|
|
||||||
|
if (evaluations === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluations.documents = evaluations.documents.filter(d => d.id !== payload.document.id);
|
||||||
|
},
|
||||||
addEvaluation(state, evaluation) {
|
addEvaluation(state, evaluation) {
|
||||||
let e = {
|
let e = {
|
||||||
type: "accompanying_period_work_evaluation",
|
type: "accompanying_period_work_evaluation",
|
||||||
@ -284,6 +297,10 @@ const store = createStore({
|
|||||||
setIsPosting(state, st) {
|
setIsPosting(state, st) {
|
||||||
state.isPosting = st;
|
state.isPosting = st;
|
||||||
},
|
},
|
||||||
|
updateDocumentTitle(state, payload) {
|
||||||
|
state.evaluationsPicked.find(e => e.key === payload.evaluationKey)
|
||||||
|
.documents.find(d => d.id === payload.id).title = payload.title;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
updateThirdParty({ commit }, payload) {
|
updateThirdParty({ commit }, payload) {
|
||||||
@ -374,13 +391,18 @@ const store = createStore({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
addDocument({commit}, payload) {
|
||||||
|
commit('addDocument', payload);
|
||||||
|
},
|
||||||
|
removeDocument({commit}, payload) {
|
||||||
|
commit('removeDocument', payload);
|
||||||
|
},
|
||||||
submit({ getters, state, commit }, callback) {
|
submit({ getters, state, commit }, callback) {
|
||||||
let
|
let
|
||||||
payload = getters.buildPayload,
|
payload = getters.buildPayload,
|
||||||
url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json`,
|
url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json`,
|
||||||
errors = []
|
errors = []
|
||||||
;
|
;
|
||||||
|
|
||||||
commit('setIsPosting', true);
|
commit('setIsPosting', true);
|
||||||
|
|
||||||
return makeFetch('PUT', url, payload)
|
return makeFetch('PUT', url, payload)
|
||||||
@ -397,6 +419,9 @@ const store = createStore({
|
|||||||
commit('setErrors', error.violations);
|
commit('setErrors', error.violations);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
updateDocumentTitle({commit}, payload) {
|
||||||
|
commit('updateDocumentTitle', payload)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
|
||||||
|
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
|
||||||
|
|
||||||
|
use function array_key_exists;
|
||||||
|
use function array_merge;
|
||||||
|
use function in_array;
|
||||||
|
use function is_array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This denormalizer rely on AbstractNormalizer for most of the job, and
|
||||||
|
* add some logic for synchronizing collection.
|
||||||
|
*/
|
||||||
|
class AccompanyingPeriodWorkEvaluationDenormalizer implements ContextAwareDenormalizerInterface, DenormalizerAwareInterface
|
||||||
|
{
|
||||||
|
use DenormalizerAwareTrait;
|
||||||
|
use ObjectToPopulateTrait;
|
||||||
|
|
||||||
|
private EntityManagerInterface $em;
|
||||||
|
|
||||||
|
private AccompanyingPeriodWorkRepository $workRepository;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
AccompanyingPeriodWorkRepository $workRepository,
|
||||||
|
EntityManagerInterface $em
|
||||||
|
) {
|
||||||
|
$this->workRepository = $workRepository;
|
||||||
|
$this->em = $em;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function denormalize($data, $type, $format = null, array $context = [])
|
||||||
|
{
|
||||||
|
$evaluation = $this->denormalizer->denormalize($data, $type, $format, array_merge(
|
||||||
|
$context,
|
||||||
|
['skip' => self::class]
|
||||||
|
));
|
||||||
|
|
||||||
|
//if (in_array('accompanying_period_work:edit', $context['groups'] ?? [], true)) {
|
||||||
|
$this->handleEvaluationCollection($data, $evaluation, $format, $context);
|
||||||
|
//}
|
||||||
|
|
||||||
|
return $evaluation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsDenormalization($data, $type, $format = null, array $context = []): bool
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodWorkEvaluation::class === $type
|
||||||
|
&& self::class !== ($context['skip'] ?? null)
|
||||||
|
&& is_array($data)
|
||||||
|
&& array_key_exists('type', $data)
|
||||||
|
&& 'accompanying_period_work_evaluation' === $data['type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleEvaluationCollection(array $data, AccompanyingPeriodWorkEvaluation $evaluation, string $format, array $context)
|
||||||
|
{
|
||||||
|
$dataById = [];
|
||||||
|
$dataWithoutId = [];
|
||||||
|
|
||||||
|
foreach ($data['documents'] as $e) {
|
||||||
|
if (array_key_exists('id', $e)) {
|
||||||
|
$dataById[$e['id']] = $e;
|
||||||
|
} else {
|
||||||
|
$dataWithoutId[] = $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dump($dataById);
|
||||||
|
dump($dataWithoutId);
|
||||||
|
|
||||||
|
dump($evaluation);
|
||||||
|
//partition the separate kept documents and removed one
|
||||||
|
[$kept, $removed] = $evaluation->getDocuments()
|
||||||
|
->partition(
|
||||||
|
static fn (int $key, AccompanyingPeriodWorkEvaluationDocument $a) => array_key_exists($a->getId(), $dataById)
|
||||||
|
);
|
||||||
|
//$kept = $evaluation->getDocuments();
|
||||||
|
dump($kept);
|
||||||
|
dump($removed);
|
||||||
|
// remove the document from evaluation
|
||||||
|
foreach ($removed as $r) {
|
||||||
|
dump($r);
|
||||||
|
$evaluation->removeDocument($r);
|
||||||
|
}
|
||||||
|
// handle the documents kept
|
||||||
|
foreach ($kept as $k) {
|
||||||
|
dump($k); // Cannot iterate over $kept which is a PersistentCollection
|
||||||
|
$evaluation->removeDocument($k);
|
||||||
|
dump($evaluation);
|
||||||
|
$document = $this->denormalizer->denormalize(
|
||||||
|
$dataById[$k->getId()],
|
||||||
|
AccompanyingPeriodWorkEvaluationDocument::class,
|
||||||
|
$format,
|
||||||
|
array_merge(
|
||||||
|
$context,
|
||||||
|
[
|
||||||
|
'groups' => ['write'],
|
||||||
|
AbstractNormalizer::OBJECT_TO_POPULATE => $k,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$evaluation->addDocument($document);
|
||||||
|
}
|
||||||
|
// create new document
|
||||||
|
foreach ($dataWithoutId as $newData) {
|
||||||
|
dump($newData);
|
||||||
|
$document = $this->denormalizer->denormalize(
|
||||||
|
$newData,
|
||||||
|
AccompanyingPeriodWorkEvaluationDocument::class,
|
||||||
|
$format,
|
||||||
|
array_merge(
|
||||||
|
$context,
|
||||||
|
['groups' => ['accompanying_period_work_evaluation:create']]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$evaluation->addDocument($document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\Person;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add title to AccompanyingPeriodWorkEvaluationDocument.
|
||||||
|
*/
|
||||||
|
final class Version20220224145951 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document DROP title');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add title to AccompanyingPeriodWorkEvaluationDocument';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document ADD title TEXT DEFAULT NULL');
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user