mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-11-03 18:58:24 +00:00 
			
		
		
		
	Compare commits
	
		
			224 Commits
		
	
	
		
			451-activi
			...
			ticket/scr
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						4f51ef81ad
	
				 | 
					
					
						|||
| 
						
						
							
						
						4637dc692c
	
				 | 
					
					
						|||
| 38935edb93 | |||
| 
						 | 
					e1ef65d4ca | ||
| ec9d0be70b | |||
| 
						 | 
					0ba2cbc1e8 | ||
| e87429933a | |||
| 
						
						
							
						
						8e2e676e3d
	
				 | 
					
					
						|||
| e12ad563a3 | |||
| 
						 | 
					711aa8db9b | ||
| e78d44953f | |||
| 18f67801c7 | |||
| 
						
						
							
						
						c815e6bc69
	
				 | 
					
					
						|||
| 807f2711fe | |||
| 
						 | 
					cd594cd580 | ||
| 
						
						
							
						
						fb6b26bfb5
	
				 | 
					
					
						|||
| 
						
						
							
						
						c5cedb8bd6
	
				 | 
					
					
						|||
| 
						
						
							
						
						2665e43a61
	
				 | 
					
					
						|||
| 
						
						
							
						
						25561cdf63
	
				 | 
					
					
						|||
| 10b73e06e1 | |||
| 
						 | 
					e7c04e34a9 | ||
| 164beee7c6 | |||
| 
						 | 
					4d96eb9457 | ||
| fe2eba3b29 | |||
| 
						 | 
					61d1232e31 | ||
| 6594d4f6a6 | |||
| 1a66a9e864 | |||
| 1b74c119dc | |||
| 14d88810f3 | |||
| 445a2c9358 | |||
| c8baf0a8aa | |||
| faed443a96 | |||
| bbf387d96f | |||
| 
						 | 
					b7c9b60744 | ||
| 
						
						
							
						
						2bd303bbbe
	
				 | 
					
					
						|||
| 
						
						
							
						
						c5e6122d2c
	
				 | 
					
					
						|||
| 088b876e20 | |||
| 3400656d7c | |||
| 
						
						
							
						
						568c8be7fd
	
				 | 
					
					
						|||
| 
						
						
							
						
						538ecc42ea
	
				 | 
					
					
						|||
| 
						
						
							
						
						15d26d4b06
	
				 | 
					
					
						|||
| 
						
						
							
						
						d8bd9bd7cd
	
				 | 
					
					
						|||
| 
						
						
							
						
						dcdfba5ccd
	
				 | 
					
					
						|||
| 
						
						
							
						
						0204bdd38d
	
				 | 
					
					
						|||
| 
						
						
							
						
						392fd01b56
	
				 | 
					
					
						|||
| 35844f3b73 | |||
| 7506b918d7 | |||
| cfba291f2c | |||
| 
						 | 
					04438c09d3 | ||
| 2a54d1b909 | |||
| 
						 | 
					628eeac5e0 | ||
| a2263b3fa1 | |||
| 74796d0fb0 | |||
| 
						
						
							
						
						c19481e40a
	
				 | 
					
					
						|||
| 
						 | 
					6eeb717b1a | ||
| 
						
						
							
						
						beb7c462da
	
				 | 
					
					
						|||
| 
						 | 
					dbf363a9e8 | ||
| 
						
						
							
						
						64a2f7c9ed
	
				 | 
					
					
						|||
| f26d9739c8 | |||
| 
						
						
							
						
						afa5edc1d8
	
				 | 
					
					
						|||
| 
						
						
							
						
						42d6c9e672
	
				 | 
					
					
						|||
| 
						
						
							
						
						2b22d4cb7c
	
				 | 
					
					
						|||
| 
						
						
							
						
						c8e5d0eb37
	
				 | 
					
					
						|||
| 2bf8ad5d6c | |||
| 11698a52e3 | |||
| 70955573e8 | |||
| 
						 | 
					3df4043eb9 | ||
| 
						
						
							
						
						06e8264dde
	
				 | 
					
					
						|||
| b451d2c4a3 | |||
| 4f93150874 | |||
| 0566ab0910 | |||
| 
						 | 
					f4eeee1598 | ||
| 33cf16fc13 | |||
| 0a331aab37 | |||
| d43b739654 | |||
| c72432efae | |||
| 
						
						
							
						
						95975fae55
	
				 | 
					
					
						|||
| 
						
						
							
						
						95a7efa138
	
				 | 
					
					
						|||
| 
						
						
							
						
						45e193ff6d
	
				 | 
					
					
						|||
| 
						
						
							
						
						dfc146ff3f
	
				 | 
					
					
						|||
| b41fcf66a9 | |||
| 
						 | 
					a8dd1b3548 | ||
| 
						
						
							
						
						2b99a480ac
	
				 | 
					
					
						|||
| 
						
						
							
						
						7633e587bb
	
				 | 
					
					
						|||
| 
						
						
							
						
						fc61dfdf3a
	
				 | 
					
					
						|||
| 
						
						
							
						
						f1a5b5c49e
	
				 | 
					
					
						|||
| 
						
						
							
						
						ec685dcd47
	
				 | 
					
					
						|||
| 
						
						
							
						
						631ae3eedd
	
				 | 
					
					
						|||
| 
						
						
							
						
						440a7837ac
	
				 | 
					
					
						|||
| 
						
						
							
						
						e0abf34784
	
				 | 
					
					
						|||
| 
						
						
							
						
						377ae9a9dc
	
				 | 
					
					
						|||
| 
						
						
							
						
						034dc30e30
	
				 | 
					
					
						|||
| 
						
						
							
						
						d615111a0f
	
				 | 
					
					
						|||
| 
						
						
							
						
						ffb756c712
	
				 | 
					
					
						|||
| 
						
						
							
						
						69daccb860
	
				 | 
					
					
						|||
| 
						
						
							
						
						16435423cf
	
				 | 
					
					
						|||
| 
						
						
							
						
						697b4ab436
	
				 | 
					
					
						|||
| 
						
						
							
						
						67d804e28e
	
				 | 
					
					
						|||
| 
						
						
							
						
						cf41fa9574
	
				 | 
					
					
						|||
| 
						
						
							
						
						b8b325f7d7
	
				 | 
					
					
						|||
| 
						
						
							
						
						e97bd8c4ef
	
				 | 
					
					
						|||
| 
						
						
							
						
						e28d7df533
	
				 | 
					
					
						|||
| 
						
						
							
						
						4b20b1bc01
	
				 | 
					
					
						|||
| 
						
						
							
						
						b15733076c
	
				 | 
					
					
						|||
| 
						
						
							
						
						25be5c9ea3
	
				 | 
					
					
						|||
| 
						
						
							
						
						b035020c6f
	
				 | 
					
					
						|||
| 
						
						
							
						
						128101dc46
	
				 | 
					
					
						|||
| 
						
						
							
						
						5f2711023e
	
				 | 
					
					
						|||
| 
						
						
							
						
						bdf2ed4bbd
	
				 | 
					
					
						|||
| 
						
						
							
						
						1df542603e
	
				 | 
					
					
						|||
| 
						
						
							
						
						80bcc68ce5
	
				 | 
					
					
						|||
| 
						
						
							
						
						154fc3e2f6
	
				 | 
					
					
						|||
| 
						
						
							
						
						e45af94c78
	
				 | 
					
					
						|||
| 
						
						
							
						
						166a6fde20
	
				 | 
					
					
						|||
| 
						
						
							
						
						631f047338
	
				 | 
					
					
						|||
| 
						
						
							
						
						a777588bb8
	
				 | 
					
					
						|||
| 
						
						
							
						
						ca78d112c2
	
				 | 
					
					
						|||
| 
						
						
							
						
						bcfd317d83
	
				 | 
					
					
						|||
| 
						
						
							
						
						348740f073
	
				 | 
					
					
						|||
| 
						
						
							
						
						0d74f0980f
	
				 | 
					
					
						|||
| 
						
						
							
						
						be19dc00db
	
				 | 
					
					
						|||
| 
						
						
							
						
						643028ffd6
	
				 | 
					
					
						|||
| 
						
						
							
						
						ac4e2e5bf2
	
				 | 
					
					
						|||
| 
						
						
							
						
						498572b96e
	
				 | 
					
					
						|||
| 
						
						
							
						
						d2a61ce69b
	
				 | 
					
					
						|||
| 
						
						
							
						
						a9c0567ee1
	
				 | 
					
					
						|||
| 
						
						
							
						
						76cec5b5a8
	
				 | 
					
					
						|||
| 
						
						
							
						
						efe8a67697
	
				 | 
					
					
						|||
| 
						
						
							
						
						26dfa9b028
	
				 | 
					
					
						|||
| 
						
						
							
						
						50025044d3
	
				 | 
					
					
						|||
| 
						
						
							
						
						e6202a2e34
	
				 | 
					
					
						|||
| 
						
						
							
						
						b863bd967d
	
				 | 
					
					
						|||
| 
						
						
							
						
						e65bcf7275
	
				 | 
					
					
						|||
| 
						
						
							
						
						e00ece4200
	
				 | 
					
					
						|||
| 
						
						
							
						
						640fd71402
	
				 | 
					
					
						|||
| 
						
						
							
						
						aae50ca290
	
				 | 
					
					
						|||
| 
						
						
							
						
						1fa483598b
	
				 | 
					
					
						|||
| 
						
						
							
						
						e4b6a468f8
	
				 | 
					
					
						|||
| 
						 | 
					66c7758023 | ||
| 
						 | 
					4750d2c24e | ||
| 
						 | 
					ca05e3d979 | ||
| 
						 | 
					a20f9b4f86 | ||
| 
						 | 
					c73c1eb8d5 | ||
| 
						 | 
					8778bb0731 | ||
| 
						 | 
					c7d20eebc5 | ||
| 
						 | 
					b9e130c159 | ||
| 
						 | 
					3e8bc94af3 | ||
| 
						 | 
					0c914c9f9f | ||
| 
						 | 
					580a60c939 | ||
| 
						 | 
					4996ac3b7c | ||
| 
						 | 
					2a23bf19cb | ||
| 
						 | 
					650d2596d9 | ||
| 
						 | 
					2bdd5a329e | ||
| 
						
						
							
						
						78d1776733
	
				 | 
					
					
						|||
| 
						
						
							
						
						66dc603c85
	
				 | 
					
					
						|||
| 
						
						
							
						
						3a8154ecce
	
				 | 
					
					
						|||
| 
						
						
							
						
						c81828e04f
	
				 | 
					
					
						|||
| 
						 | 
					ec17dd7de2 | ||
| 76c076a5f3 | |||
| 
						 | 
					f0045edd6c | ||
| 
						 | 
					d00b76ffcd | ||
| 
						 | 
					8991f0ef3f | ||
| 
						 | 
					d6f5eae0c9 | ||
| 
						 | 
					821fce3dd8 | ||
| 
						 | 
					1d33ae1e39 | ||
| 
						 | 
					19af0feb57 | ||
| 
						 | 
					1c09e9a692 | ||
| 
						 | 
					d72e748388 | ||
| 
						 | 
					ab850b7b70 | ||
| 
						 | 
					3f9745d8cf | ||
| 
						 | 
					473765366a | ||
| 
						 | 
					6500c24a7f | ||
| 
						 | 
					1d00457141 | ||
| 
						 | 
					eb0bf56cff | ||
| 
						 | 
					7b8cd90cf1 | ||
| 
						 | 
					a27d92aba0 | ||
| 
						 | 
					85bdfb9e21 | ||
| 
						 | 
					4cffcf4de1 | ||
| 
						 | 
					b2587a688f | ||
| 
						 | 
					c9f0e9843b | ||
| 
						 | 
					b40ad9e445 | ||
| 
						 | 
					3e10e47e29 | ||
| 
						 | 
					2a1963e993 | ||
| 34c171659b | |||
| 
						
						
							
						
						2d8b960d9e
	
				 | 
					
					
						|||
| 831ae03431 | |||
| 
						
						
							
						
						45828174d1
	
				 | 
					
					
						|||
| 
						
						
							
						
						ed45f14a45
	
				 | 
					
					
						|||
| 
						
						
							
						
						fa67835690
	
				 | 
					
					
						|||
| 
						
						
							
						
						b434d38091
	
				 | 
					
					
						|||
| 
						 | 
					800a952532 | ||
| 
						
						
							
						
						9f355032a8
	
				 | 
					
					
						|||
| 
						
						
							
						
						0bc6e62d4d
	
				 | 
					
					
						|||
| 
						
						
							
						
						46fb1c04b5
	
				 | 
					
					
						|||
| 3b2c3d1464 | |||
| 
						 | 
					0bd6038160 | ||
| 
						 | 
					baab8e94ce | ||
| 
						
						
							
						
						e2deb55fdb
	
				 | 
					
					
						|||
| 
						 | 
					2cdfb50058 | ||
| 
						
						
							
						
						39d701feb2
	
				 | 
					
					
						|||
| 
						
						
							
						
						613ee8b186
	
				 | 
					
					
						|||
| 
						
						
							
						
						56a1a488de
	
				 | 
					
					
						|||
| 3f789ad0f4 | |||
| 
						
						
							
						
						467bea7cde
	
				 | 
					
					
						|||
| 
						
						
							
						
						670b8eb82b
	
				 | 
					
					
						|||
| 
						
						
							
						
						a9760b323f
	
				 | 
					
					
						|||
| 
						
						
							
						
						71a3a1924a
	
				 | 
					
					
						|||
| 
						
						
							
						
						ecdc1e25bf
	
				 | 
					
					
						|||
| 
						
						
							
						
						dd37427be1
	
				 | 
					
					
						|||
| 
						
						
							
						
						c8467df1b1
	
				 | 
					
					
						|||
| 
						
						
							
						
						4c89a954fa
	
				 | 
					
					
						|||
| 
						
						
							
						
						7c1f3b114d
	
				 | 
					
					
						|||
| 
						
						
							
						
						36bc4dab24
	
				 | 
					
					
						|||
| 
						
						
							
						
						4b30d92282
	
				 | 
					
					
						|||
| 
						
						
							
						
						75fbec5489
	
				 | 
					
					
						|||
| 
						
						
							
						
						912fdd6349
	
				 | 
					
					
						|||
| 
						
						
							
						
						5832542978
	
				 | 
					
					
						|||
| 
						
						
							
						
						5c3585a1ed
	
				 | 
					
					
						|||
| 
						
						
							
						
						a2f1e20ddf
	
				 | 
					
					
						|||
| 
						
						
							
						
						4d67702a76
	
				 | 
					
					
						|||
| 18e442db29 | |||
| 
						 | 
					deb3d92189 | ||
| 
						
						
							
						
						a59ea7db31
	
				 | 
					
					
						|||
| 
						
						
							
						
						a738b0cac9
	
				 | 
					
					
						
@@ -1,7 +0,0 @@
 | 
			
		||||
kind: DX
 | 
			
		||||
body: |
 | 
			
		||||
    Send notifications log to dedicated channel, if it exists
 | 
			
		||||
time: 2025-10-27T15:00:53.309372316+01:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: ""
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20240530-160003.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20240530-160003.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: |
 | 
			
		||||
  Upgrade import of address list to the last version of compiled addresses of belgian-best-address
 | 
			
		||||
time: 2024-05-30T16:00:03.440767606+02:00
 | 
			
		||||
custom:
 | 
			
		||||
  Issue: ""
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20240531-190242.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20240531-190242.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: |
 | 
			
		||||
  Upgrade CKEditor and refactor configuration with use of typescript
 | 
			
		||||
time: 2024-05-31T19:02:42.776662753+02:00
 | 
			
		||||
custom:
 | 
			
		||||
  Issue: ""
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20250904-181032.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20250904-181032.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: Add a command to generate a list of permissions
 | 
			
		||||
time: 2025-09-04T18:10:32.334524026+02:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: ""
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
kind: Fixed
 | 
			
		||||
body: 'Fix: display also social actions linked to parents of the selected social issue'
 | 
			
		||||
time: 2025-10-29T12:43:55.008647232+01:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: "451"
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
@@ -1,10 +0,0 @@
 | 
			
		||||
## v4.3.0 - 2025-09-08
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges   
 | 
			
		||||
* Add a command to generate a list of permissions   
 | 
			
		||||
* ([#412](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/412)) Add an absence end date   
 | 
			
		||||
 | 
			
		||||
  **Schema Change**: Add columns or tables
 | 
			
		||||
### Fixed
 | 
			
		||||
* fix date formatting in calendar range display   
 | 
			
		||||
* Change route URL to avoid clash with person duplicate controller method   
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
## v4.4.0 - 2025-09-11
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works   
 | 
			
		||||
* ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation   
 | 
			
		||||
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works   
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix display of 'duplicate' and 'merge' buttons in CRUD templates   
 | 
			
		||||
* Fix saving notification preferences in user's profile   
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
## v4.4.1 - 2025-09-11
 | 
			
		||||
### Fixed
 | 
			
		||||
* fix translations in duplicate evaluation document modal and realign close modal button   
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
## v4.4.2 - 2025-09-12
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix document generation and workflow generation do not work on accompanying period work documents   
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
## v4.5.0 - 2025-10-03
 | 
			
		||||
### Feature
 | 
			
		||||
* Only allow delete of attachment on workflows that are not final   
 | 
			
		||||
* Move up signature buttons on index workflow page for easier access   
 | 
			
		||||
* Filter out document from attachment list if it is the same as the workflow document   
 | 
			
		||||
* Block edition on attached document on workflow, if the workflow is finalized or sent external   
 | 
			
		||||
* Convert workflow's attached document to pdf while sending them external   
 | 
			
		||||
* After a signature is canceled or rejected, going to a waiting page until the post-process routines apply a workflow transition   
 | 
			
		||||
### Fixed
 | 
			
		||||
* ([#426](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/426)) Increased the number of required characters when setting a new password in Chill from 9 to 14 - GDPR compliance   
 | 
			
		||||
* Fix permissions on storedObject which are subject by a workflow   
 | 
			
		||||
### DX
 | 
			
		||||
* Introduce a WaitingScreen component to display a waiting screen   
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
## v4.5.1 - 2025-10-03
 | 
			
		||||
### Fixed
 | 
			
		||||
* Add missing javascript dependency   
 | 
			
		||||
* Add exception handling for conversion of attachment on sending external, when documens are already in pdf   
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
## v4.6.0 - 2025-10-15
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#423](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/423)) Create environment banner that can be activated and configured depending on the image deployed   
 | 
			
		||||
* ([#394](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/394)) Only show active workflow on the page "my tracked workflow"   
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix loading of classLists in SocialIssuesAcc.vue, ensure elements are present   
 | 
			
		||||
* Fix the rendering of list of StoredObjectVersions, where there are kept version (before converting to pdf) and intermediate versions deleted   
 | 
			
		||||
* ([#434](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/434)) Notification: fix editing of sent notification by removing form.addressesEmails, a field that no longer exists   
 | 
			
		||||
* Fix loading of social issues and social actions within vue component   
 | 
			
		||||
* ([#446](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/446)) Add unique condition on stored object filename, with cleaning step on existing duplicate filenames   
 | 
			
		||||
 | 
			
		||||
  **Schema Change**: Drop or rename table or columns, or enforce new constraint that must be manually fixed
 | 
			
		||||
* [workflow] take permissions into account to delete the workflow attachment   
 | 
			
		||||
* ([#448](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/448)) Fix the execution of daily cronjob notification, when the previous last execution storage was invalid   
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
## v4.6.1 - 2025-10-27
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix export case where no 'reason' is picked within the PersonHavingActivityBetweenDateFilter.php   
 | 
			
		||||
@@ -19,11 +19,11 @@ max_line_length = 80
 | 
			
		||||
[COMMIT_EDITMSG]
 | 
			
		||||
max_line_length = 0
 | 
			
		||||
 | 
			
		||||
[*.{js, vue, ts}]
 | 
			
		||||
[*.{js,vue,ts}]
 | 
			
		||||
indent_size = 2
 | 
			
		||||
indent_style = space
 | 
			
		||||
 | 
			
		||||
[.rst]
 | 
			
		||||
ident_size = 3
 | 
			
		||||
ident_style = space
 | 
			
		||||
[*.rst]
 | 
			
		||||
indent_size = 3
 | 
			
		||||
indent_style = space
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -234,12 +234,9 @@ This must be a decision made by a human, not by an AI. Every AI task must abort
 | 
			
		||||
 | 
			
		||||
#### Running Tests
 | 
			
		||||
 | 
			
		||||
The tests are run from the project's root (not from the bundle's root).
 | 
			
		||||
The tests are run from the project's root (not from the bundle's root: so, do not change the directory to any bundle directory before running tests).
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Run all tests
 | 
			
		||||
vendor/bin/phpunit
 | 
			
		||||
 | 
			
		||||
# Run a specific test file
 | 
			
		||||
vendor/bin/phpunit path/to/TestFile.php
 | 
			
		||||
 | 
			
		||||
@@ -247,9 +244,6 @@ vendor/bin/phpunit path/to/TestFile.php
 | 
			
		||||
vendor/bin/phpunit --filter methodName path/to/TestFile.php
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
When writing tests, only test specific files. Do not run all tests or the full
 | 
			
		||||
test suite.
 | 
			
		||||
 | 
			
		||||
#### Test Structure
 | 
			
		||||
 | 
			
		||||
Tests are organized by bundle and follow the same structure as the bundle itself:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
{
 | 
			
		||||
  "tabWidth": 2,
 | 
			
		||||
  "useTabs": false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    // Use IntelliSense to learn about possible attributes.
 | 
			
		||||
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
 | 
			
		||||
    "version": "0.2.0",
 | 
			
		||||
    "configurations": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "Chill Debug",
 | 
			
		||||
            "type": "php",
 | 
			
		||||
            "request": "launch",
 | 
			
		||||
            "port": 9000,
 | 
			
		||||
            "pathMappings": {
 | 
			
		||||
                "/var/www/html": "${workspaceFolder}"
 | 
			
		||||
            },
 | 
			
		||||
            "preLaunchTask": "symfony"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "Yarn Encore Dev (Watch)",
 | 
			
		||||
            "type": "node-terminal",
 | 
			
		||||
            "request": "launch",
 | 
			
		||||
            "command": "yarn encore dev --watch",
 | 
			
		||||
            "cwd": "${workspaceFolder}"
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "compounds": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "Chill Debug + Yarn Encore Dev (Watch)",
 | 
			
		||||
            "configurations": ["Chill Debug", "Yarn Encore Dev (Watch)"]
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
{
 | 
			
		||||
    "tasks": [
 | 
			
		||||
        {
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "symfony",
 | 
			
		||||
            "args": [
 | 
			
		||||
                "server:start",
 | 
			
		||||
                "--allow-http",
 | 
			
		||||
                "--no-tls",
 | 
			
		||||
                "--port=8000",
 | 
			
		||||
                "--allow-all-ip",
 | 
			
		||||
                "-d"
 | 
			
		||||
            ],
 | 
			
		||||
            "label": "symfony"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "yarn",
 | 
			
		||||
            "args": ["encore", "dev", "--watch"],
 | 
			
		||||
            "label": "webpack"
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -6,72 +6,6 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
 | 
			
		||||
and is generated by [Changie](https://github.com/miniscruff/changie).
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## v4.6.1 - 2025-10-27
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix export case where no 'reason' is picked within the PersonHavingActivityBetweenDateFilter.php   
 | 
			
		||||
 | 
			
		||||
## v4.6.0 - 2025-10-15
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#423](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/423)) Create environment banner that can be activated and configured depending on the image deployed   
 | 
			
		||||
* ([#394](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/394)) Only show active workflow on the page "my tracked workflow"   
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix loading of classLists in SocialIssuesAcc.vue, ensure elements are present   
 | 
			
		||||
* Fix the rendering of list of StoredObjectVersions, where there are kept version (before converting to pdf) and intermediate versions deleted   
 | 
			
		||||
* ([#434](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/434)) Notification: fix editing of sent notification by removing form.addressesEmails, a field that no longer exists   
 | 
			
		||||
* Fix loading of social issues and social actions within vue component   
 | 
			
		||||
* ([#446](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/446)) Add unique condition on stored object filename, with cleaning step on existing duplicate filenames   
 | 
			
		||||
 | 
			
		||||
  **Schema Change**: Drop or rename table or columns, or enforce new constraint that must be manually fixed
 | 
			
		||||
* [workflow] take permissions into account to delete the workflow attachment   
 | 
			
		||||
* ([#448](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/448)) Fix the execution of daily cronjob notification, when the previous last execution storage was invalid   
 | 
			
		||||
 | 
			
		||||
## v4.5.1 - 2025-10-03
 | 
			
		||||
### Fixed
 | 
			
		||||
* Add missing javascript dependency   
 | 
			
		||||
* Add exception handling for conversion of attachment on sending external, when documens are already in pdf   
 | 
			
		||||
 | 
			
		||||
## v4.5.0 - 2025-10-03
 | 
			
		||||
### Feature
 | 
			
		||||
* Only allow delete of attachment on workflows that are not final   
 | 
			
		||||
* Move up signature buttons on index workflow page for easier access   
 | 
			
		||||
* Filter out document from attachment list if it is the same as the workflow document   
 | 
			
		||||
* Block edition on attached document on workflow, if the workflow is finalized or sent external   
 | 
			
		||||
* Convert workflow's attached document to pdf while sending them external   
 | 
			
		||||
* After a signature is canceled or rejected, going to a waiting page until the post-process routines apply a workflow transition   
 | 
			
		||||
### Fixed
 | 
			
		||||
* ([#426](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/426)) Increased the number of required characters when setting a new password in Chill from 9 to 14 - GDPR compliance   
 | 
			
		||||
* Fix permissions on storedObject which are subject by a workflow   
 | 
			
		||||
### DX
 | 
			
		||||
* Introduce a WaitingScreen component to display a waiting screen   
 | 
			
		||||
 | 
			
		||||
## v4.4.2 - 2025-09-12
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix document generation and workflow generation do not work on accompanying period work documents   
 | 
			
		||||
 | 
			
		||||
## v4.4.1 - 2025-09-11
 | 
			
		||||
### Fixed
 | 
			
		||||
* fix translations in duplicate evaluation document modal and realign close modal button   
 | 
			
		||||
 | 
			
		||||
## v4.4.0 - 2025-09-11
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works   
 | 
			
		||||
* ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation   
 | 
			
		||||
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works   
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix display of 'duplicate' and 'merge' buttons in CRUD templates   
 | 
			
		||||
* Fix saving notification preferences in user's profile   
 | 
			
		||||
 | 
			
		||||
## v4.3.0 - 2025-09-08
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges   
 | 
			
		||||
* Add a command to generate a list of permissions   
 | 
			
		||||
* ([#412](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/412)) Add an absence end date   
 | 
			
		||||
 | 
			
		||||
  **Schema Change**: Add columns or tables
 | 
			
		||||
### Fixed
 | 
			
		||||
* fix date formatting in calendar range display   
 | 
			
		||||
* Change route URL to avoid clash with person duplicate controller method   
 | 
			
		||||
 | 
			
		||||
## v4.2.1 - 2025-09-03
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix exports to work with DirectExportInterface   
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ Arborescence:
 | 
			
		||||
    - person
 | 
			
		||||
    - personvendee
 | 
			
		||||
    - household_edit_metadata
 | 
			
		||||
        - index.js
 | 
			
		||||
        - index.ts
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Organisation des feuilles de styles
 | 
			
		||||
 
 | 
			
		||||
@@ -133,6 +133,7 @@
 | 
			
		||||
            "Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
 | 
			
		||||
            "Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
 | 
			
		||||
            "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
 | 
			
		||||
            "Chill\\TicketBundle\\": "src/Bundle/ChillTicketBundle/src",
 | 
			
		||||
            "Chill\\Utils\\Rector\\": "utils/rector/src"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ return [
 | 
			
		||||
    Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true],
 | 
			
		||||
    Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true],
 | 
			
		||||
    Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
 | 
			
		||||
    Chill\TicketBundle\ChillTicketBundle::class => ['all' => true],
 | 
			
		||||
    Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
 | 
			
		||||
    Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
 | 
			
		||||
];
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,6 @@
 | 
			
		||||
chill_main:
 | 
			
		||||
    available_languages: [ '%env(resolve:LOCALE)%', 'en' ]
 | 
			
		||||
    available_countries: ['BE', 'FR']
 | 
			
		||||
    top_banner:
 | 
			
		||||
        visible: false
 | 
			
		||||
        text:
 | 
			
		||||
            fr: 'Vous travaillez actuellement avec la version de PRÉ-PRODUCTION.'
 | 
			
		||||
            nl: 'Je werkt momenteel in de PRE-PRODUCTIE versie'
 | 
			
		||||
        color: '#353535'
 | 
			
		||||
        background_color: '#d8bb48'
 | 
			
		||||
    notifications:
 | 
			
		||||
        from_email: '%env(resolve:NOTIFICATION_FROM_EMAIL)%'
 | 
			
		||||
        from_name: '%env(resolve:NOTIFICATION_FROM_NAME)%'
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
chill_doc_store:
 | 
			
		||||
    use_driver: openstack
 | 
			
		||||
    use_driver: local_storage
 | 
			
		||||
    local_storage:
 | 
			
		||||
        storage_path: '%kernel.project_dir%/var/storage'
 | 
			
		||||
    openstack:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								config/packages/chill_ticket.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								config/packages/chill_ticket.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
chill_ticket:
 | 
			
		||||
    ticket:
 | 
			
		||||
        person_per_ticket:    one # One of "one"; "many"
 | 
			
		||||
        response_time_exceeded_delay: PT12H
 | 
			
		||||
 | 
			
		||||
@@ -14,6 +14,7 @@ doctrine_migrations:
 | 
			
		||||
        'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations'
 | 
			
		||||
        'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations'
 | 
			
		||||
        'Chill\Migrations\Report': '@ChillReportBundle/migrations'
 | 
			
		||||
        'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations'
 | 
			
		||||
    all_or_nothing:
 | 
			
		||||
        true
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								config/routes/chill_ticket.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								config/routes/chill_ticket.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
chill_ticket_bundle:
 | 
			
		||||
    resource: '@ChillTicketBundle/config/routes.yaml'
 | 
			
		||||
@@ -11,24 +11,94 @@
 | 
			
		||||
Create a new bundle
 | 
			
		||||
*******************
 | 
			
		||||
 | 
			
		||||
Create your own bundle is not a trivial task.
 | 
			
		||||
 | 
			
		||||
The easiest way to achieve this is seems to be : 
 | 
			
		||||
 | 
			
		||||
1. Prepare a fresh installation of the chill project, in a new directory
 | 
			
		||||
2. Create a new bundle in this project, in the src directory
 | 
			
		||||
3. Initialize a git repository **at the root bundle**, and create your initial commit.
 | 
			
		||||
4. Register the bundle with composer/packagist. If you do not plan to distribute your bundle with packagist, you may use a custom repository for achieve this [#f1]_
 | 
			
		||||
5. Move to a development installation, made as described in the :ref:`installation-for-development` section, and add your new repository to the composer.json file
 | 
			
		||||
6. Work as :ref:`usual <editing-code-and-commiting>`
 | 
			
		||||
 | 
			
		||||
.. warning::
 | 
			
		||||
 | 
			
		||||
    This part of the doc is not yet tested
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
Create a new directory with Bundle class
 | 
			
		||||
----------------------------------------
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
 | 
			
		||||
   mkdir -p src/Bundle/ChillSomeBundle/src/config
 | 
			
		||||
   mkdir -p src/Bundle/ChillSomeBundle/src/Controller
 | 
			
		||||
 | 
			
		||||
Add a bundle file
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
    <?php
 | 
			
		||||
 | 
			
		||||
    declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 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.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    namespace Chill\SomeBundle;
 | 
			
		||||
 | 
			
		||||
    use Symfony\Component\HttpKernel\Bundle\Bundle;
 | 
			
		||||
 | 
			
		||||
    class ChillSomeBundle extends Bundle {}
 | 
			
		||||
 | 
			
		||||
And a route file:
 | 
			
		||||
 | 
			
		||||
.. code-block:: yaml
 | 
			
		||||
 | 
			
		||||
   chill_ticket_controller:
 | 
			
		||||
       resource: '@ChillTicketBundle/Controller/'
 | 
			
		||||
       type: annotation
 | 
			
		||||
 | 
			
		||||
Register the new psr-4 namespace
 | 
			
		||||
--------------------------------
 | 
			
		||||
 | 
			
		||||
In composer.json, add the new psr4 namespace
 | 
			
		||||
 | 
			
		||||
.. code-block:: diff
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
    "autoload": {
 | 
			
		||||
        "psr-4": {
 | 
			
		||||
    +        "Chill\\SomeBundle\\": "src/Bundle/ChillSomeBundle/src",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. rubric:: Footnotes
 | 
			
		||||
Register the bundle
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
Register in the file :code:`config/bundles.php`:
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
    Vendor\Bundle\YourBundle\YourBundle::class => ['all' => true],
 | 
			
		||||
 | 
			
		||||
And import routes in :code:`config/routes/chill_some_bundle.yaml`:
 | 
			
		||||
 | 
			
		||||
.. code-block:: yaml
 | 
			
		||||
 | 
			
		||||
   chill_ticket_bundle:
 | 
			
		||||
       resource: '@ChillSomeBundle/config/routes.yaml'
 | 
			
		||||
 | 
			
		||||
Add the doctrine_migration namespace
 | 
			
		||||
------------------------------------
 | 
			
		||||
 | 
			
		||||
Add the namespace to :code:`config/packages/doctrine_migrations_chill.yaml`
 | 
			
		||||
 | 
			
		||||
.. code-block:: diff
 | 
			
		||||
 | 
			
		||||
   doctrine_migrations:
 | 
			
		||||
       migrations_paths:
 | 
			
		||||
   +        'Chill\Some\Ticket': '@ChillSomeBundle/migrations'
 | 
			
		||||
 | 
			
		||||
Dump autoloading
 | 
			
		||||
----------------
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
 | 
			
		||||
   symfony composer dump-autoload
 | 
			
		||||
 | 
			
		||||
.. [#f1] Be aware that we use the Affero GPL Licence, which ensure that all users must have access to derivative works done with this software.
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,6 @@
 | 
			
		||||
    "@tsconfig/node20": "^20.1.4",
 | 
			
		||||
    "@types/dompurify": "^3.0.5",
 | 
			
		||||
    "@types/leaflet": "^1.9.3",
 | 
			
		||||
    "@vueuse/core": "^13.9.0",
 | 
			
		||||
    "bootstrap-icons": "^1.11.3",
 | 
			
		||||
    "dropzone": "^5.7.6",
 | 
			
		||||
    "es6-promise": "^4.2.8",
 | 
			
		||||
@@ -80,12 +79,12 @@
 | 
			
		||||
    "dev": "encore dev",
 | 
			
		||||
    "watch": "encore dev --watch",
 | 
			
		||||
    "build": "encore production --progress",
 | 
			
		||||
    "specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml",
 | 
			
		||||
    "specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml src/Bundle/ChillTicketBundle/chill.api.specs.yaml> templates/api/specs.yaml",
 | 
			
		||||
    "specs-validate": "swagger-cli validate templates/api/specs.yaml",
 | 
			
		||||
    "specs-create-dir": "mkdir -p templates/api",
 | 
			
		||||
    "specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
 | 
			
		||||
    "version": "node --version",
 | 
			
		||||
    "eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
 | 
			
		||||
    "eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
 | 
			
		||||
  },
 | 
			
		||||
  "private": true
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,10 @@
 | 
			
		||||
      <!-- temporarily removed, the time to find a fix -->
 | 
			
		||||
      <exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
 | 
			
		||||
    </testsuite>
 | 
			
		||||
 | 
			
		||||
    <testsuite name="TicketBundle">
 | 
			
		||||
      <directory suffix="Test.php">src/Bundle/ChillTicketBundle/tests/</directory>
 | 
			
		||||
    </testsuite>
 | 
			
		||||
    <!--
 | 
			
		||||
        <testsuite name="ReportBundle">
 | 
			
		||||
            <directory suffix="Test.php">src/Bundle/ChillReportBundle/Tests/</directory>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								resources/ticket_motives_import/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								resources/ticket_motives_import/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
In this directory, you find an example of file for the command `chill:main:ticket_motives_import`.
 | 
			
		||||
 | 
			
		||||
This file contains a list of ticket motives to import into the system. Each entry is a dictionary with two keys: `code` and `label`. The `code` key contains the unique code for the ticket motive, and the `label` key contains the human-readable label for the ticket motive.
 | 
			
		||||
 | 
			
		||||
The `stored_objects` key contains the documents that will be associated with the tickets. They must be found in the same directory.
 | 
			
		||||
 | 
			
		||||
The command `chill:main:ticket_motives_import` uses this file to import the specified ticket motives into the system.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										136
									
								
								resources/ticket_motives_import/motives.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								resources/ticket_motives_import/motives.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
			
		||||
- label:
 | 
			
		||||
    fr: Appel famille pour annonce de décès
 | 
			
		||||
  urgent: false
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Date du décès
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: lieu du décès (domicile ou hôpital)
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: nom de l’hôpital
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: service concerné
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 2_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 3_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 4_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: 'Appel famille pour annonce absence : hospitalisation ou consultation'
 | 
			
		||||
  urgent: false
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Quel hôpital
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: quel service
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: pour quelles raisons
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 'consultation : date et heure'
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: hospitalisation complète ou HDJ
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 5_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 6_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 7_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: 'Appel famille pour annonce absence : interruption de prise en charge'
 | 
			
		||||
  urgent: false
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Pour quelles raisons ? Date
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: durée
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: accord médical ?
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 8_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 9_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 10_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: 'Appel famille pour annonce absence : changement d’adresse'
 | 
			
		||||
  urgent: false
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Où
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Pourquoi ? Pour combien de temps ? Besoin d’un relais des soins ? Nouvelle adresse ?
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 11_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 12_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 13_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: Appel famille pour altération de l’état général du patient
 | 
			
		||||
  urgent: true
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Recherche des symptômes
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Attentes par rapport à la demande
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 14_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 15_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 16_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: Appel famille pour prise en charge de la douleur
 | 
			
		||||
  urgent: true
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Localisation douleur
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Horaire dernier passage
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Traitements en cours
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 17_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 18_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 19_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: Appel famille pour information sur la date de prise en charge
 | 
			
		||||
  urgent: false
 | 
			
		||||
  supplementary_informations: []
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 20_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 21_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 22_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
							
								
								
									
										6
									
								
								resources/translation_override/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								resources/translation_override/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
In this directory, you find an example of file for the command `chill:main:override_translation`.
 | 
			
		||||
 | 
			
		||||
This file contains a list of translations to override in the translation catalogue. Each entry is a dictionary with two keys: `from` and `to`. The `from` key contains the original translation string, and the `to` key contains the replacement string.
 | 
			
		||||
 | 
			
		||||
The command `chill:main:override_translation` uses this file to generate a new translation catalogue with the specified overrides applied.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								resources/translation_override/overrides.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								resources/translation_override/overrides.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
- {from: "de l'usager", to: "du patient"}
 | 
			
		||||
- {from: "l'usager", to: "le patient"}
 | 
			
		||||
- {from: "L'usager", to: "Le patient"}
 | 
			
		||||
- {from: "d'usagers", to: "de patients"}
 | 
			
		||||
- {from: "usagers", to: "patients"}
 | 
			
		||||
- {from: "Usagers", to: "Patients"}
 | 
			
		||||
- {from: "usager", to: "patient"}
 | 
			
		||||
- {from: "Usager", to: "Patient"}
 | 
			
		||||
@@ -90,9 +90,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
 | 
			
		||||
 | 
			
		||||
    public function getFormDefaultData(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            'reasons' => [],
 | 
			
		||||
        ];
 | 
			
		||||
        return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array
 | 
			
		||||
 
 | 
			
		||||
@@ -42,8 +42,6 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
 | 
			
		||||
    public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void
 | 
			
		||||
    {
 | 
			
		||||
        error_log('alterQuery called with data: '.json_encode(array_keys($data)));
 | 
			
		||||
 | 
			
		||||
        // create a subquery for activity
 | 
			
		||||
        $sqb = $qb->getEntityManager()->createQueryBuilder();
 | 
			
		||||
        $sqb->select('1')
 | 
			
		||||
@@ -61,6 +59,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
        if (\in_array('activity', $qb->getAllAliases(), true)) {
 | 
			
		||||
            $sqb->andWhere('activity_person_having_activity.id = activity.id');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isset($data['reasons']) && [] !== $data['reasons']) {
 | 
			
		||||
            // add clause activity reason
 | 
			
		||||
            $sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
 | 
			
		||||
@@ -125,38 +124,12 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
 | 
			
		||||
    public function normalizeFormData(array $formData): array
 | 
			
		||||
    {
 | 
			
		||||
        $normalized = [
 | 
			
		||||
            'date_from_rolling' => $formData['date_from_rolling']->normalize(),
 | 
			
		||||
            'date_to_rolling' => $formData['date_to_rolling']->normalize(),
 | 
			
		||||
            'reasons' => [],
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        if (isset($formData['reasons']) && [] !== $formData['reasons']) {
 | 
			
		||||
            $normalized['reasons'] = array_map(
 | 
			
		||||
                fn (ActivityReason $reason) => $reason->getId(),
 | 
			
		||||
                $formData['reasons']
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $normalized;
 | 
			
		||||
        return ['date_from_rolling' => $formData['date_from_rolling']->normalize(), 'date_to_rolling' => $formData['date_to_rolling']->normalize()];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function denormalizeFormData(array $formData, int $fromVersion): array
 | 
			
		||||
    {
 | 
			
		||||
        $denormalized = [
 | 
			
		||||
            'date_from_rolling' => RollingDate::fromNormalized($formData['date_from_rolling']),
 | 
			
		||||
            'date_to_rolling' => RollingDate::fromNormalized($formData['date_to_rolling']),
 | 
			
		||||
            'reasons' => [],
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        if (isset($formData['reasons']) && [] !== $formData['reasons']) {
 | 
			
		||||
            $denormalized['reasons'] = array_map(
 | 
			
		||||
                fn ($id) => $this->activityReasonRepository->find($id),
 | 
			
		||||
                $formData['reasons']
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $denormalized;
 | 
			
		||||
        return ['date_from_rolling' => RollingDate::fromNormalized($formData['date_from_rolling']), 'date_to_rolling' => RollingDate::fromNormalized($formData['date_to_rolling'])];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getFormDefaultData(): array
 | 
			
		||||
@@ -170,12 +143,10 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
 | 
			
		||||
    public function describeAction($data, ExportGenerationContext $context): array
 | 
			
		||||
    {
 | 
			
		||||
        $reasons = $data['reasons'] ?? [];
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            [] === $reasons ?
 | 
			
		||||
                'export.filter.activity.describe_action_with_no_subject'
 | 
			
		||||
                : 'export.filter.activity.describe_action_with_subject',
 | 
			
		||||
            [] === $data['reasons'] ?
 | 
			
		||||
                'export.filter.person_between_dates.describe_action_with_no_subject'
 | 
			
		||||
                : 'export.filter.person_between_dates.describe_action_with_subject',
 | 
			
		||||
            [
 | 
			
		||||
                'date_from' => $this->rollingDateConverter->convert($data['date_from_rolling']),
 | 
			
		||||
                'date_to' => $this->rollingDateConverter->convert($data['date_to_rolling']),
 | 
			
		||||
@@ -183,7 +154,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
                    ', ',
 | 
			
		||||
                    array_map(
 | 
			
		||||
                        fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"',
 | 
			
		||||
                        $reasons
 | 
			
		||||
                        $data['reasons']
 | 
			
		||||
                    )
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
@@ -197,7 +168,6 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
 | 
			
		||||
    public function validateForm($data, ExecutionContextInterface $context): void
 | 
			
		||||
    {
 | 
			
		||||
        error_log('validateForm called with data: '.json_encode(array_keys($data)));
 | 
			
		||||
        if ($this->rollingDateConverter->convert($data['date_from_rolling'])
 | 
			
		||||
            >= $this->rollingDateConverter->convert($data['date_to_rolling'])) {
 | 
			
		||||
            $context->buildViolation('export.filter.activity.person_between_dates.date mismatch')
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <concerned-groups v-if="hasPerson" />
 | 
			
		||||
    <social-issues-acc v-if="hasSocialIssues" />
 | 
			
		||||
    <location v-if="hasLocation" />
 | 
			
		||||
  <concerned-groups v-if="hasPerson" />
 | 
			
		||||
  <social-issues-acc v-if="hasSocialIssues" />
 | 
			
		||||
  <location v-if="hasLocation" />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -10,12 +10,12 @@ import SocialIssuesAcc from "./components/SocialIssuesAcc.vue";
 | 
			
		||||
import Location from "./components/Location.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "App",
 | 
			
		||||
    props: ["hasSocialIssues", "hasLocation", "hasPerson"],
 | 
			
		||||
    components: {
 | 
			
		||||
        ConcernedGroups,
 | 
			
		||||
        SocialIssuesAcc,
 | 
			
		||||
        Location,
 | 
			
		||||
    },
 | 
			
		||||
  name: "App",
 | 
			
		||||
  props: ["hasSocialIssues", "hasLocation", "hasPerson"],
 | 
			
		||||
  components: {
 | 
			
		||||
    ConcernedGroups,
 | 
			
		||||
    SocialIssuesAcc,
 | 
			
		||||
    Location,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,43 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <teleport to="#add-persons" v-if="isComponentVisible">
 | 
			
		||||
        <div class="flex-bloc concerned-groups" :class="getContext">
 | 
			
		||||
            <persons-bloc
 | 
			
		||||
                v-for="bloc in contextPersonsBlocs"
 | 
			
		||||
                :key="bloc.key"
 | 
			
		||||
                :bloc="bloc"
 | 
			
		||||
                :bloc-width="getBlocWidth"
 | 
			
		||||
                :set-persons-in-bloc="setPersonsInBloc"
 | 
			
		||||
            />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div
 | 
			
		||||
            v-if="
 | 
			
		||||
                getContext === 'accompanyingCourse' &&
 | 
			
		||||
                suggestedEntities.length > 0
 | 
			
		||||
            "
 | 
			
		||||
  <teleport to="#add-persons" v-if="isComponentVisible">
 | 
			
		||||
    <div class="flex-bloc concerned-groups" :class="getContext">
 | 
			
		||||
      <persons-bloc
 | 
			
		||||
        v-for="bloc in contextPersonsBlocs"
 | 
			
		||||
        :key="bloc.key"
 | 
			
		||||
        :bloc="bloc"
 | 
			
		||||
        :bloc-width="getBlocWidth"
 | 
			
		||||
        :set-persons-in-bloc="setPersonsInBloc"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div
 | 
			
		||||
      v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0"
 | 
			
		||||
    >
 | 
			
		||||
      <ul class="list-suggest add-items inline">
 | 
			
		||||
        <li
 | 
			
		||||
          v-for="(p, i) in suggestedEntities"
 | 
			
		||||
          @click="addSuggestedEntity(p)"
 | 
			
		||||
          :key="`suggestedEntities-${i}`"
 | 
			
		||||
        >
 | 
			
		||||
            <ul class="list-suggest add-items inline">
 | 
			
		||||
                <li
 | 
			
		||||
                    v-for="(p, i) in suggestedEntities"
 | 
			
		||||
                    @click="addSuggestedEntity(p)"
 | 
			
		||||
                    :key="`suggestedEntities-${i}`"
 | 
			
		||||
                >
 | 
			
		||||
                    <person-text v-if="p.type === 'person'" :person="p" />
 | 
			
		||||
                    <span v-else>{{ p.text }}</span>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
          <person-text v-if="p.type === 'person'" :person="p" />
 | 
			
		||||
          <span v-else>{{ p.text }}</span>
 | 
			
		||||
        </li>
 | 
			
		||||
      </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
        <ul class="record_actions">
 | 
			
		||||
            <li class="add-persons">
 | 
			
		||||
                <add-persons
 | 
			
		||||
                    :buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
 | 
			
		||||
                    :modalTitle="trans(ACTIVITY_ADD_PERSONS)"
 | 
			
		||||
                    v-bind:key="addPersons.key"
 | 
			
		||||
                    v-bind:options="addPersonsOptions"
 | 
			
		||||
                    @addNewPersons="addNewPersons"
 | 
			
		||||
                    ref="addPersons"
 | 
			
		||||
                >
 | 
			
		||||
                </add-persons>
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </teleport>
 | 
			
		||||
    <ul class="record_actions">
 | 
			
		||||
      <li class="add-persons">
 | 
			
		||||
        <add-persons
 | 
			
		||||
          :buttonTitle="trans(ACTIVITY_ADD_PERSONS)"
 | 
			
		||||
          :modalTitle="trans(ACTIVITY_ADD_PERSONS)"
 | 
			
		||||
          v-bind:key="addPersons.key"
 | 
			
		||||
          v-bind:options="addPersonsOptions"
 | 
			
		||||
          @addNewPersons="addNewPersons"
 | 
			
		||||
          ref="addPersons"
 | 
			
		||||
        >
 | 
			
		||||
        </add-persons>
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
  </teleport>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -49,208 +46,208 @@ import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
 | 
			
		||||
import PersonsBloc from "./ConcernedGroups/PersonsBloc.vue";
 | 
			
		||||
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
 | 
			
		||||
import {
 | 
			
		||||
    ACTIVITY_BLOC_PERSONS,
 | 
			
		||||
    ACTIVITY_BLOC_PERSONS_ASSOCIATED,
 | 
			
		||||
    ACTIVITY_BLOC_THIRDPARTY,
 | 
			
		||||
    ACTIVITY_BLOC_USERS,
 | 
			
		||||
    ACTIVITY_ADD_PERSONS,
 | 
			
		||||
    trans,
 | 
			
		||||
  ACTIVITY_BLOC_PERSONS,
 | 
			
		||||
  ACTIVITY_BLOC_PERSONS_ASSOCIATED,
 | 
			
		||||
  ACTIVITY_BLOC_THIRDPARTY,
 | 
			
		||||
  ACTIVITY_BLOC_USERS,
 | 
			
		||||
  ACTIVITY_ADD_PERSONS,
 | 
			
		||||
  trans,
 | 
			
		||||
} from "translator";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "ConcernedGroups",
 | 
			
		||||
    components: {
 | 
			
		||||
        AddPersons,
 | 
			
		||||
        PersonsBloc,
 | 
			
		||||
        PersonText,
 | 
			
		||||
  name: "ConcernedGroups",
 | 
			
		||||
  components: {
 | 
			
		||||
    AddPersons,
 | 
			
		||||
    PersonsBloc,
 | 
			
		||||
    PersonText,
 | 
			
		||||
  },
 | 
			
		||||
  setup() {
 | 
			
		||||
    return {
 | 
			
		||||
      trans,
 | 
			
		||||
      ACTIVITY_ADD_PERSONS,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      personsBlocs: [
 | 
			
		||||
        {
 | 
			
		||||
          key: "persons",
 | 
			
		||||
          title: trans(ACTIVITY_BLOC_PERSONS),
 | 
			
		||||
          persons: [],
 | 
			
		||||
          included: false,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: "personsAssociated",
 | 
			
		||||
          title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
 | 
			
		||||
          persons: [],
 | 
			
		||||
          included: window.activity
 | 
			
		||||
            ? window.activity.activityType.personsVisible !== 0
 | 
			
		||||
            : true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: "personsNotAssociated",
 | 
			
		||||
          title: "activity.bloc_persons_not_associated",
 | 
			
		||||
          persons: [],
 | 
			
		||||
          included: window.activity
 | 
			
		||||
            ? window.activity.activityType.personsVisible !== 0
 | 
			
		||||
            : true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: "thirdparty",
 | 
			
		||||
          title: trans(ACTIVITY_BLOC_THIRDPARTY),
 | 
			
		||||
          persons: [],
 | 
			
		||||
          included: window.activity
 | 
			
		||||
            ? window.activity.activityType.thirdPartiesVisible !== 0
 | 
			
		||||
            : true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          key: "users",
 | 
			
		||||
          title: trans(ACTIVITY_BLOC_USERS),
 | 
			
		||||
          persons: [],
 | 
			
		||||
          included: window.activity
 | 
			
		||||
            ? window.activity.activityType.usersVisible !== 0
 | 
			
		||||
            : true,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      addPersons: {
 | 
			
		||||
        key: "activity",
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    isComponentVisible() {
 | 
			
		||||
      return window.activity
 | 
			
		||||
        ? window.activity.activityType.personsVisible !== 0 ||
 | 
			
		||||
            window.activity.activityType.thirdPartiesVisible !== 0 ||
 | 
			
		||||
            window.activity.activityType.usersVisible !== 0
 | 
			
		||||
        : true;
 | 
			
		||||
    },
 | 
			
		||||
    setup() {
 | 
			
		||||
        return {
 | 
			
		||||
            trans,
 | 
			
		||||
            ACTIVITY_ADD_PERSONS,
 | 
			
		||||
        };
 | 
			
		||||
    ...mapState({
 | 
			
		||||
      persons: (state) => state.activity.persons,
 | 
			
		||||
      thirdParties: (state) => state.activity.thirdParties,
 | 
			
		||||
      users: (state) => state.activity.users,
 | 
			
		||||
      accompanyingCourse: (state) => state.activity.accompanyingPeriod,
 | 
			
		||||
    }),
 | 
			
		||||
    ...mapGetters(["suggestedEntities"]),
 | 
			
		||||
    getContext() {
 | 
			
		||||
      return this.accompanyingCourse ? "accompanyingCourse" : "person";
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            personsBlocs: [
 | 
			
		||||
                {
 | 
			
		||||
                    key: "persons",
 | 
			
		||||
                    title: trans(ACTIVITY_BLOC_PERSONS),
 | 
			
		||||
                    persons: [],
 | 
			
		||||
                    included: false,
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "personsAssociated",
 | 
			
		||||
                    title: trans(ACTIVITY_BLOC_PERSONS_ASSOCIATED),
 | 
			
		||||
                    persons: [],
 | 
			
		||||
                    included: window.activity
 | 
			
		||||
                        ? window.activity.activityType.personsVisible !== 0
 | 
			
		||||
                        : true,
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "personsNotAssociated",
 | 
			
		||||
                    title: "activity.bloc_persons_not_associated",
 | 
			
		||||
                    persons: [],
 | 
			
		||||
                    included: window.activity
 | 
			
		||||
                        ? window.activity.activityType.personsVisible !== 0
 | 
			
		||||
                        : true,
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "thirdparty",
 | 
			
		||||
                    title: trans(ACTIVITY_BLOC_THIRDPARTY),
 | 
			
		||||
                    persons: [],
 | 
			
		||||
                    included: window.activity
 | 
			
		||||
                        ? window.activity.activityType.thirdPartiesVisible !== 0
 | 
			
		||||
                        : true,
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    key: "users",
 | 
			
		||||
                    title: trans(ACTIVITY_BLOC_USERS),
 | 
			
		||||
                    persons: [],
 | 
			
		||||
                    included: window.activity
 | 
			
		||||
                        ? window.activity.activityType.usersVisible !== 0
 | 
			
		||||
                        : true,
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            addPersons: {
 | 
			
		||||
                key: "activity",
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    contextPersonsBlocs() {
 | 
			
		||||
      return this.personsBlocs.filter((bloc) => bloc.included !== false);
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        isComponentVisible() {
 | 
			
		||||
            return window.activity
 | 
			
		||||
                ? window.activity.activityType.personsVisible !== 0 ||
 | 
			
		||||
                      window.activity.activityType.thirdPartiesVisible !== 0 ||
 | 
			
		||||
                      window.activity.activityType.usersVisible !== 0
 | 
			
		||||
                : true;
 | 
			
		||||
        },
 | 
			
		||||
        ...mapState({
 | 
			
		||||
            persons: (state) => state.activity.persons,
 | 
			
		||||
            thirdParties: (state) => state.activity.thirdParties,
 | 
			
		||||
            users: (state) => state.activity.users,
 | 
			
		||||
            accompanyingCourse: (state) => state.activity.accompanyingPeriod,
 | 
			
		||||
        }),
 | 
			
		||||
        ...mapGetters(["suggestedEntities"]),
 | 
			
		||||
        getContext() {
 | 
			
		||||
            return this.accompanyingCourse ? "accompanyingCourse" : "person";
 | 
			
		||||
        },
 | 
			
		||||
        contextPersonsBlocs() {
 | 
			
		||||
            return this.personsBlocs.filter((bloc) => bloc.included !== false);
 | 
			
		||||
        },
 | 
			
		||||
        addPersonsOptions() {
 | 
			
		||||
            let optionsType = [];
 | 
			
		||||
            if (window.activity) {
 | 
			
		||||
                if (window.activity.activityType.personsVisible !== 0) {
 | 
			
		||||
                    optionsType.push("person");
 | 
			
		||||
                }
 | 
			
		||||
                if (window.activity.activityType.thirdPartiesVisible !== 0) {
 | 
			
		||||
                    optionsType.push("thirdparty");
 | 
			
		||||
                }
 | 
			
		||||
                if (window.activity.activityType.usersVisible !== 0) {
 | 
			
		||||
                    optionsType.push("user");
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                optionsType = ["person", "thirdparty", "user"];
 | 
			
		||||
            }
 | 
			
		||||
            return {
 | 
			
		||||
                type: optionsType,
 | 
			
		||||
                priority: null,
 | 
			
		||||
                uniq: false,
 | 
			
		||||
                button: {
 | 
			
		||||
                    size: "btn-sm",
 | 
			
		||||
                },
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        getBlocWidth() {
 | 
			
		||||
            return Math.round(100 / this.contextPersonsBlocs.length) + "%";
 | 
			
		||||
    addPersonsOptions() {
 | 
			
		||||
      let optionsType = [];
 | 
			
		||||
      if (window.activity) {
 | 
			
		||||
        if (window.activity.activityType.personsVisible !== 0) {
 | 
			
		||||
          optionsType.push("person");
 | 
			
		||||
        }
 | 
			
		||||
        if (window.activity.activityType.thirdPartiesVisible !== 0) {
 | 
			
		||||
          optionsType.push("thirdparty");
 | 
			
		||||
        }
 | 
			
		||||
        if (window.activity.activityType.usersVisible !== 0) {
 | 
			
		||||
          optionsType.push("user");
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        optionsType = ["person", "thirdparty", "user"];
 | 
			
		||||
      }
 | 
			
		||||
      return {
 | 
			
		||||
        type: optionsType,
 | 
			
		||||
        priority: null,
 | 
			
		||||
        uniq: false,
 | 
			
		||||
        button: {
 | 
			
		||||
          size: "btn-sm",
 | 
			
		||||
        },
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.setPersonsInBloc();
 | 
			
		||||
    getBlocWidth() {
 | 
			
		||||
      return Math.round(100 / this.contextPersonsBlocs.length) + "%";
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        setPersonsInBloc() {
 | 
			
		||||
            let groups;
 | 
			
		||||
            if (this.accompanyingCourse) {
 | 
			
		||||
                groups = this.splitPersonsInGroups();
 | 
			
		||||
            }
 | 
			
		||||
            this.personsBlocs.forEach((bloc) => {
 | 
			
		||||
                if (this.accompanyingCourse) {
 | 
			
		||||
                    switch (bloc.key) {
 | 
			
		||||
                        case "personsAssociated":
 | 
			
		||||
                            bloc.persons = groups.personsAssociated;
 | 
			
		||||
                            bloc.included = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        case "personsNotAssociated":
 | 
			
		||||
                            bloc.persons = groups.personsNotAssociated;
 | 
			
		||||
                            bloc.included = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    switch (bloc.key) {
 | 
			
		||||
                        case "persons":
 | 
			
		||||
                            bloc.persons = this.persons;
 | 
			
		||||
                            bloc.included = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                switch (bloc.key) {
 | 
			
		||||
                    case "thirdparty":
 | 
			
		||||
                        bloc.persons = this.thirdParties;
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "users":
 | 
			
		||||
                        bloc.persons = this.users;
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }, groups);
 | 
			
		||||
        },
 | 
			
		||||
        splitPersonsInGroups() {
 | 
			
		||||
            let personsAssociated = [];
 | 
			
		||||
            let personsNotAssociated = this.persons;
 | 
			
		||||
            let participations = this.getCourseParticipations();
 | 
			
		||||
            this.persons.forEach((person) => {
 | 
			
		||||
                participations.forEach((participation) => {
 | 
			
		||||
                    if (person.id === participation.id) {
 | 
			
		||||
                        //console.log(person.id);
 | 
			
		||||
                        personsAssociated.push(person);
 | 
			
		||||
                        personsNotAssociated = personsNotAssociated.filter(
 | 
			
		||||
                            (p) => p !== person,
 | 
			
		||||
                        );
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
            return {
 | 
			
		||||
                personsAssociated: personsAssociated,
 | 
			
		||||
                personsNotAssociated: personsNotAssociated,
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        getCourseParticipations() {
 | 
			
		||||
            let participations = [];
 | 
			
		||||
            this.accompanyingCourse.participations.forEach((participation) => {
 | 
			
		||||
                if (!participation.endDate) {
 | 
			
		||||
                    participations.push(participation.person);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            return participations;
 | 
			
		||||
        },
 | 
			
		||||
        addNewPersons({ selected, modal }) {
 | 
			
		||||
            console.log("@@@ CLICK button addNewPersons", selected);
 | 
			
		||||
            selected.forEach((item) => {
 | 
			
		||||
                this.$store.dispatch("addPersonsInvolved", item);
 | 
			
		||||
            }, this);
 | 
			
		||||
            this.$refs.addPersons.resetSearch(); // to cast child method
 | 
			
		||||
            modal.showModal = false;
 | 
			
		||||
            this.setPersonsInBloc();
 | 
			
		||||
        },
 | 
			
		||||
        addSuggestedEntity(person) {
 | 
			
		||||
            this.$store.dispatch("addPersonsInvolved", {
 | 
			
		||||
                result: person,
 | 
			
		||||
                type: "person",
 | 
			
		||||
            });
 | 
			
		||||
            this.setPersonsInBloc();
 | 
			
		||||
        },
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.setPersonsInBloc();
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    setPersonsInBloc() {
 | 
			
		||||
      let groups;
 | 
			
		||||
      if (this.accompanyingCourse) {
 | 
			
		||||
        groups = this.splitPersonsInGroups();
 | 
			
		||||
      }
 | 
			
		||||
      this.personsBlocs.forEach((bloc) => {
 | 
			
		||||
        if (this.accompanyingCourse) {
 | 
			
		||||
          switch (bloc.key) {
 | 
			
		||||
            case "personsAssociated":
 | 
			
		||||
              bloc.persons = groups.personsAssociated;
 | 
			
		||||
              bloc.included = true;
 | 
			
		||||
              break;
 | 
			
		||||
            case "personsNotAssociated":
 | 
			
		||||
              bloc.persons = groups.personsNotAssociated;
 | 
			
		||||
              bloc.included = true;
 | 
			
		||||
              break;
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          switch (bloc.key) {
 | 
			
		||||
            case "persons":
 | 
			
		||||
              bloc.persons = this.persons;
 | 
			
		||||
              bloc.included = true;
 | 
			
		||||
              break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        switch (bloc.key) {
 | 
			
		||||
          case "thirdparty":
 | 
			
		||||
            bloc.persons = this.thirdParties;
 | 
			
		||||
            break;
 | 
			
		||||
          case "users":
 | 
			
		||||
            bloc.persons = this.users;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
      }, groups);
 | 
			
		||||
    },
 | 
			
		||||
    splitPersonsInGroups() {
 | 
			
		||||
      let personsAssociated = [];
 | 
			
		||||
      let personsNotAssociated = this.persons;
 | 
			
		||||
      let participations = this.getCourseParticipations();
 | 
			
		||||
      this.persons.forEach((person) => {
 | 
			
		||||
        participations.forEach((participation) => {
 | 
			
		||||
          if (person.id === participation.id) {
 | 
			
		||||
            //console.log(person.id);
 | 
			
		||||
            personsAssociated.push(person);
 | 
			
		||||
            personsNotAssociated = personsNotAssociated.filter(
 | 
			
		||||
              (p) => p !== person,
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
      return {
 | 
			
		||||
        personsAssociated: personsAssociated,
 | 
			
		||||
        personsNotAssociated: personsNotAssociated,
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    getCourseParticipations() {
 | 
			
		||||
      let participations = [];
 | 
			
		||||
      this.accompanyingCourse.participations.forEach((participation) => {
 | 
			
		||||
        if (!participation.endDate) {
 | 
			
		||||
          participations.push(participation.person);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      return participations;
 | 
			
		||||
    },
 | 
			
		||||
    addNewPersons({ selected, modal }) {
 | 
			
		||||
      console.log("@@@ CLICK button addNewPersons", selected);
 | 
			
		||||
      selected.forEach((item) => {
 | 
			
		||||
        this.$store.dispatch("addPersonsInvolved", item);
 | 
			
		||||
      }, this);
 | 
			
		||||
      this.$refs.addPersons.resetSearch(); // to cast child method
 | 
			
		||||
      modal.showModal = false;
 | 
			
		||||
      this.setPersonsInBloc();
 | 
			
		||||
    },
 | 
			
		||||
    addSuggestedEntity(person) {
 | 
			
		||||
      this.$store.dispatch("addPersonsInvolved", {
 | 
			
		||||
        result: person,
 | 
			
		||||
        type: "person",
 | 
			
		||||
      });
 | 
			
		||||
      this.setPersonsInBloc();
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,29 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <li>
 | 
			
		||||
        <span :title="person.text" @click.prevent="$emit('remove', person)">
 | 
			
		||||
            <span class="chill_denomination">
 | 
			
		||||
                <person-text :person="person" :is-cut="true" />
 | 
			
		||||
            </span>
 | 
			
		||||
        </span>
 | 
			
		||||
    </li>
 | 
			
		||||
  <li>
 | 
			
		||||
    <span :title="person.text" @click.prevent="$emit('remove', person)">
 | 
			
		||||
      <span class="chill_denomination">
 | 
			
		||||
        <person-text :person="person" :is-cut="true" />
 | 
			
		||||
      </span>
 | 
			
		||||
    </span>
 | 
			
		||||
  </li>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "PersonBadge",
 | 
			
		||||
    props: ["person"],
 | 
			
		||||
    components: {
 | 
			
		||||
        PersonText,
 | 
			
		||||
    },
 | 
			
		||||
    // computed: {
 | 
			
		||||
    //    textCutted() {
 | 
			
		||||
    //       let more = (this.person.text.length > 15) ?'…' : '';
 | 
			
		||||
    //       return this.person.text.slice(0,15) + more;
 | 
			
		||||
    //    }
 | 
			
		||||
    // },
 | 
			
		||||
    emits: ["remove"],
 | 
			
		||||
  name: "PersonBadge",
 | 
			
		||||
  props: ["person"],
 | 
			
		||||
  components: {
 | 
			
		||||
    PersonText,
 | 
			
		||||
  },
 | 
			
		||||
  // computed: {
 | 
			
		||||
  //    textCutted() {
 | 
			
		||||
  //       let more = (this.person.text.length > 15) ?'…' : '';
 | 
			
		||||
  //       return this.person.text.slice(0,15) + more;
 | 
			
		||||
  //    }
 | 
			
		||||
  // },
 | 
			
		||||
  emits: ["remove"],
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,38 +1,38 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
 | 
			
		||||
        <div class="item-row">
 | 
			
		||||
            <div class="item-col">
 | 
			
		||||
                <h4>{{ $t(bloc.title) }}</h4>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="item-col">
 | 
			
		||||
                <ul class="list-suggest remove-items">
 | 
			
		||||
                    <person-badge
 | 
			
		||||
                        v-for="person in bloc.persons"
 | 
			
		||||
                        :key="person.id"
 | 
			
		||||
                        :person="person"
 | 
			
		||||
                        @remove="removePerson"
 | 
			
		||||
                    />
 | 
			
		||||
                </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
  <div class="item-bloc" :style="{ 'flex-basis': blocWidth }">
 | 
			
		||||
    <div class="item-row">
 | 
			
		||||
      <div class="item-col">
 | 
			
		||||
        <h4>{{ $t(bloc.title) }}</h4>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="item-col">
 | 
			
		||||
        <ul class="list-suggest remove-items">
 | 
			
		||||
          <person-badge
 | 
			
		||||
            v-for="person in bloc.persons"
 | 
			
		||||
            :key="person.id"
 | 
			
		||||
            :person="person"
 | 
			
		||||
            @remove="removePerson"
 | 
			
		||||
          />
 | 
			
		||||
        </ul>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import PersonBadge from "./PersonBadge.vue";
 | 
			
		||||
export default {
 | 
			
		||||
    name: "PersonsBloc",
 | 
			
		||||
    components: {
 | 
			
		||||
        PersonBadge,
 | 
			
		||||
    },
 | 
			
		||||
    props: ["bloc", "setPersonsInBloc", "blocWidth"],
 | 
			
		||||
    methods: {
 | 
			
		||||
        removePerson(item) {
 | 
			
		||||
            console.log("@@ CLICK remove person: item", item);
 | 
			
		||||
            this.$store.dispatch("removePersonInvolved", item);
 | 
			
		||||
            this.setPersonsInBloc();
 | 
			
		||||
        },
 | 
			
		||||
  name: "PersonsBloc",
 | 
			
		||||
  components: {
 | 
			
		||||
    PersonBadge,
 | 
			
		||||
  },
 | 
			
		||||
  props: ["bloc", "setPersonsInBloc", "blocWidth"],
 | 
			
		||||
  methods: {
 | 
			
		||||
    removePerson(item) {
 | 
			
		||||
      console.log("@@ CLICK remove person: item", item);
 | 
			
		||||
      this.$store.dispatch("removePersonInvolved", item);
 | 
			
		||||
      this.setPersonsInBloc();
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,32 +1,32 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <teleport to="#location">
 | 
			
		||||
        <div class="mb-3 row">
 | 
			
		||||
            <label :class="locationClassList">
 | 
			
		||||
                {{ trans(ACTIVITY_LOCATION) }}
 | 
			
		||||
            </label>
 | 
			
		||||
            <div class="col-sm-8">
 | 
			
		||||
                <VueMultiselect
 | 
			
		||||
                    name="selectLocation"
 | 
			
		||||
                    id="selectLocation"
 | 
			
		||||
                    label="name"
 | 
			
		||||
                    track-by="id"
 | 
			
		||||
                    open-direction="top"
 | 
			
		||||
                    :multiple="false"
 | 
			
		||||
                    :searchable="true"
 | 
			
		||||
                    :placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
 | 
			
		||||
                    :custom-label="customLabel"
 | 
			
		||||
                    :select-label="trans(MULTISELECT_SELECT_LABEL)"
 | 
			
		||||
                    :deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
 | 
			
		||||
                    :selected-label="trans(MULTISELECT_SELECTED_LABEL)"
 | 
			
		||||
                    :options="availableLocations"
 | 
			
		||||
                    group-values="locations"
 | 
			
		||||
                    group-label="locationGroup"
 | 
			
		||||
                    v-model="location"
 | 
			
		||||
                />
 | 
			
		||||
                <new-location v-bind:available-locations="availableLocations" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </teleport>
 | 
			
		||||
  <teleport to="#location">
 | 
			
		||||
    <div class="mb-3 row">
 | 
			
		||||
      <label :class="locationClassList">
 | 
			
		||||
        {{ trans(ACTIVITY_LOCATION) }}
 | 
			
		||||
      </label>
 | 
			
		||||
      <div class="col-sm-8">
 | 
			
		||||
        <VueMultiselect
 | 
			
		||||
          name="selectLocation"
 | 
			
		||||
          id="selectLocation"
 | 
			
		||||
          label="name"
 | 
			
		||||
          track-by="id"
 | 
			
		||||
          open-direction="top"
 | 
			
		||||
          :multiple="false"
 | 
			
		||||
          :searchable="true"
 | 
			
		||||
          :placeholder="trans(ACTIVITY_CHOOSE_LOCATION)"
 | 
			
		||||
          :custom-label="customLabel"
 | 
			
		||||
          :select-label="trans(MULTISELECT_SELECT_LABEL)"
 | 
			
		||||
          :deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
 | 
			
		||||
          :selected-label="trans(MULTISELECT_SELECTED_LABEL)"
 | 
			
		||||
          :options="availableLocations"
 | 
			
		||||
          group-values="locations"
 | 
			
		||||
          group-label="locationGroup"
 | 
			
		||||
          v-model="location"
 | 
			
		||||
        />
 | 
			
		||||
        <new-location v-bind:available-locations="availableLocations" />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </teleport>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -35,60 +35,60 @@ import VueMultiselect from "vue-multiselect";
 | 
			
		||||
import NewLocation from "./Location/NewLocation.vue";
 | 
			
		||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
 | 
			
		||||
import {
 | 
			
		||||
    trans,
 | 
			
		||||
    ACTIVITY_LOCATION,
 | 
			
		||||
    ACTIVITY_CHOOSE_LOCATION,
 | 
			
		||||
    MULTISELECT_SELECT_LABEL,
 | 
			
		||||
    MULTISELECT_DESELECT_LABEL,
 | 
			
		||||
    MULTISELECT_SELECTED_LABEL,
 | 
			
		||||
  trans,
 | 
			
		||||
  ACTIVITY_LOCATION,
 | 
			
		||||
  ACTIVITY_CHOOSE_LOCATION,
 | 
			
		||||
  MULTISELECT_SELECT_LABEL,
 | 
			
		||||
  MULTISELECT_DESELECT_LABEL,
 | 
			
		||||
  MULTISELECT_SELECTED_LABEL,
 | 
			
		||||
} from "translator";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "Location",
 | 
			
		||||
    components: {
 | 
			
		||||
        NewLocation,
 | 
			
		||||
        VueMultiselect,
 | 
			
		||||
  name: "Location",
 | 
			
		||||
  components: {
 | 
			
		||||
    NewLocation,
 | 
			
		||||
    VueMultiselect,
 | 
			
		||||
  },
 | 
			
		||||
  setup() {
 | 
			
		||||
    return {
 | 
			
		||||
      trans,
 | 
			
		||||
      ACTIVITY_LOCATION,
 | 
			
		||||
      ACTIVITY_CHOOSE_LOCATION,
 | 
			
		||||
      MULTISELECT_SELECT_LABEL,
 | 
			
		||||
      MULTISELECT_DESELECT_LABEL,
 | 
			
		||||
      MULTISELECT_SELECTED_LABEL,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    ...mapState(["activity", "availableLocations"]),
 | 
			
		||||
    ...mapGetters(["suggestedEntities"]),
 | 
			
		||||
    location: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.activity.location;
 | 
			
		||||
      },
 | 
			
		||||
      set(value) {
 | 
			
		||||
        this.$store.dispatch("updateLocation", value);
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup() {
 | 
			
		||||
        return {
 | 
			
		||||
            trans,
 | 
			
		||||
            ACTIVITY_LOCATION,
 | 
			
		||||
            ACTIVITY_CHOOSE_LOCATION,
 | 
			
		||||
            MULTISELECT_SELECT_LABEL,
 | 
			
		||||
            MULTISELECT_DESELECT_LABEL,
 | 
			
		||||
            MULTISELECT_SELECTED_LABEL,
 | 
			
		||||
        };
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    labelAccompanyingCourseLocation(value) {
 | 
			
		||||
      return `${value.address.text} (${localizeString(value.locationType.title)})`;
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            locationClassList: `col-form-label col-sm-4 ${document.querySelector("input#chill_activitybundle_activity_location").getAttribute("required") ? "required" : ""}`,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        ...mapState(["activity", "availableLocations"]),
 | 
			
		||||
        ...mapGetters(["suggestedEntities"]),
 | 
			
		||||
        location: {
 | 
			
		||||
            get() {
 | 
			
		||||
                return this.activity.location;
 | 
			
		||||
            },
 | 
			
		||||
            set(value) {
 | 
			
		||||
                this.$store.dispatch("updateLocation", value);
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        labelAccompanyingCourseLocation(value) {
 | 
			
		||||
            return `${value.address.text} (${localizeString(value.locationType.title)})`;
 | 
			
		||||
        },
 | 
			
		||||
        customLabel(value) {
 | 
			
		||||
            return value.locationType
 | 
			
		||||
                ? value.name
 | 
			
		||||
                    ? value.name === "__AccompanyingCourseLocation__"
 | 
			
		||||
                        ? this.labelAccompanyingCourseLocation(value)
 | 
			
		||||
                        : `${value.name} (${localizeString(value.locationType.title)})`
 | 
			
		||||
                    : localizeString(value.locationType.title)
 | 
			
		||||
                : "";
 | 
			
		||||
        },
 | 
			
		||||
    customLabel(value) {
 | 
			
		||||
      return value.locationType
 | 
			
		||||
        ? value.name
 | 
			
		||||
          ? value.name === "__AccompanyingCourseLocation__"
 | 
			
		||||
            ? this.labelAccompanyingCourseLocation(value)
 | 
			
		||||
            : `${value.name} (${localizeString(value.locationType.title)})`
 | 
			
		||||
          : localizeString(value.locationType.title)
 | 
			
		||||
        : "";
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,123 +1,114 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <ul class="record_actions">
 | 
			
		||||
            <li>
 | 
			
		||||
                <a class="btn btn-sm btn-create" @click="openModal">
 | 
			
		||||
                    {{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
 | 
			
		||||
                </a>
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
  <div>
 | 
			
		||||
    <ul class="record_actions">
 | 
			
		||||
      <li>
 | 
			
		||||
        <a class="btn btn-sm btn-create" @click="openModal">
 | 
			
		||||
          {{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
 | 
			
		||||
        </a>
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
        <teleport to="body">
 | 
			
		||||
            <modal
 | 
			
		||||
                v-if="modal.showModal"
 | 
			
		||||
                :modalDialogClass="modal.modalDialogClass"
 | 
			
		||||
                @close="modal.showModal = false"
 | 
			
		||||
            >
 | 
			
		||||
                <template #header>
 | 
			
		||||
                    <h3 class="modal-title">
 | 
			
		||||
                        {{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
 | 
			
		||||
                    </h3>
 | 
			
		||||
                </template>
 | 
			
		||||
                <template #body>
 | 
			
		||||
                    <form>
 | 
			
		||||
                        <div class="alert alert-warning" v-if="errors.length">
 | 
			
		||||
                            <ul>
 | 
			
		||||
                                <li v-for="(e, i) in errors" :key="i">
 | 
			
		||||
                                    {{ e }}
 | 
			
		||||
                                </li>
 | 
			
		||||
                            </ul>
 | 
			
		||||
                        </div>
 | 
			
		||||
    <teleport to="body">
 | 
			
		||||
      <modal
 | 
			
		||||
        v-if="modal.showModal"
 | 
			
		||||
        :modalDialogClass="modal.modalDialogClass"
 | 
			
		||||
        @close="modal.showModal = false"
 | 
			
		||||
      >
 | 
			
		||||
        <template #header>
 | 
			
		||||
          <h3 class="modal-title">
 | 
			
		||||
            {{ trans(ACTIVITY_CREATE_NEW_LOCATION) }}
 | 
			
		||||
          </h3>
 | 
			
		||||
        </template>
 | 
			
		||||
        <template #body>
 | 
			
		||||
          <form>
 | 
			
		||||
            <div class="alert alert-warning" v-if="errors.length">
 | 
			
		||||
              <ul>
 | 
			
		||||
                <li v-for="(e, i) in errors" :key="i">
 | 
			
		||||
                  {{ e }}
 | 
			
		||||
                </li>
 | 
			
		||||
              </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="form-floating mb-3">
 | 
			
		||||
                            <select
 | 
			
		||||
                                class="form-select form-select-lg"
 | 
			
		||||
                                id="type"
 | 
			
		||||
                                required
 | 
			
		||||
                                v-model="selectType"
 | 
			
		||||
                            >
 | 
			
		||||
                                <option selected disabled value="">
 | 
			
		||||
                                    {{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
 | 
			
		||||
                                </option>
 | 
			
		||||
                                <option
 | 
			
		||||
                                    v-for="t in locationTypes"
 | 
			
		||||
                                    :value="t"
 | 
			
		||||
                                    :key="t.id"
 | 
			
		||||
                                >
 | 
			
		||||
                                    {{ localizeString(t.title) }}
 | 
			
		||||
                                </option>
 | 
			
		||||
                            </select>
 | 
			
		||||
                            <label>{{
 | 
			
		||||
                                trans(ACTIVITY_LOCATION_FIELDS_TYPE)
 | 
			
		||||
                            }}</label>
 | 
			
		||||
                        </div>
 | 
			
		||||
            <div class="form-floating mb-3">
 | 
			
		||||
              <select
 | 
			
		||||
                class="form-select form-select-lg"
 | 
			
		||||
                id="type"
 | 
			
		||||
                required
 | 
			
		||||
                v-model="selectType"
 | 
			
		||||
              >
 | 
			
		||||
                <option selected disabled value="">
 | 
			
		||||
                  {{ trans(ACTIVITY_CHOOSE_LOCATION_TYPE) }}
 | 
			
		||||
                </option>
 | 
			
		||||
                <option v-for="t in locationTypes" :value="t" :key="t.id">
 | 
			
		||||
                  {{ localizeString(t.title) }}
 | 
			
		||||
                </option>
 | 
			
		||||
              </select>
 | 
			
		||||
              <label>{{ trans(ACTIVITY_LOCATION_FIELDS_TYPE) }}</label>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="form-floating mb-3">
 | 
			
		||||
                            <input
 | 
			
		||||
                                class="form-control form-control-lg"
 | 
			
		||||
                                id="name"
 | 
			
		||||
                                v-model="inputName"
 | 
			
		||||
                                placeholder
 | 
			
		||||
                            />
 | 
			
		||||
                            <label for="name">{{
 | 
			
		||||
                                trans(ACTIVITY_LOCATION_FIELDS_NAME)
 | 
			
		||||
                            }}</label>
 | 
			
		||||
                        </div>
 | 
			
		||||
            <div class="form-floating mb-3">
 | 
			
		||||
              <input
 | 
			
		||||
                class="form-control form-control-lg"
 | 
			
		||||
                id="name"
 | 
			
		||||
                v-model="inputName"
 | 
			
		||||
                placeholder
 | 
			
		||||
              />
 | 
			
		||||
              <label for="name">{{
 | 
			
		||||
                trans(ACTIVITY_LOCATION_FIELDS_NAME)
 | 
			
		||||
              }}</label>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
                        <add-address
 | 
			
		||||
                            :context="addAddress.context"
 | 
			
		||||
                            :options="addAddress.options"
 | 
			
		||||
                            :addressChangedCallback="submitNewAddress"
 | 
			
		||||
                            v-if="showAddAddress"
 | 
			
		||||
                            ref="addAddress"
 | 
			
		||||
                        />
 | 
			
		||||
            <add-address
 | 
			
		||||
              :context="addAddress.context"
 | 
			
		||||
              :options="addAddress.options"
 | 
			
		||||
              :addressChangedCallback="submitNewAddress"
 | 
			
		||||
              v-if="showAddAddress"
 | 
			
		||||
              ref="addAddress"
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
                        <div class="form-floating mb-3" v-if="showContactData">
 | 
			
		||||
                            <input
 | 
			
		||||
                                class="form-control form-control-lg"
 | 
			
		||||
                                id="phonenumber1"
 | 
			
		||||
                                v-model="inputPhonenumber1"
 | 
			
		||||
                                placeholder
 | 
			
		||||
                            />
 | 
			
		||||
                            <label for="phonenumber1">{{
 | 
			
		||||
                                trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
 | 
			
		||||
                            }}</label>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="form-floating mb-3" v-if="hasPhonenumber1">
 | 
			
		||||
                            <input
 | 
			
		||||
                                class="form-control form-control-lg"
 | 
			
		||||
                                id="phonenumber2"
 | 
			
		||||
                                v-model="inputPhonenumber2"
 | 
			
		||||
                                placeholder
 | 
			
		||||
                            />
 | 
			
		||||
                            <label for="phonenumber2">{{
 | 
			
		||||
                                trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
 | 
			
		||||
                            }}</label>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="form-floating mb-3" v-if="showContactData">
 | 
			
		||||
                            <input
 | 
			
		||||
                                class="form-control form-control-lg"
 | 
			
		||||
                                id="email"
 | 
			
		||||
                                v-model="inputEmail"
 | 
			
		||||
                                placeholder
 | 
			
		||||
                            />
 | 
			
		||||
                            <label for="email">{{
 | 
			
		||||
                                trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
 | 
			
		||||
                            }}</label>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </form>
 | 
			
		||||
                </template>
 | 
			
		||||
                <template #footer>
 | 
			
		||||
                    <button
 | 
			
		||||
                        class="btn btn-save"
 | 
			
		||||
                        @click.prevent="saveNewLocation"
 | 
			
		||||
                    >
 | 
			
		||||
                        {{ trans(SAVE) }}
 | 
			
		||||
                    </button>
 | 
			
		||||
                </template>
 | 
			
		||||
            </modal>
 | 
			
		||||
        </teleport>
 | 
			
		||||
    </div>
 | 
			
		||||
            <div class="form-floating mb-3" v-if="showContactData">
 | 
			
		||||
              <input
 | 
			
		||||
                class="form-control form-control-lg"
 | 
			
		||||
                id="phonenumber1"
 | 
			
		||||
                v-model="inputPhonenumber1"
 | 
			
		||||
                placeholder
 | 
			
		||||
              />
 | 
			
		||||
              <label for="phonenumber1">{{
 | 
			
		||||
                trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER1)
 | 
			
		||||
              }}</label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-floating mb-3" v-if="hasPhonenumber1">
 | 
			
		||||
              <input
 | 
			
		||||
                class="form-control form-control-lg"
 | 
			
		||||
                id="phonenumber2"
 | 
			
		||||
                v-model="inputPhonenumber2"
 | 
			
		||||
                placeholder
 | 
			
		||||
              />
 | 
			
		||||
              <label for="phonenumber2">{{
 | 
			
		||||
                trans(ACTIVITY_LOCATION_FIELDS_PHONENUMBER2)
 | 
			
		||||
              }}</label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-floating mb-3" v-if="showContactData">
 | 
			
		||||
              <input
 | 
			
		||||
                class="form-control form-control-lg"
 | 
			
		||||
                id="email"
 | 
			
		||||
                v-model="inputEmail"
 | 
			
		||||
                placeholder
 | 
			
		||||
              />
 | 
			
		||||
              <label for="email">{{
 | 
			
		||||
                trans(ACTIVITY_LOCATION_FIELDS_EMAIL)
 | 
			
		||||
              }}</label>
 | 
			
		||||
            </div>
 | 
			
		||||
          </form>
 | 
			
		||||
        </template>
 | 
			
		||||
        <template #footer>
 | 
			
		||||
          <button class="btn btn-save" @click.prevent="saveNewLocation">
 | 
			
		||||
            {{ trans(SAVE) }}
 | 
			
		||||
          </button>
 | 
			
		||||
        </template>
 | 
			
		||||
      </modal>
 | 
			
		||||
    </teleport>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -128,237 +119,236 @@ import { getLocationTypes } from "../../api";
 | 
			
		||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
 | 
			
		||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
 | 
			
		||||
import {
 | 
			
		||||
    SAVE,
 | 
			
		||||
    ACTIVITY_LOCATION_FIELDS_EMAIL,
 | 
			
		||||
    ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
 | 
			
		||||
    ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
 | 
			
		||||
    ACTIVITY_LOCATION_FIELDS_NAME,
 | 
			
		||||
    ACTIVITY_LOCATION_FIELDS_TYPE,
 | 
			
		||||
    ACTIVITY_CHOOSE_LOCATION_TYPE,
 | 
			
		||||
    ACTIVITY_CREATE_NEW_LOCATION,
 | 
			
		||||
    trans,
 | 
			
		||||
  SAVE,
 | 
			
		||||
  ACTIVITY_LOCATION_FIELDS_EMAIL,
 | 
			
		||||
  ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
 | 
			
		||||
  ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
 | 
			
		||||
  ACTIVITY_LOCATION_FIELDS_NAME,
 | 
			
		||||
  ACTIVITY_LOCATION_FIELDS_TYPE,
 | 
			
		||||
  ACTIVITY_CHOOSE_LOCATION_TYPE,
 | 
			
		||||
  ACTIVITY_CREATE_NEW_LOCATION,
 | 
			
		||||
  trans,
 | 
			
		||||
} from "translator";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "NewLocation",
 | 
			
		||||
    components: {
 | 
			
		||||
        Modal,
 | 
			
		||||
        AddAddress,
 | 
			
		||||
  name: "NewLocation",
 | 
			
		||||
  components: {
 | 
			
		||||
    Modal,
 | 
			
		||||
    AddAddress,
 | 
			
		||||
  },
 | 
			
		||||
  setup() {
 | 
			
		||||
    return {
 | 
			
		||||
      trans,
 | 
			
		||||
      SAVE,
 | 
			
		||||
      ACTIVITY_LOCATION_FIELDS_EMAIL,
 | 
			
		||||
      ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
 | 
			
		||||
      ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
 | 
			
		||||
      ACTIVITY_LOCATION_FIELDS_NAME,
 | 
			
		||||
      ACTIVITY_LOCATION_FIELDS_TYPE,
 | 
			
		||||
      ACTIVITY_CHOOSE_LOCATION_TYPE,
 | 
			
		||||
      ACTIVITY_CREATE_NEW_LOCATION,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  props: ["availableLocations"],
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      errors: [],
 | 
			
		||||
      selected: {
 | 
			
		||||
        type: null,
 | 
			
		||||
        name: null,
 | 
			
		||||
        addressId: null,
 | 
			
		||||
        phonenumber1: null,
 | 
			
		||||
        phonenumber2: null,
 | 
			
		||||
        email: null,
 | 
			
		||||
      },
 | 
			
		||||
      locationTypes: [],
 | 
			
		||||
      modal: {
 | 
			
		||||
        showModal: false,
 | 
			
		||||
        modalDialogClass: "modal-dialog-scrollable modal-xl",
 | 
			
		||||
      },
 | 
			
		||||
      addAddress: {
 | 
			
		||||
        options: {
 | 
			
		||||
          button: {
 | 
			
		||||
            text: {
 | 
			
		||||
              create: "activity.create_address",
 | 
			
		||||
              edit: "activity.edit_address",
 | 
			
		||||
            },
 | 
			
		||||
            size: "btn-sm",
 | 
			
		||||
          },
 | 
			
		||||
          title: {
 | 
			
		||||
            create: "activity.create_address",
 | 
			
		||||
            edit: "activity.edit_address",
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        context: {
 | 
			
		||||
          target: {
 | 
			
		||||
            //name, id
 | 
			
		||||
          },
 | 
			
		||||
          edit: false,
 | 
			
		||||
          addressId: null,
 | 
			
		||||
          defaults: window.addaddress,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    ...mapState(["activity"]),
 | 
			
		||||
    selectType: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.selected.type;
 | 
			
		||||
      },
 | 
			
		||||
      set(value) {
 | 
			
		||||
        this.selected.type = value;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    setup() {
 | 
			
		||||
        return {
 | 
			
		||||
            trans,
 | 
			
		||||
            SAVE,
 | 
			
		||||
            ACTIVITY_LOCATION_FIELDS_EMAIL,
 | 
			
		||||
            ACTIVITY_LOCATION_FIELDS_PHONENUMBER1,
 | 
			
		||||
            ACTIVITY_LOCATION_FIELDS_PHONENUMBER2,
 | 
			
		||||
            ACTIVITY_LOCATION_FIELDS_NAME,
 | 
			
		||||
            ACTIVITY_LOCATION_FIELDS_TYPE,
 | 
			
		||||
            ACTIVITY_CHOOSE_LOCATION_TYPE,
 | 
			
		||||
            ACTIVITY_CREATE_NEW_LOCATION,
 | 
			
		||||
    inputName: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.selected.name;
 | 
			
		||||
      },
 | 
			
		||||
      set(value) {
 | 
			
		||||
        this.selected.name = value;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    inputEmail: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.selected.email;
 | 
			
		||||
      },
 | 
			
		||||
      set(value) {
 | 
			
		||||
        this.selected.email = value;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    inputPhonenumber1: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.selected.phonenumber1;
 | 
			
		||||
      },
 | 
			
		||||
      set(value) {
 | 
			
		||||
        this.selected.phonenumber1 = value;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    inputPhonenumber2: {
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.selected.phonenumber2;
 | 
			
		||||
      },
 | 
			
		||||
      set(value) {
 | 
			
		||||
        this.selected.phonenumber2 = value;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    hasPhonenumber1() {
 | 
			
		||||
      return (
 | 
			
		||||
        this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== ""
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
    showAddAddress() {
 | 
			
		||||
      let cond = false;
 | 
			
		||||
      if (this.selected.type) {
 | 
			
		||||
        if (this.selected.type.addressRequired !== "never") {
 | 
			
		||||
          cond = true;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return cond;
 | 
			
		||||
    },
 | 
			
		||||
    showContactData() {
 | 
			
		||||
      let cond = false;
 | 
			
		||||
      if (this.selected.type) {
 | 
			
		||||
        if (this.selected.type.contactData !== "never") {
 | 
			
		||||
          cond = true;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return cond;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.getLocationTypesList();
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    localizeString,
 | 
			
		||||
    checkForm() {
 | 
			
		||||
      let cond = true;
 | 
			
		||||
      this.errors = [];
 | 
			
		||||
      if (!this.selected.type) {
 | 
			
		||||
        this.errors.push("Type de localisation requis");
 | 
			
		||||
        cond = false;
 | 
			
		||||
      } else {
 | 
			
		||||
        if (
 | 
			
		||||
          this.selected.type.addressRequired === "required" &&
 | 
			
		||||
          !this.selected.addressId
 | 
			
		||||
        ) {
 | 
			
		||||
          this.errors.push("Adresse requise");
 | 
			
		||||
          cond = false;
 | 
			
		||||
        }
 | 
			
		||||
        if (
 | 
			
		||||
          this.selected.type.contactData === "required" &&
 | 
			
		||||
          !this.selected.phonenumber1
 | 
			
		||||
        ) {
 | 
			
		||||
          this.errors.push("Numéro de téléphone requis");
 | 
			
		||||
          cond = false;
 | 
			
		||||
        }
 | 
			
		||||
        if (
 | 
			
		||||
          this.selected.type.contactData === "required" &&
 | 
			
		||||
          !this.selected.email
 | 
			
		||||
        ) {
 | 
			
		||||
          this.errors.push("Adresse email requise");
 | 
			
		||||
          cond = false;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return cond;
 | 
			
		||||
    },
 | 
			
		||||
    getLocationTypesList() {
 | 
			
		||||
      getLocationTypes().then((results) => {
 | 
			
		||||
        this.locationTypes = results.filter(
 | 
			
		||||
          (t) => t.availableForUsers === true,
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    openModal() {
 | 
			
		||||
      this.modal.showModal = true;
 | 
			
		||||
    },
 | 
			
		||||
    saveNewLocation() {
 | 
			
		||||
      if (this.checkForm()) {
 | 
			
		||||
        let body = {
 | 
			
		||||
          type: "location",
 | 
			
		||||
          name: this.selected.name,
 | 
			
		||||
          locationType: {
 | 
			
		||||
            id: this.selected.type.id,
 | 
			
		||||
            type: "location-type",
 | 
			
		||||
          },
 | 
			
		||||
          phonenumber1: this.selected.phonenumber1,
 | 
			
		||||
          phonenumber2: this.selected.phonenumber2,
 | 
			
		||||
          email: this.selected.email,
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    props: ["availableLocations"],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            errors: [],
 | 
			
		||||
            selected: {
 | 
			
		||||
                type: null,
 | 
			
		||||
                name: null,
 | 
			
		||||
                addressId: null,
 | 
			
		||||
                phonenumber1: null,
 | 
			
		||||
                phonenumber2: null,
 | 
			
		||||
                email: null,
 | 
			
		||||
        if (this.selected.addressId) {
 | 
			
		||||
          body = Object.assign(body, {
 | 
			
		||||
            address: {
 | 
			
		||||
              id: this.selected.addressId,
 | 
			
		||||
            },
 | 
			
		||||
            locationTypes: [],
 | 
			
		||||
            modal: {
 | 
			
		||||
                showModal: false,
 | 
			
		||||
                modalDialogClass: "modal-dialog-scrollable modal-xl",
 | 
			
		||||
            },
 | 
			
		||||
            addAddress: {
 | 
			
		||||
                options: {
 | 
			
		||||
                    button: {
 | 
			
		||||
                        text: {
 | 
			
		||||
                            create: "activity.create_address",
 | 
			
		||||
                            edit: "activity.edit_address",
 | 
			
		||||
                        },
 | 
			
		||||
                        size: "btn-sm",
 | 
			
		||||
                    },
 | 
			
		||||
                    title: {
 | 
			
		||||
                        create: "activity.create_address",
 | 
			
		||||
                        edit: "activity.edit_address",
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                context: {
 | 
			
		||||
                    target: {
 | 
			
		||||
                        //name, id
 | 
			
		||||
                    },
 | 
			
		||||
                    edit: false,
 | 
			
		||||
                    addressId: null,
 | 
			
		||||
                    defaults: window.addaddress,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        ...mapState(["activity"]),
 | 
			
		||||
        selectType: {
 | 
			
		||||
            get() {
 | 
			
		||||
                return this.selected.type;
 | 
			
		||||
            },
 | 
			
		||||
            set(value) {
 | 
			
		||||
                this.selected.type = value;
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        inputName: {
 | 
			
		||||
            get() {
 | 
			
		||||
                return this.selected.name;
 | 
			
		||||
            },
 | 
			
		||||
            set(value) {
 | 
			
		||||
                this.selected.name = value;
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        inputEmail: {
 | 
			
		||||
            get() {
 | 
			
		||||
                return this.selected.email;
 | 
			
		||||
            },
 | 
			
		||||
            set(value) {
 | 
			
		||||
                this.selected.email = value;
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        inputPhonenumber1: {
 | 
			
		||||
            get() {
 | 
			
		||||
                return this.selected.phonenumber1;
 | 
			
		||||
            },
 | 
			
		||||
            set(value) {
 | 
			
		||||
                this.selected.phonenumber1 = value;
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        inputPhonenumber2: {
 | 
			
		||||
            get() {
 | 
			
		||||
                return this.selected.phonenumber2;
 | 
			
		||||
            },
 | 
			
		||||
            set(value) {
 | 
			
		||||
                this.selected.phonenumber2 = value;
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        hasPhonenumber1() {
 | 
			
		||||
            return (
 | 
			
		||||
                this.selected.phonenumber1 !== null &&
 | 
			
		||||
                this.selected.phonenumber1 !== ""
 | 
			
		||||
            );
 | 
			
		||||
        },
 | 
			
		||||
        showAddAddress() {
 | 
			
		||||
            let cond = false;
 | 
			
		||||
            if (this.selected.type) {
 | 
			
		||||
                if (this.selected.type.addressRequired !== "never") {
 | 
			
		||||
                    cond = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return cond;
 | 
			
		||||
        },
 | 
			
		||||
        showContactData() {
 | 
			
		||||
            let cond = false;
 | 
			
		||||
            if (this.selected.type) {
 | 
			
		||||
                if (this.selected.type.contactData !== "never") {
 | 
			
		||||
                    cond = true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return cond;
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.getLocationTypesList();
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        localizeString,
 | 
			
		||||
        checkForm() {
 | 
			
		||||
            let cond = true;
 | 
			
		||||
            this.errors = [];
 | 
			
		||||
            if (!this.selected.type) {
 | 
			
		||||
                this.errors.push("Type de localisation requis");
 | 
			
		||||
                cond = false;
 | 
			
		||||
            } else {
 | 
			
		||||
                if (
 | 
			
		||||
                    this.selected.type.addressRequired === "required" &&
 | 
			
		||||
                    !this.selected.addressId
 | 
			
		||||
                ) {
 | 
			
		||||
                    this.errors.push("Adresse requise");
 | 
			
		||||
                    cond = false;
 | 
			
		||||
                }
 | 
			
		||||
                if (
 | 
			
		||||
                    this.selected.type.contactData === "required" &&
 | 
			
		||||
                    !this.selected.phonenumber1
 | 
			
		||||
                ) {
 | 
			
		||||
                    this.errors.push("Numéro de téléphone requis");
 | 
			
		||||
                    cond = false;
 | 
			
		||||
                }
 | 
			
		||||
                if (
 | 
			
		||||
                    this.selected.type.contactData === "required" &&
 | 
			
		||||
                    !this.selected.email
 | 
			
		||||
                ) {
 | 
			
		||||
                    this.errors.push("Adresse email requise");
 | 
			
		||||
                    cond = false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return cond;
 | 
			
		||||
        },
 | 
			
		||||
        getLocationTypesList() {
 | 
			
		||||
            getLocationTypes().then((results) => {
 | 
			
		||||
                this.locationTypes = results.filter(
 | 
			
		||||
                    (t) => t.availableForUsers === true,
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        openModal() {
 | 
			
		||||
            this.modal.showModal = true;
 | 
			
		||||
        },
 | 
			
		||||
        saveNewLocation() {
 | 
			
		||||
            if (this.checkForm()) {
 | 
			
		||||
                let body = {
 | 
			
		||||
                    type: "location",
 | 
			
		||||
                    name: this.selected.name,
 | 
			
		||||
                    locationType: {
 | 
			
		||||
                        id: this.selected.type.id,
 | 
			
		||||
                        type: "location-type",
 | 
			
		||||
                    },
 | 
			
		||||
                    phonenumber1: this.selected.phonenumber1,
 | 
			
		||||
                    phonenumber2: this.selected.phonenumber2,
 | 
			
		||||
                    email: this.selected.email,
 | 
			
		||||
                };
 | 
			
		||||
                if (this.selected.addressId) {
 | 
			
		||||
                    body = Object.assign(body, {
 | 
			
		||||
                        address: {
 | 
			
		||||
                            id: this.selected.addressId,
 | 
			
		||||
                        },
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                makeFetch("POST", "/api/1.0/main/location.json", body)
 | 
			
		||||
                    .then((response) => {
 | 
			
		||||
                        this.$store.dispatch("addAvailableLocationGroup", {
 | 
			
		||||
                            locationGroup: "Localisations nouvellement créées",
 | 
			
		||||
                            locations: [response],
 | 
			
		||||
                        });
 | 
			
		||||
                        this.$store.dispatch("updateLocation", response);
 | 
			
		||||
                        this.modal.showModal = false;
 | 
			
		||||
                    })
 | 
			
		||||
                    .catch((error) => {
 | 
			
		||||
                        if (error.name === "ValidationException") {
 | 
			
		||||
                            for (let v of error.violations) {
 | 
			
		||||
                                this.errors.push(v);
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            this.errors.push("An error occurred");
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
        makeFetch("POST", "/api/1.0/main/location.json", body)
 | 
			
		||||
          .then((response) => {
 | 
			
		||||
            this.$store.dispatch("addAvailableLocationGroup", {
 | 
			
		||||
              locationGroup: "Localisations nouvellement créées",
 | 
			
		||||
              locations: [response],
 | 
			
		||||
            });
 | 
			
		||||
            this.$store.dispatch("updateLocation", response);
 | 
			
		||||
            this.modal.showModal = false;
 | 
			
		||||
          })
 | 
			
		||||
          .catch((error) => {
 | 
			
		||||
            if (error.name === "ValidationException") {
 | 
			
		||||
              for (let v of error.violations) {
 | 
			
		||||
                this.errors.push(v);
 | 
			
		||||
              }
 | 
			
		||||
            } else {
 | 
			
		||||
              this.errors.push("An error occurred");
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        submitNewAddress(payload) {
 | 
			
		||||
            this.selected.addressId = payload.addressId;
 | 
			
		||||
            this.addAddress.context.addressId = payload.addressId;
 | 
			
		||||
            this.addAddress.context.edit = true;
 | 
			
		||||
        },
 | 
			
		||||
          });
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    submitNewAddress(payload) {
 | 
			
		||||
      this.selected.addressId = payload.addressId;
 | 
			
		||||
      this.addAddress.context.addressId = payload.addressId;
 | 
			
		||||
      this.addAddress.context.edit = true;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,103 +1,98 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <teleport to="#social-issues-acc">
 | 
			
		||||
        <div class="mb-3 row">
 | 
			
		||||
            <div class="col-4">
 | 
			
		||||
                <label :class="socialIssuesClassList">{{
 | 
			
		||||
                    trans(ACTIVITY_SOCIAL_ISSUES)
 | 
			
		||||
                }}</label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col-8">
 | 
			
		||||
                <check-social-issue
 | 
			
		||||
                    v-for="issue in socialIssuesList"
 | 
			
		||||
                    :key="issue.id"
 | 
			
		||||
                    :issue="issue"
 | 
			
		||||
                    :selection="socialIssuesSelected"
 | 
			
		||||
                    @updateSelected="updateIssuesSelected"
 | 
			
		||||
                >
 | 
			
		||||
                </check-social-issue>
 | 
			
		||||
  <teleport to="#social-issues-acc">
 | 
			
		||||
    <div class="mb-3 row">
 | 
			
		||||
      <div class="col-4">
 | 
			
		||||
        <label :class="socialIssuesClassList">{{
 | 
			
		||||
          trans(ACTIVITY_SOCIAL_ISSUES)
 | 
			
		||||
        }}</label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="col-8">
 | 
			
		||||
        <check-social-issue
 | 
			
		||||
          v-for="issue in socialIssuesList"
 | 
			
		||||
          :key="issue.id"
 | 
			
		||||
          :issue="issue"
 | 
			
		||||
          :selection="socialIssuesSelected"
 | 
			
		||||
          @updateSelected="updateIssuesSelected"
 | 
			
		||||
        >
 | 
			
		||||
        </check-social-issue>
 | 
			
		||||
 | 
			
		||||
                <div class="my-3">
 | 
			
		||||
                    <VueMultiselect
 | 
			
		||||
                        name="otherIssues"
 | 
			
		||||
                        label="text"
 | 
			
		||||
                        track-by="id"
 | 
			
		||||
                        open-direction="bottom"
 | 
			
		||||
                        :close-on-select="true"
 | 
			
		||||
                        :preserve-search="false"
 | 
			
		||||
                        :reset-after="true"
 | 
			
		||||
                        :hide-selected="true"
 | 
			
		||||
                        :taggable="false"
 | 
			
		||||
                        :multiple="false"
 | 
			
		||||
                        :searchable="true"
 | 
			
		||||
                        :allow-empty="true"
 | 
			
		||||
                        :show-labels="false"
 | 
			
		||||
                        :loading="issueIsLoading"
 | 
			
		||||
                        :placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
 | 
			
		||||
                        :options="socialIssuesOther"
 | 
			
		||||
                        @select="addIssueInList"
 | 
			
		||||
                    >
 | 
			
		||||
                    </VueMultiselect>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        <div class="my-3">
 | 
			
		||||
          <VueMultiselect
 | 
			
		||||
            name="otherIssues"
 | 
			
		||||
            label="text"
 | 
			
		||||
            track-by="id"
 | 
			
		||||
            open-direction="bottom"
 | 
			
		||||
            :close-on-select="true"
 | 
			
		||||
            :preserve-search="false"
 | 
			
		||||
            :reset-after="true"
 | 
			
		||||
            :hide-selected="true"
 | 
			
		||||
            :taggable="false"
 | 
			
		||||
            :multiple="false"
 | 
			
		||||
            :searchable="true"
 | 
			
		||||
            :allow-empty="true"
 | 
			
		||||
            :show-labels="false"
 | 
			
		||||
            :loading="issueIsLoading"
 | 
			
		||||
            :placeholder="trans(ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE)"
 | 
			
		||||
            :options="socialIssuesOther"
 | 
			
		||||
            @select="addIssueInList"
 | 
			
		||||
          >
 | 
			
		||||
          </VueMultiselect>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="mb-3 row">
 | 
			
		||||
      <div class="col-4">
 | 
			
		||||
        <label :class="socialActionsClassList">{{
 | 
			
		||||
          trans(ACTIVITY_SOCIAL_ACTIONS)
 | 
			
		||||
        }}</label>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="col-8">
 | 
			
		||||
        <div v-if="actionIsLoading === true">
 | 
			
		||||
          <i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="mb-3 row">
 | 
			
		||||
            <div class="col-4">
 | 
			
		||||
                <label :class="socialActionsClassList">{{
 | 
			
		||||
                    trans(ACTIVITY_SOCIAL_ACTIONS)
 | 
			
		||||
                }}</label>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col-8">
 | 
			
		||||
                <div v-if="actionIsLoading === true">
 | 
			
		||||
                    <i
 | 
			
		||||
                        class="chill-green fa fa-circle-o-notch fa-spin fa-lg"
 | 
			
		||||
                    ></i>
 | 
			
		||||
                </div>
 | 
			
		||||
        <span
 | 
			
		||||
          v-else-if="socialIssuesSelected.length === 0"
 | 
			
		||||
          class="inline-choice chill-no-data-statement mt-3"
 | 
			
		||||
        >
 | 
			
		||||
          {{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
 | 
			
		||||
        </span>
 | 
			
		||||
 | 
			
		||||
                <span
 | 
			
		||||
                    v-else-if="socialIssuesSelected.length === 0"
 | 
			
		||||
                    class="inline-choice chill-no-data-statement mt-3"
 | 
			
		||||
                >
 | 
			
		||||
                    {{ trans(ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE) }}
 | 
			
		||||
                </span>
 | 
			
		||||
        <template
 | 
			
		||||
          v-else-if="
 | 
			
		||||
            socialActionsList.length > 0 &&
 | 
			
		||||
            (socialIssuesSelected.length || socialActionsSelected.length)
 | 
			
		||||
          "
 | 
			
		||||
        >
 | 
			
		||||
          <div
 | 
			
		||||
            id="actionsList"
 | 
			
		||||
            v-for="group in socialActionsList"
 | 
			
		||||
            :key="group.issue"
 | 
			
		||||
          >
 | 
			
		||||
            <span class="badge bg-chill-l-gray text-dark">{{
 | 
			
		||||
              group.issue
 | 
			
		||||
            }}</span>
 | 
			
		||||
            <check-social-action
 | 
			
		||||
              v-for="action in group.actions"
 | 
			
		||||
              :key="action.id"
 | 
			
		||||
              :action="action"
 | 
			
		||||
              :selection="socialActionsSelected"
 | 
			
		||||
              @updateSelected="updateActionsSelected"
 | 
			
		||||
            >
 | 
			
		||||
            </check-social-action>
 | 
			
		||||
          </div>
 | 
			
		||||
        </template>
 | 
			
		||||
 | 
			
		||||
                <template
 | 
			
		||||
                    v-else-if="
 | 
			
		||||
                        socialActionsList.length > 0 &&
 | 
			
		||||
                        (socialIssuesSelected.length ||
 | 
			
		||||
                            socialActionsSelected.length)
 | 
			
		||||
                    "
 | 
			
		||||
                >
 | 
			
		||||
                    <div
 | 
			
		||||
                        id="actionsList"
 | 
			
		||||
                        v-for="group in socialActionsList"
 | 
			
		||||
                        :key="group.issue"
 | 
			
		||||
                    >
 | 
			
		||||
                        <span class="badge bg-chill-l-gray text-dark">{{
 | 
			
		||||
                            group.issue
 | 
			
		||||
                        }}</span>
 | 
			
		||||
                        <check-social-action
 | 
			
		||||
                            v-for="action in group.actions"
 | 
			
		||||
                            :key="action.id"
 | 
			
		||||
                            :action="action"
 | 
			
		||||
                            :selection="socialActionsSelected"
 | 
			
		||||
                            @updateSelected="updateActionsSelected"
 | 
			
		||||
                        >
 | 
			
		||||
                        </check-social-action>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <span
 | 
			
		||||
                    v-else-if="
 | 
			
		||||
                        actionAreLoaded && socialActionsList.length === 0
 | 
			
		||||
                    "
 | 
			
		||||
                    class="inline-choice chill-no-data-statement mt-3"
 | 
			
		||||
                >
 | 
			
		||||
                    {{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </teleport>
 | 
			
		||||
        <span
 | 
			
		||||
          v-else-if="actionAreLoaded && socialActionsList.length === 0"
 | 
			
		||||
          class="inline-choice chill-no-data-statement mt-3"
 | 
			
		||||
        >
 | 
			
		||||
          {{ trans(ACTIVITY_SOCIAL_ACTION_LIST_EMPTY) }}
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </teleport>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -106,175 +101,153 @@ import CheckSocialIssue from "./SocialIssuesAcc/CheckSocialIssue.vue";
 | 
			
		||||
import CheckSocialAction from "./SocialIssuesAcc/CheckSocialAction.vue";
 | 
			
		||||
import { getSocialIssues, getSocialActionByIssue } from "../api.js";
 | 
			
		||||
import {
 | 
			
		||||
    ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
 | 
			
		||||
    ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
 | 
			
		||||
    ACTIVITY_SOCIAL_ACTIONS,
 | 
			
		||||
    ACTIVITY_SOCIAL_ISSUES,
 | 
			
		||||
    ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
 | 
			
		||||
    trans,
 | 
			
		||||
  ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
 | 
			
		||||
  ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
 | 
			
		||||
  ACTIVITY_SOCIAL_ACTIONS,
 | 
			
		||||
  ACTIVITY_SOCIAL_ISSUES,
 | 
			
		||||
  ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
 | 
			
		||||
  trans,
 | 
			
		||||
} from "translator";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "SocialIssuesAcc",
 | 
			
		||||
    components: {
 | 
			
		||||
        CheckSocialIssue,
 | 
			
		||||
        CheckSocialAction,
 | 
			
		||||
        VueMultiselect,
 | 
			
		||||
  name: "SocialIssuesAcc",
 | 
			
		||||
  components: {
 | 
			
		||||
    CheckSocialIssue,
 | 
			
		||||
    CheckSocialAction,
 | 
			
		||||
    VueMultiselect,
 | 
			
		||||
  },
 | 
			
		||||
  setup() {
 | 
			
		||||
    return {
 | 
			
		||||
      trans,
 | 
			
		||||
      ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
 | 
			
		||||
      ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
 | 
			
		||||
      ACTIVITY_SOCIAL_ACTIONS,
 | 
			
		||||
      ACTIVITY_SOCIAL_ISSUES,
 | 
			
		||||
      ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      issueIsLoading: false,
 | 
			
		||||
      actionIsLoading: false,
 | 
			
		||||
      actionAreLoaded: false,
 | 
			
		||||
      socialIssuesClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
 | 
			
		||||
      socialActionsClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialActions").getAttribute("required") ? "required" : ""}`,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    socialIssuesList() {
 | 
			
		||||
      return this.$store.state.activity.accompanyingPeriod.socialIssues;
 | 
			
		||||
    },
 | 
			
		||||
    setup() {
 | 
			
		||||
        return {
 | 
			
		||||
            trans,
 | 
			
		||||
            ACTIVITY_SOCIAL_ACTION_LIST_EMPTY,
 | 
			
		||||
            ACTIVITY_SELECT_FIRST_A_SOCIAL_ISSUE,
 | 
			
		||||
            ACTIVITY_SOCIAL_ACTIONS,
 | 
			
		||||
            ACTIVITY_SOCIAL_ISSUES,
 | 
			
		||||
            ACTIVITY_CHOOSE_OTHER_SOCIAL_ISSUE,
 | 
			
		||||
        };
 | 
			
		||||
    socialIssuesSelected() {
 | 
			
		||||
      return this.$store.state.activity.socialIssues;
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            issueIsLoading: false,
 | 
			
		||||
            actionIsLoading: false,
 | 
			
		||||
            actionAreLoaded: false,
 | 
			
		||||
            socialIssuesClassList: {
 | 
			
		||||
                "col-form-label": true,
 | 
			
		||||
                required: false,
 | 
			
		||||
            },
 | 
			
		||||
            socialActionsClassList: {
 | 
			
		||||
                "col-form-label": true,
 | 
			
		||||
                required: false,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    socialIssuesOther() {
 | 
			
		||||
      return this.$store.state.socialIssuesOther;
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        socialIssuesList() {
 | 
			
		||||
            return this.$store.state.activity.accompanyingPeriod.socialIssues;
 | 
			
		||||
        },
 | 
			
		||||
        socialIssuesSelected() {
 | 
			
		||||
            return this.$store.state.activity.socialIssues;
 | 
			
		||||
        },
 | 
			
		||||
        socialIssuesOther() {
 | 
			
		||||
            return this.$store.state.socialIssuesOther;
 | 
			
		||||
        },
 | 
			
		||||
        socialActionsList() {
 | 
			
		||||
            return this.$store.getters.socialActionsListSorted;
 | 
			
		||||
        },
 | 
			
		||||
        socialActionsSelected() {
 | 
			
		||||
            return this.$store.state.activity.socialActions;
 | 
			
		||||
        },
 | 
			
		||||
    socialActionsList() {
 | 
			
		||||
      return this.$store.getters.socialActionsListSorted;
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        /* Load classNames after element is present */
 | 
			
		||||
        const socialActionsEl = document.querySelector(
 | 
			
		||||
            "input#chill_activitybundle_activity_socialActions",
 | 
			
		||||
        );
 | 
			
		||||
        if (socialActionsEl && socialActionsEl.hasAttribute("required")) {
 | 
			
		||||
            this.socialActionsClassList.required = true;
 | 
			
		||||
    socialActionsSelected() {
 | 
			
		||||
      return this.$store.state.activity.socialActions;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    /* Load other issues in multiselect */
 | 
			
		||||
    this.issueIsLoading = true;
 | 
			
		||||
    this.actionAreLoaded = false;
 | 
			
		||||
 | 
			
		||||
    getSocialIssues().then((response) => {
 | 
			
		||||
      /* Add issues to the store */
 | 
			
		||||
      this.$store.commit("updateIssuesOther", response);
 | 
			
		||||
 | 
			
		||||
      /* Add in list the issues already associated (if not yet listed) */
 | 
			
		||||
      this.socialIssuesSelected.forEach((issue) => {
 | 
			
		||||
        if (
 | 
			
		||||
          this.socialIssuesList.filter((i) => i.id === issue.id).length !== 1
 | 
			
		||||
        ) {
 | 
			
		||||
          this.$store.commit("addIssueInList", issue);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
        const socialIssuesEl = document.querySelector(
 | 
			
		||||
            "input#chill_activitybundle_activity_socialIssues",
 | 
			
		||||
        );
 | 
			
		||||
        if (socialIssuesEl && socialIssuesEl.hasAttribute("required")) {
 | 
			
		||||
            this.socialIssuesClassList.required = true;
 | 
			
		||||
        }
 | 
			
		||||
      /* Remove from multiselect the issues that are not yet in the checkbox list */
 | 
			
		||||
      this.socialIssuesList.forEach((issue) => {
 | 
			
		||||
        this.$store.commit("removeIssueInOther", issue);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
        /* Load other issues in multiselect */
 | 
			
		||||
        this.issueIsLoading = true;
 | 
			
		||||
        this.actionAreLoaded = false;
 | 
			
		||||
      /* Filter issues */
 | 
			
		||||
      this.$store.commit("filterList", "issues");
 | 
			
		||||
 | 
			
		||||
        getSocialIssues().then((response) => {
 | 
			
		||||
            /* Add issues to the store */
 | 
			
		||||
            this.$store.commit("updateIssuesOther", response);
 | 
			
		||||
      /* Add in list the actions already associated (if not yet listed) */
 | 
			
		||||
      this.socialActionsSelected.forEach((action) => {
 | 
			
		||||
        this.$store.commit("addActionInList", action);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
            /* Add in list the issues already associated (if not yet listed) */
 | 
			
		||||
            this.socialIssuesSelected.forEach((issue) => {
 | 
			
		||||
                if (
 | 
			
		||||
                    this.socialIssuesList.filter((i) => i.id === issue.id)
 | 
			
		||||
                        .length !== 1
 | 
			
		||||
                ) {
 | 
			
		||||
                    this.$store.commit("addIssueInList", issue);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
      /* Filter actions */
 | 
			
		||||
      this.$store.commit("filterList", "actions");
 | 
			
		||||
 | 
			
		||||
            /* Remove from multiselect the issues that are not yet in the checkbox list */
 | 
			
		||||
            this.socialIssuesList.forEach((issue) => {
 | 
			
		||||
                this.$store.commit("removeIssueInOther", issue);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            /* Filter issues */
 | 
			
		||||
            this.$store.commit("filterList", "issues");
 | 
			
		||||
 | 
			
		||||
            /* Add in list the actions already associated (if not yet listed) */
 | 
			
		||||
            this.socialActionsSelected.forEach((action) => {
 | 
			
		||||
                this.$store.commit("addActionInList", action);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            /* Filter actions */
 | 
			
		||||
            this.$store.commit("filterList", "actions");
 | 
			
		||||
 | 
			
		||||
            this.issueIsLoading = false;
 | 
			
		||||
            this.actionAreLoaded = true;
 | 
			
		||||
            this.updateActionsList();
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        /* When choosing an issue in multiselect, add it in checkboxes (as selected),
 | 
			
		||||
      this.issueIsLoading = false;
 | 
			
		||||
      this.actionAreLoaded = true;
 | 
			
		||||
      this.updateActionsList();
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    /* When choosing an issue in multiselect, add it in checkboxes (as selected),
 | 
			
		||||
         remove it from multiselect, and add socialActions concerned
 | 
			
		||||
      */
 | 
			
		||||
        addIssueInList(value) {
 | 
			
		||||
            //console.log('addIssueInList', value);
 | 
			
		||||
            this.$store.commit("addIssueInList", value);
 | 
			
		||||
            this.$store.commit("removeIssueInOther", value);
 | 
			
		||||
            this.$store.dispatch("addIssueSelected", value);
 | 
			
		||||
            this.updateActionsList();
 | 
			
		||||
        },
 | 
			
		||||
        /* Update value for selected issues checkboxes
 | 
			
		||||
         */
 | 
			
		||||
        updateIssuesSelected(issues) {
 | 
			
		||||
            //console.log('updateIssuesSelected', issues);
 | 
			
		||||
            this.$store.dispatch("updateIssuesSelected", issues);
 | 
			
		||||
            this.updateActionsList();
 | 
			
		||||
        },
 | 
			
		||||
        /* Update value for selected actions checkboxes
 | 
			
		||||
         */
 | 
			
		||||
        updateActionsSelected(actions) {
 | 
			
		||||
            //console.log('updateActionsSelected', actions);
 | 
			
		||||
            this.$store.dispatch("updateActionsSelected", actions);
 | 
			
		||||
        },
 | 
			
		||||
        /* Add socialActions concerned: after reset, loop on each issue selected
 | 
			
		||||
    addIssueInList(value) {
 | 
			
		||||
      //console.log('addIssueInList', value);
 | 
			
		||||
      this.$store.commit("addIssueInList", value);
 | 
			
		||||
      this.$store.commit("removeIssueInOther", value);
 | 
			
		||||
      this.$store.dispatch("addIssueSelected", value);
 | 
			
		||||
      this.updateActionsList();
 | 
			
		||||
    },
 | 
			
		||||
    /* Update value for selected issues checkboxes
 | 
			
		||||
     */
 | 
			
		||||
    updateIssuesSelected(issues) {
 | 
			
		||||
      //console.log('updateIssuesSelected', issues);
 | 
			
		||||
      this.$store.dispatch("updateIssuesSelected", issues);
 | 
			
		||||
      this.updateActionsList();
 | 
			
		||||
    },
 | 
			
		||||
    /* Update value for selected actions checkboxes
 | 
			
		||||
     */
 | 
			
		||||
    updateActionsSelected(actions) {
 | 
			
		||||
      //console.log('updateActionsSelected', actions);
 | 
			
		||||
      this.$store.dispatch("updateActionsSelected", actions);
 | 
			
		||||
    },
 | 
			
		||||
    /* Add socialActions concerned: after reset, loop on each issue selected
 | 
			
		||||
         to get social actions concerned
 | 
			
		||||
      */
 | 
			
		||||
        updateActionsList() {
 | 
			
		||||
            this.resetActionsList();
 | 
			
		||||
            this.socialIssuesSelected.forEach((item) => {
 | 
			
		||||
                this.actionIsLoading = true;
 | 
			
		||||
                getSocialActionByIssue(item.id).then(
 | 
			
		||||
                    (actions) =>
 | 
			
		||||
                        new Promise((resolve) => {
 | 
			
		||||
                            actions.results.forEach((action) => {
 | 
			
		||||
                                this.$store.commit("addActionInList", action);
 | 
			
		||||
                            }, this);
 | 
			
		||||
    updateActionsList() {
 | 
			
		||||
      this.resetActionsList();
 | 
			
		||||
      this.socialIssuesSelected.forEach((item) => {
 | 
			
		||||
        this.actionIsLoading = true;
 | 
			
		||||
        getSocialActionByIssue(item.id).then(
 | 
			
		||||
          (actions) =>
 | 
			
		||||
            new Promise((resolve) => {
 | 
			
		||||
              actions.results.forEach((action) => {
 | 
			
		||||
                this.$store.commit("addActionInList", action);
 | 
			
		||||
              }, this);
 | 
			
		||||
 | 
			
		||||
                            this.$store.commit("filterList", "actions");
 | 
			
		||||
              this.$store.commit("filterList", "actions");
 | 
			
		||||
 | 
			
		||||
                            this.actionIsLoading = false;
 | 
			
		||||
                            this.actionAreLoaded = true;
 | 
			
		||||
                            resolve();
 | 
			
		||||
                        }),
 | 
			
		||||
                );
 | 
			
		||||
            }, this);
 | 
			
		||||
        },
 | 
			
		||||
        /* Reset socialActions List: flush list and restore selected actions
 | 
			
		||||
         */
 | 
			
		||||
        resetActionsList() {
 | 
			
		||||
            this.$store.commit("resetActionsList");
 | 
			
		||||
            this.actionAreLoaded = false;
 | 
			
		||||
            this.socialActionsSelected.forEach((item) => {
 | 
			
		||||
                this.$store.commit("addActionInList", item);
 | 
			
		||||
            }, this);
 | 
			
		||||
        },
 | 
			
		||||
              this.actionIsLoading = false;
 | 
			
		||||
              this.actionAreLoaded = true;
 | 
			
		||||
              resolve();
 | 
			
		||||
            }),
 | 
			
		||||
        );
 | 
			
		||||
      }, this);
 | 
			
		||||
    },
 | 
			
		||||
    /* Reset socialActions List: flush list and restore selected actions
 | 
			
		||||
     */
 | 
			
		||||
    resetActionsList() {
 | 
			
		||||
      this.$store.commit("resetActionsList");
 | 
			
		||||
      this.actionAreLoaded = false;
 | 
			
		||||
      this.socialActionsSelected.forEach((item) => {
 | 
			
		||||
        this.$store.commit("addActionInList", item);
 | 
			
		||||
      }, this);
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@@ -284,18 +257,18 @@ export default {
 | 
			
		||||
@import "ChillMainAssets/chill/scss/chill_variables";
 | 
			
		||||
 | 
			
		||||
span.multiselect__single {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
  display: none !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#actionsList {
 | 
			
		||||
    border-radius: 0.5rem;
 | 
			
		||||
    padding: 1rem;
 | 
			
		||||
    margin: 0.5rem;
 | 
			
		||||
    background-color: whitesmoke;
 | 
			
		||||
  border-radius: 0.5rem;
 | 
			
		||||
  padding: 1rem;
 | 
			
		||||
  margin: 0.5rem;
 | 
			
		||||
  background-color: whitesmoke;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
span.badge {
 | 
			
		||||
    margin-bottom: 0.5rem;
 | 
			
		||||
    @include badge_social($social-issue-color);
 | 
			
		||||
  margin-bottom: 0.5rem;
 | 
			
		||||
  @include badge_social($social-issue-color);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,38 +1,38 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <span class="inline-choice">
 | 
			
		||||
        <div class="form-check">
 | 
			
		||||
            <input
 | 
			
		||||
                class="form-check-input"
 | 
			
		||||
                type="checkbox"
 | 
			
		||||
                v-model="selected"
 | 
			
		||||
                name="action"
 | 
			
		||||
                :id="action.id"
 | 
			
		||||
                :value="action"
 | 
			
		||||
            />
 | 
			
		||||
            <label class="form-check-label" :for="action.id">
 | 
			
		||||
                <span class="badge bg-light text-dark" :title="action.text">{{
 | 
			
		||||
                    action.text
 | 
			
		||||
                }}</span>
 | 
			
		||||
            </label>
 | 
			
		||||
        </div>
 | 
			
		||||
    </span>
 | 
			
		||||
  <span class="inline-choice">
 | 
			
		||||
    <div class="form-check">
 | 
			
		||||
      <input
 | 
			
		||||
        class="form-check-input"
 | 
			
		||||
        type="checkbox"
 | 
			
		||||
        v-model="selected"
 | 
			
		||||
        name="action"
 | 
			
		||||
        :id="action.id"
 | 
			
		||||
        :value="action"
 | 
			
		||||
      />
 | 
			
		||||
      <label class="form-check-label" :for="action.id">
 | 
			
		||||
        <span class="badge bg-light text-dark" :title="action.text">{{
 | 
			
		||||
          action.text
 | 
			
		||||
        }}</span>
 | 
			
		||||
      </label>
 | 
			
		||||
    </div>
 | 
			
		||||
  </span>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    name: "CheckSocialAction",
 | 
			
		||||
    props: ["action", "selection"],
 | 
			
		||||
    emits: ["updateSelected"],
 | 
			
		||||
    computed: {
 | 
			
		||||
        selected: {
 | 
			
		||||
            set(value) {
 | 
			
		||||
                this.$emit("updateSelected", value);
 | 
			
		||||
            },
 | 
			
		||||
            get() {
 | 
			
		||||
                return this.selection;
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
  name: "CheckSocialAction",
 | 
			
		||||
  props: ["action", "selection"],
 | 
			
		||||
  emits: ["updateSelected"],
 | 
			
		||||
  computed: {
 | 
			
		||||
    selected: {
 | 
			
		||||
      set(value) {
 | 
			
		||||
        this.$emit("updateSelected", value);
 | 
			
		||||
      },
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.selection;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@@ -41,13 +41,13 @@ export default {
 | 
			
		||||
@import "ChillPersonAssets/chill/scss/mixins";
 | 
			
		||||
@import "ChillMainAssets/chill/scss/chill_variables";
 | 
			
		||||
span.badge {
 | 
			
		||||
    @include badge_social($social-action-color);
 | 
			
		||||
    font-size: 95%;
 | 
			
		||||
    margin-bottom: 5px;
 | 
			
		||||
    margin-right: 1em;
 | 
			
		||||
    max-width: 100%; /* Adjust as needed */
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
  @include badge_social($social-action-color);
 | 
			
		||||
  font-size: 95%;
 | 
			
		||||
  margin-bottom: 5px;
 | 
			
		||||
  margin-right: 1em;
 | 
			
		||||
  max-width: 100%; /* Adjust as needed */
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,38 +1,36 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <span class="inline-choice">
 | 
			
		||||
        <div class="form-check">
 | 
			
		||||
            <input
 | 
			
		||||
                class="form-check-input"
 | 
			
		||||
                type="checkbox"
 | 
			
		||||
                v-model="selected"
 | 
			
		||||
                name="issue"
 | 
			
		||||
                :id="issue.id"
 | 
			
		||||
                :value="issue"
 | 
			
		||||
            />
 | 
			
		||||
            <label class="form-check-label" :for="issue.id">
 | 
			
		||||
                <span class="badge bg-chill-l-gray text-dark">{{
 | 
			
		||||
                    issue.text
 | 
			
		||||
                }}</span>
 | 
			
		||||
            </label>
 | 
			
		||||
        </div>
 | 
			
		||||
    </span>
 | 
			
		||||
  <span class="inline-choice">
 | 
			
		||||
    <div class="form-check">
 | 
			
		||||
      <input
 | 
			
		||||
        class="form-check-input"
 | 
			
		||||
        type="checkbox"
 | 
			
		||||
        v-model="selected"
 | 
			
		||||
        name="issue"
 | 
			
		||||
        :id="issue.id"
 | 
			
		||||
        :value="issue"
 | 
			
		||||
      />
 | 
			
		||||
      <label class="form-check-label" :for="issue.id">
 | 
			
		||||
        <span class="badge bg-chill-l-gray text-dark">{{ issue.text }}</span>
 | 
			
		||||
      </label>
 | 
			
		||||
    </div>
 | 
			
		||||
  </span>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    name: "CheckSocialIssue",
 | 
			
		||||
    props: ["issue", "selection"],
 | 
			
		||||
    emits: ["updateSelected"],
 | 
			
		||||
    computed: {
 | 
			
		||||
        selected: {
 | 
			
		||||
            set(value) {
 | 
			
		||||
                this.$emit("updateSelected", value);
 | 
			
		||||
            },
 | 
			
		||||
            get() {
 | 
			
		||||
                return this.selection;
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
  name: "CheckSocialIssue",
 | 
			
		||||
  props: ["issue", "selection"],
 | 
			
		||||
  emits: ["updateSelected"],
 | 
			
		||||
  computed: {
 | 
			
		||||
    selected: {
 | 
			
		||||
      set(value) {
 | 
			
		||||
        this.$emit("updateSelected", value);
 | 
			
		||||
      },
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.selection;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@@ -41,9 +39,9 @@ export default {
 | 
			
		||||
@import "ChillPersonAssets/chill/scss/mixins";
 | 
			
		||||
@import "ChillMainAssets/chill/scss/chill_variables";
 | 
			
		||||
span.badge {
 | 
			
		||||
    @include badge_social($social-issue-color);
 | 
			
		||||
    font-size: 95%;
 | 
			
		||||
    margin-bottom: 5px;
 | 
			
		||||
    margin-right: 1em;
 | 
			
		||||
  @include badge_social($social-issue-color);
 | 
			
		||||
  font-size: 95%;
 | 
			
		||||
  margin-bottom: 5px;
 | 
			
		||||
  margin-right: 1em;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -55,6 +55,5 @@
 | 
			
		||||
			</dl>
 | 
			
		||||
 | 
			
		||||
		{% endblock %}
 | 
			
		||||
        {% block content_view_actions_duplicate_link %}{% endblock %}
 | 
			
		||||
	{% endembed %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,76 +1,74 @@
 | 
			
		||||
import { EventInput } from "@fullcalendar/core";
 | 
			
		||||
import {
 | 
			
		||||
    DateTime,
 | 
			
		||||
    Location,
 | 
			
		||||
    User,
 | 
			
		||||
    UserAssociatedInterface,
 | 
			
		||||
  DateTime,
 | 
			
		||||
  Location,
 | 
			
		||||
  User,
 | 
			
		||||
  UserAssociatedInterface,
 | 
			
		||||
} from "../../../ChillMainBundle/Resources/public/types";
 | 
			
		||||
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
 | 
			
		||||
 | 
			
		||||
export interface CalendarRange {
 | 
			
		||||
    id: number;
 | 
			
		||||
    endDate: DateTime;
 | 
			
		||||
    startDate: DateTime;
 | 
			
		||||
    user: User;
 | 
			
		||||
    location: Location;
 | 
			
		||||
    createdAt: DateTime;
 | 
			
		||||
    createdBy: User;
 | 
			
		||||
    updatedAt: DateTime;
 | 
			
		||||
    updatedBy: User;
 | 
			
		||||
  id: number;
 | 
			
		||||
  endDate: DateTime;
 | 
			
		||||
  startDate: DateTime;
 | 
			
		||||
  user: User;
 | 
			
		||||
  location: Location;
 | 
			
		||||
  createdAt: DateTime;
 | 
			
		||||
  createdBy: User;
 | 
			
		||||
  updatedAt: DateTime;
 | 
			
		||||
  updatedBy: User;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CalendarRangeCreate {
 | 
			
		||||
    user: UserAssociatedInterface;
 | 
			
		||||
    startDate: DateTime;
 | 
			
		||||
    endDate: DateTime;
 | 
			
		||||
    location: Location;
 | 
			
		||||
  user: UserAssociatedInterface;
 | 
			
		||||
  startDate: DateTime;
 | 
			
		||||
  endDate: DateTime;
 | 
			
		||||
  location: Location;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CalendarRangeEdit {
 | 
			
		||||
    startDate?: DateTime;
 | 
			
		||||
    endDate?: DateTime;
 | 
			
		||||
    location?: Location;
 | 
			
		||||
  startDate?: DateTime;
 | 
			
		||||
  endDate?: DateTime;
 | 
			
		||||
  location?: Location;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Calendar {
 | 
			
		||||
    id: number;
 | 
			
		||||
  id: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CalendarLight {
 | 
			
		||||
    id: number;
 | 
			
		||||
    endDate: DateTime;
 | 
			
		||||
    startDate: DateTime;
 | 
			
		||||
    mainUser: User;
 | 
			
		||||
    persons: Person[];
 | 
			
		||||
    status: "valid" | "moved" | "canceled";
 | 
			
		||||
  id: number;
 | 
			
		||||
  endDate: DateTime;
 | 
			
		||||
  startDate: DateTime;
 | 
			
		||||
  mainUser: User;
 | 
			
		||||
  persons: Person[];
 | 
			
		||||
  status: "valid" | "moved" | "canceled";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CalendarRemote {
 | 
			
		||||
    id: number;
 | 
			
		||||
    endDate: DateTime;
 | 
			
		||||
    startDate: DateTime;
 | 
			
		||||
    title: string;
 | 
			
		||||
    isAllDay: boolean;
 | 
			
		||||
  id: number;
 | 
			
		||||
  endDate: DateTime;
 | 
			
		||||
  startDate: DateTime;
 | 
			
		||||
  title: string;
 | 
			
		||||
  isAllDay: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type EventInputCalendarRange = EventInput & {
 | 
			
		||||
    id: string;
 | 
			
		||||
    userId: number;
 | 
			
		||||
    userLabel: string;
 | 
			
		||||
    calendarRangeId: number;
 | 
			
		||||
    locationId: number;
 | 
			
		||||
    locationName: string;
 | 
			
		||||
    start: string;
 | 
			
		||||
    end: string;
 | 
			
		||||
    is: "range";
 | 
			
		||||
  id: string;
 | 
			
		||||
  userId: number;
 | 
			
		||||
  userLabel: string;
 | 
			
		||||
  calendarRangeId: number;
 | 
			
		||||
  locationId: number;
 | 
			
		||||
  locationName: string;
 | 
			
		||||
  start: string;
 | 
			
		||||
  end: string;
 | 
			
		||||
  is: "range";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function isEventInputCalendarRange(
 | 
			
		||||
    toBeDetermined: EventInputCalendarRange | EventInput,
 | 
			
		||||
  toBeDetermined: EventInputCalendarRange | EventInput,
 | 
			
		||||
): toBeDetermined is EventInputCalendarRange {
 | 
			
		||||
    return (
 | 
			
		||||
        typeof toBeDetermined.is === "string" && toBeDetermined.is === "range"
 | 
			
		||||
    );
 | 
			
		||||
  return typeof toBeDetermined.is === "string" && toBeDetermined.is === "range";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,166 +1,146 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <teleport to="#mainUser">
 | 
			
		||||
        <h2 class="chill-red">Utilisateur principal</h2>
 | 
			
		||||
        <div>
 | 
			
		||||
            <div>
 | 
			
		||||
                <div v-if="null !== this.$store.getters.getMainUser">
 | 
			
		||||
                    <calendar-active :user="this.$store.getters.getMainUser" />
 | 
			
		||||
                </div>
 | 
			
		||||
                <pick-entity
 | 
			
		||||
                    :multiple="false"
 | 
			
		||||
                    :types="['user']"
 | 
			
		||||
                    :uniqid="'main_user_calendar'"
 | 
			
		||||
                    :picked="
 | 
			
		||||
                        null !== this.$store.getters.getMainUser
 | 
			
		||||
                            ? [this.$store.getters.getMainUser]
 | 
			
		||||
                            : []
 | 
			
		||||
                    "
 | 
			
		||||
                    :removable-if-set="false"
 | 
			
		||||
                    :display-picked="false"
 | 
			
		||||
                    :suggested="this.suggestedUsers"
 | 
			
		||||
                    :label="'main_user'"
 | 
			
		||||
                    @add-new-entity="setMainUser"
 | 
			
		||||
                />
 | 
			
		||||
            </div>
 | 
			
		||||
  <teleport to="#mainUser">
 | 
			
		||||
    <h2 class="chill-red">Utilisateur principal</h2>
 | 
			
		||||
    <div>
 | 
			
		||||
      <div>
 | 
			
		||||
        <div v-if="null !== this.$store.getters.getMainUser">
 | 
			
		||||
          <calendar-active :user="this.$store.getters.getMainUser" />
 | 
			
		||||
        </div>
 | 
			
		||||
    </teleport>
 | 
			
		||||
        <pick-entity
 | 
			
		||||
          :multiple="false"
 | 
			
		||||
          :types="['user']"
 | 
			
		||||
          :uniqid="'main_user_calendar'"
 | 
			
		||||
          :picked="
 | 
			
		||||
            null !== this.$store.getters.getMainUser
 | 
			
		||||
              ? [this.$store.getters.getMainUser]
 | 
			
		||||
              : []
 | 
			
		||||
          "
 | 
			
		||||
          :removable-if-set="false"
 | 
			
		||||
          :display-picked="false"
 | 
			
		||||
          :suggested="this.suggestedUsers"
 | 
			
		||||
          :label="'main_user'"
 | 
			
		||||
          @add-new-entity="setMainUser"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </teleport>
 | 
			
		||||
 | 
			
		||||
    <concerned-groups />
 | 
			
		||||
  <concerned-groups />
 | 
			
		||||
 | 
			
		||||
    <teleport to="#schedule">
 | 
			
		||||
        <div class="row mb-3" v-if="activity.startDate !== null">
 | 
			
		||||
            <label class="col-form-label col-sm-4">Date</label>
 | 
			
		||||
            <div class="col-sm-8">
 | 
			
		||||
                {{ $d(activity.startDate, "long") }} -
 | 
			
		||||
                {{ $d(activity.endDate, "hoursOnly") }}
 | 
			
		||||
                <span v-if="activity.calendarRange === null"
 | 
			
		||||
                    >(Pas de plage de disponibilité sélectionnée)</span
 | 
			
		||||
                >
 | 
			
		||||
                <span v-else>(Une plage de disponibilité sélectionnée)</span>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </teleport>
 | 
			
		||||
 | 
			
		||||
    <location />
 | 
			
		||||
 | 
			
		||||
    <teleport to="#fullCalendar">
 | 
			
		||||
        <div class="calendar-actives">
 | 
			
		||||
            <template v-for="u in getActiveUsers" :key="u.id">
 | 
			
		||||
                <calendar-active
 | 
			
		||||
                    :user="u"
 | 
			
		||||
                    :invite="this.$store.getters.getInviteForUser(u)"
 | 
			
		||||
                />
 | 
			
		||||
            </template>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div
 | 
			
		||||
            class="display-options row justify-content-between"
 | 
			
		||||
            style="margin-top: 1rem"
 | 
			
		||||
  <teleport to="#schedule">
 | 
			
		||||
    <div class="row mb-3" v-if="activity.startDate !== null">
 | 
			
		||||
      <label class="col-form-label col-sm-4">Date</label>
 | 
			
		||||
      <div class="col-sm-8">
 | 
			
		||||
        {{ $d(activity.startDate, "long") }} -
 | 
			
		||||
        {{ $d(activity.endDate, "hoursOnly") }}
 | 
			
		||||
        <span v-if="activity.calendarRange === null"
 | 
			
		||||
          >(Pas de plage de disponibilité sélectionnée)</span
 | 
			
		||||
        >
 | 
			
		||||
            <div class="col-sm-9 col-xs-12">
 | 
			
		||||
                <div class="input-group mb-3">
 | 
			
		||||
                    <label class="input-group-text" for="slotDuration"
 | 
			
		||||
                        >Durée des créneaux</label
 | 
			
		||||
                    >
 | 
			
		||||
                    <select
 | 
			
		||||
                        v-model="slotDuration"
 | 
			
		||||
                        id="slotDuration"
 | 
			
		||||
                        class="form-select"
 | 
			
		||||
                    >
 | 
			
		||||
                        <option value="00:05:00">5 minutes</option>
 | 
			
		||||
                        <option value="00:10:00">10 minutes</option>
 | 
			
		||||
                        <option value="00:15:00">15 minutes</option>
 | 
			
		||||
                        <option value="00:30:00">30 minutes</option>
 | 
			
		||||
                        <option value="00:45:00">45 minutes</option>
 | 
			
		||||
                        <option value="00:60:00">60 minutes</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                    <label class="input-group-text" for="slotMinTime">De</label>
 | 
			
		||||
                    <select
 | 
			
		||||
                        v-model="slotMinTime"
 | 
			
		||||
                        id="slotMinTime"
 | 
			
		||||
                        class="form-select"
 | 
			
		||||
                    >
 | 
			
		||||
                        <option value="00:00:00">0h</option>
 | 
			
		||||
                        <option value="01:00:00">1h</option>
 | 
			
		||||
                        <option value="02:00:00">2h</option>
 | 
			
		||||
                        <option value="03:00:00">3h</option>
 | 
			
		||||
                        <option value="04:00:00">4h</option>
 | 
			
		||||
                        <option value="05:00:00">5h</option>
 | 
			
		||||
                        <option value="06:00:00">6h</option>
 | 
			
		||||
                        <option value="07:00:00">7h</option>
 | 
			
		||||
                        <option value="08:00:00">8h</option>
 | 
			
		||||
                        <option value="09:00:00">9h</option>
 | 
			
		||||
                        <option value="10:00:00">10h</option>
 | 
			
		||||
                        <option value="11:00:00">11h</option>
 | 
			
		||||
                        <option value="12:00:00">12h</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                    <label class="input-group-text" for="slotMaxTime">À</label>
 | 
			
		||||
                    <select
 | 
			
		||||
                        v-model="slotMaxTime"
 | 
			
		||||
                        id="slotMaxTime"
 | 
			
		||||
                        class="form-select"
 | 
			
		||||
                    >
 | 
			
		||||
                        <option value="12:00:00">12h</option>
 | 
			
		||||
                        <option value="13:00:00">13h</option>
 | 
			
		||||
                        <option value="14:00:00">14h</option>
 | 
			
		||||
                        <option value="15:00:00">15h</option>
 | 
			
		||||
                        <option value="16:00:00">16h</option>
 | 
			
		||||
                        <option value="17:00:00">17h</option>
 | 
			
		||||
                        <option value="18:00:00">18h</option>
 | 
			
		||||
                        <option value="19:00:00">19h</option>
 | 
			
		||||
                        <option value="20:00:00">20h</option>
 | 
			
		||||
                        <option value="21:00:00">21h</option>
 | 
			
		||||
                        <option value="22:00:00">22h</option>
 | 
			
		||||
                        <option value="23:00:00">23h</option>
 | 
			
		||||
                        <option value="23:59:59">24h</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="col-sm-3 col-xs-12">
 | 
			
		||||
                <div class="float-end">
 | 
			
		||||
                    <div class="form-check input-group">
 | 
			
		||||
                        <span class="input-group-text">
 | 
			
		||||
                            <input
 | 
			
		||||
                                id="showHideWE"
 | 
			
		||||
                                class="mt-0"
 | 
			
		||||
                                type="checkbox"
 | 
			
		||||
                                v-model="hideWeekends"
 | 
			
		||||
                            />
 | 
			
		||||
                        </span>
 | 
			
		||||
                        <label
 | 
			
		||||
                            for="showHideWE"
 | 
			
		||||
                            class="form-check-label input-group-text"
 | 
			
		||||
                            >Week-ends</label
 | 
			
		||||
                        >
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        <span v-else>(Une plage de disponibilité sélectionnée)</span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </teleport>
 | 
			
		||||
 | 
			
		||||
  <location />
 | 
			
		||||
 | 
			
		||||
  <teleport to="#fullCalendar">
 | 
			
		||||
    <div class="calendar-actives">
 | 
			
		||||
      <template v-for="u in getActiveUsers" :key="u.id">
 | 
			
		||||
        <calendar-active
 | 
			
		||||
          :user="u"
 | 
			
		||||
          :invite="this.$store.getters.getInviteForUser(u)"
 | 
			
		||||
        />
 | 
			
		||||
      </template>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div
 | 
			
		||||
      class="display-options row justify-content-between"
 | 
			
		||||
      style="margin-top: 1rem"
 | 
			
		||||
    >
 | 
			
		||||
      <div class="col-sm-9 col-xs-12">
 | 
			
		||||
        <div class="input-group mb-3">
 | 
			
		||||
          <label class="input-group-text" for="slotDuration"
 | 
			
		||||
            >Durée des créneaux</label
 | 
			
		||||
          >
 | 
			
		||||
          <select v-model="slotDuration" id="slotDuration" class="form-select">
 | 
			
		||||
            <option value="00:05:00">5 minutes</option>
 | 
			
		||||
            <option value="00:10:00">10 minutes</option>
 | 
			
		||||
            <option value="00:15:00">15 minutes</option>
 | 
			
		||||
            <option value="00:30:00">30 minutes</option>
 | 
			
		||||
          </select>
 | 
			
		||||
          <label class="input-group-text" for="slotMinTime">De</label>
 | 
			
		||||
          <select v-model="slotMinTime" id="slotMinTime" class="form-select">
 | 
			
		||||
            <option value="00:00:00">0h</option>
 | 
			
		||||
            <option value="01:00:00">1h</option>
 | 
			
		||||
            <option value="02:00:00">2h</option>
 | 
			
		||||
            <option value="03:00:00">3h</option>
 | 
			
		||||
            <option value="04:00:00">4h</option>
 | 
			
		||||
            <option value="05:00:00">5h</option>
 | 
			
		||||
            <option value="06:00:00">6h</option>
 | 
			
		||||
            <option value="07:00:00">7h</option>
 | 
			
		||||
            <option value="08:00:00">8h</option>
 | 
			
		||||
            <option value="09:00:00">9h</option>
 | 
			
		||||
            <option value="10:00:00">10h</option>
 | 
			
		||||
            <option value="11:00:00">11h</option>
 | 
			
		||||
            <option value="12:00:00">12h</option>
 | 
			
		||||
          </select>
 | 
			
		||||
          <label class="input-group-text" for="slotMaxTime">À</label>
 | 
			
		||||
          <select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
 | 
			
		||||
            <option value="12:00:00">12h</option>
 | 
			
		||||
            <option value="13:00:00">13h</option>
 | 
			
		||||
            <option value="14:00:00">14h</option>
 | 
			
		||||
            <option value="15:00:00">15h</option>
 | 
			
		||||
            <option value="16:00:00">16h</option>
 | 
			
		||||
            <option value="17:00:00">17h</option>
 | 
			
		||||
            <option value="18:00:00">18h</option>
 | 
			
		||||
            <option value="19:00:00">19h</option>
 | 
			
		||||
            <option value="20:00:00">20h</option>
 | 
			
		||||
            <option value="21:00:00">21h</option>
 | 
			
		||||
            <option value="22:00:00">22h</option>
 | 
			
		||||
            <option value="23:00:00">23h</option>
 | 
			
		||||
            <option value="23:59:59">24h</option>
 | 
			
		||||
          </select>
 | 
			
		||||
        </div>
 | 
			
		||||
        <FullCalendar ref="fullCalendar" :options="calendarOptions">
 | 
			
		||||
            <template #eventContent="arg">
 | 
			
		||||
                <span>
 | 
			
		||||
                    <b v-if="arg.event.extendedProps.is === 'remote'">{{
 | 
			
		||||
                        arg.event.title
 | 
			
		||||
                    }}</b>
 | 
			
		||||
                    <b v-else-if="arg.event.extendedProps.is === 'range'"
 | 
			
		||||
                        >{{ arg.timeText }}
 | 
			
		||||
                        {{ arg.event.extendedProps.locationName }}
 | 
			
		||||
                        <small>{{
 | 
			
		||||
                            arg.event.extendedProps.userLabel
 | 
			
		||||
                        }}</small></b
 | 
			
		||||
                    >
 | 
			
		||||
                    <b v-else-if="arg.event.extendedProps.is === 'current'"
 | 
			
		||||
                        >{{ arg.timeText }} {{ $t("current_selected") }}
 | 
			
		||||
                    </b>
 | 
			
		||||
                    <b v-else-if="arg.event.extendedProps.is === 'local'">{{
 | 
			
		||||
                        arg.event.title
 | 
			
		||||
                    }}</b>
 | 
			
		||||
                    <b v-else
 | 
			
		||||
                        >{{ arg.timeText }} {{ $t("current_selected") }}
 | 
			
		||||
                    </b>
 | 
			
		||||
                </span>
 | 
			
		||||
            </template>
 | 
			
		||||
        </FullCalendar>
 | 
			
		||||
    </teleport>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="col-sm-3 col-xs-12">
 | 
			
		||||
        <div class="float-end">
 | 
			
		||||
          <div class="form-check input-group">
 | 
			
		||||
            <span class="input-group-text">
 | 
			
		||||
              <input
 | 
			
		||||
                id="showHideWE"
 | 
			
		||||
                class="mt-0"
 | 
			
		||||
                type="checkbox"
 | 
			
		||||
                v-model="hideWeekends"
 | 
			
		||||
              />
 | 
			
		||||
            </span>
 | 
			
		||||
            <label for="showHideWE" class="form-check-label input-group-text"
 | 
			
		||||
              >Week-ends</label
 | 
			
		||||
            >
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <FullCalendar ref="fullCalendar" :options="calendarOptions">
 | 
			
		||||
      <template #eventContent="arg">
 | 
			
		||||
        <span>
 | 
			
		||||
          <b v-if="arg.event.extendedProps.is === 'remote'">{{
 | 
			
		||||
            arg.event.title
 | 
			
		||||
          }}</b>
 | 
			
		||||
          <b v-else-if="arg.event.extendedProps.is === 'range'"
 | 
			
		||||
            >{{ arg.timeText }}
 | 
			
		||||
            {{ arg.event.extendedProps.locationName }}
 | 
			
		||||
            <small>{{ arg.event.extendedProps.userLabel }}</small></b
 | 
			
		||||
          >
 | 
			
		||||
          <b v-else-if="arg.event.extendedProps.is === 'current'"
 | 
			
		||||
            >{{ arg.timeText }} {{ $t("current_selected") }}
 | 
			
		||||
          </b>
 | 
			
		||||
          <b v-else-if="arg.event.extendedProps.is === 'local'">{{
 | 
			
		||||
            arg.event.title
 | 
			
		||||
          }}</b>
 | 
			
		||||
          <b v-else>{{ arg.timeText }} {{ $t("current_selected") }} </b>
 | 
			
		||||
        </span>
 | 
			
		||||
      </template>
 | 
			
		||||
    </FullCalendar>
 | 
			
		||||
  </teleport>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -177,219 +157,210 @@ import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
 | 
			
		||||
import { mapGetters, mapState } from "vuex";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "App",
 | 
			
		||||
    components: {
 | 
			
		||||
        ConcernedGroups,
 | 
			
		||||
        Location,
 | 
			
		||||
        FullCalendar,
 | 
			
		||||
        CalendarActive,
 | 
			
		||||
        PickEntity,
 | 
			
		||||
  name: "App",
 | 
			
		||||
  components: {
 | 
			
		||||
    ConcernedGroups,
 | 
			
		||||
    Location,
 | 
			
		||||
    FullCalendar,
 | 
			
		||||
    CalendarActive,
 | 
			
		||||
    PickEntity,
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      errorMsg: [],
 | 
			
		||||
      showMyCalendar: false,
 | 
			
		||||
      slotDuration: "00:05:00",
 | 
			
		||||
      slotMinTime: "09:00:00",
 | 
			
		||||
      slotMaxTime: "18:00:00",
 | 
			
		||||
      hideWeekEnds: true,
 | 
			
		||||
      previousUser: [],
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    ...mapGetters(["getMainUser"]),
 | 
			
		||||
    ...mapState(["activity"]),
 | 
			
		||||
    events() {
 | 
			
		||||
      return this.$store.getters.getEventSources;
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            errorMsg: [],
 | 
			
		||||
            showMyCalendar: false,
 | 
			
		||||
            slotDuration: "00:05:00",
 | 
			
		||||
            slotMinTime: "09:00:00",
 | 
			
		||||
            slotMaxTime: "18:00:00",
 | 
			
		||||
            hideWeekEnds: true,
 | 
			
		||||
            previousUser: [],
 | 
			
		||||
        };
 | 
			
		||||
    calendarOptions() {
 | 
			
		||||
      return {
 | 
			
		||||
        locale: frLocale,
 | 
			
		||||
        plugins: [
 | 
			
		||||
          dayGridPlugin,
 | 
			
		||||
          interactionPlugin,
 | 
			
		||||
          timeGridPlugin,
 | 
			
		||||
          dayGridPlugin,
 | 
			
		||||
          listPlugin,
 | 
			
		||||
        ],
 | 
			
		||||
        initialView: "timeGridWeek",
 | 
			
		||||
        initialDate: this.$store.getters.getInitialDate,
 | 
			
		||||
        eventSources: this.events,
 | 
			
		||||
        selectable: true,
 | 
			
		||||
        slotMinTime: this.slotMinTime,
 | 
			
		||||
        slotMaxTime: this.slotMaxTime,
 | 
			
		||||
        scrollTimeReset: false,
 | 
			
		||||
        datesSet: this.onDatesSet,
 | 
			
		||||
        select: this.onDateSelect,
 | 
			
		||||
        eventChange: this.onEventChange,
 | 
			
		||||
        eventClick: this.onEventClick,
 | 
			
		||||
        selectMirror: true,
 | 
			
		||||
        editable: true,
 | 
			
		||||
        weekends: !this.hideWeekEnds,
 | 
			
		||||
        headerToolbar: {
 | 
			
		||||
          left: "prev,next today",
 | 
			
		||||
          center: "title",
 | 
			
		||||
          right: "timeGridWeek,timeGridDay,listWeek",
 | 
			
		||||
        },
 | 
			
		||||
        views: {
 | 
			
		||||
          timeGrid: {
 | 
			
		||||
            slotEventOverlap: false,
 | 
			
		||||
            slotDuration: this.slotDuration,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        ...mapGetters(["getMainUser"]),
 | 
			
		||||
        ...mapState(["activity"]),
 | 
			
		||||
        events() {
 | 
			
		||||
            return this.$store.getters.getEventSources;
 | 
			
		||||
        },
 | 
			
		||||
        calendarOptions() {
 | 
			
		||||
            return {
 | 
			
		||||
                locale: frLocale,
 | 
			
		||||
                plugins: [
 | 
			
		||||
                    dayGridPlugin,
 | 
			
		||||
                    interactionPlugin,
 | 
			
		||||
                    timeGridPlugin,
 | 
			
		||||
                    dayGridPlugin,
 | 
			
		||||
                    listPlugin,
 | 
			
		||||
                ],
 | 
			
		||||
                initialView: "timeGridWeek",
 | 
			
		||||
                initialDate: this.$store.getters.getInitialDate,
 | 
			
		||||
                eventSources: this.events,
 | 
			
		||||
                selectable: true,
 | 
			
		||||
                slotMinTime: this.slotMinTime,
 | 
			
		||||
                slotMaxTime: this.slotMaxTime,
 | 
			
		||||
                scrollTimeReset: false,
 | 
			
		||||
                datesSet: this.onDatesSet,
 | 
			
		||||
                select: this.onDateSelect,
 | 
			
		||||
                eventChange: this.onEventChange,
 | 
			
		||||
                eventClick: this.onEventClick,
 | 
			
		||||
                selectMirror: true,
 | 
			
		||||
                editable: true,
 | 
			
		||||
                weekends: !this.hideWeekEnds,
 | 
			
		||||
                headerToolbar: {
 | 
			
		||||
                    left: "prev,next today",
 | 
			
		||||
                    center: "title",
 | 
			
		||||
                    right: "timeGridWeek,timeGridDay,listWeek",
 | 
			
		||||
                },
 | 
			
		||||
                views: {
 | 
			
		||||
                    timeGrid: {
 | 
			
		||||
                        slotEventOverlap: false,
 | 
			
		||||
                        slotDuration: this.slotDuration,
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        getActiveUsers() {
 | 
			
		||||
            const users = [];
 | 
			
		||||
            for (const id of this.$store.state.currentView.users.keys()) {
 | 
			
		||||
                users.push(this.$store.getters.getUserDataById(id).user);
 | 
			
		||||
            }
 | 
			
		||||
            return users;
 | 
			
		||||
        },
 | 
			
		||||
        suggestedUsers() {
 | 
			
		||||
            const suggested = [];
 | 
			
		||||
 | 
			
		||||
            this.$data.previousUser.forEach((u) => {
 | 
			
		||||
                if (u.id !== this.$store.getters.getMainUser.id) {
 | 
			
		||||
                    suggested.push(u);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return suggested;
 | 
			
		||||
        },
 | 
			
		||||
    getActiveUsers() {
 | 
			
		||||
      const users = [];
 | 
			
		||||
      for (const id of this.$store.state.currentView.users.keys()) {
 | 
			
		||||
        users.push(this.$store.getters.getUserDataById(id).user);
 | 
			
		||||
      }
 | 
			
		||||
      return users;
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        setMainUser({ entity }) {
 | 
			
		||||
            const user = entity;
 | 
			
		||||
            console.log("setMainUser APP", entity);
 | 
			
		||||
    suggestedUsers() {
 | 
			
		||||
      const suggested = [];
 | 
			
		||||
 | 
			
		||||
            if (
 | 
			
		||||
                user.id !== this.$store.getters.getMainUser &&
 | 
			
		||||
                (this.$store.state.activity.calendarRange !== null ||
 | 
			
		||||
                    this.$store.state.activity.startDate !== null ||
 | 
			
		||||
                    this.$store.state.activity.endDate !== null)
 | 
			
		||||
            ) {
 | 
			
		||||
                if (
 | 
			
		||||
                    !window.confirm(
 | 
			
		||||
                        this.$t("change_main_user_will_reset_event_data"),
 | 
			
		||||
                    )
 | 
			
		||||
                ) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
      this.$data.previousUser.forEach((u) => {
 | 
			
		||||
        if (u.id !== this.$store.getters.getMainUser.id) {
 | 
			
		||||
          suggested.push(u);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
            // add the previous user, if any, in the previous user list (in use for suggestion)
 | 
			
		||||
            if (null !== this.$store.getters.getMainUser) {
 | 
			
		||||
                const suggestedUids = new Set(
 | 
			
		||||
                    this.$data.previousUser.map((u) => u.id),
 | 
			
		||||
                );
 | 
			
		||||
                if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
 | 
			
		||||
                    this.$data.previousUser.push(
 | 
			
		||||
                        this.$store.getters.getMainUser,
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.$store.dispatch("setMainUser", user);
 | 
			
		||||
            this.$store.commit("showUserOnCalendar", {
 | 
			
		||||
                user,
 | 
			
		||||
                ranges: true,
 | 
			
		||||
                remotes: true,
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        removeMainUser(user) {
 | 
			
		||||
            console.log("removeMainUser APP", user);
 | 
			
		||||
 | 
			
		||||
            window.alert(this.$t("main_user_is_mandatory"));
 | 
			
		||||
            return;
 | 
			
		||||
        },
 | 
			
		||||
        onDatesSet(event) {
 | 
			
		||||
            console.log("onDatesSet", event);
 | 
			
		||||
            this.$store.dispatch("setCurrentDatesView", {
 | 
			
		||||
                start: event.start,
 | 
			
		||||
                end: event.end,
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        onDateSelect(payload) {
 | 
			
		||||
            console.log("onDateSelect", payload);
 | 
			
		||||
 | 
			
		||||
            // show an alert if changing mainUser
 | 
			
		||||
            if (
 | 
			
		||||
                (this.$store.getters.getMainUser !== null &&
 | 
			
		||||
                    this.$store.state.me.id !==
 | 
			
		||||
                        this.$store.getters.getMainUser.id) ||
 | 
			
		||||
                this.$store.getters.getMainUser === null
 | 
			
		||||
            ) {
 | 
			
		||||
                if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
 | 
			
		||||
                    return;
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.$store.commit("showUserOnCalendar", {
 | 
			
		||||
                        user: this.$store.state.me,
 | 
			
		||||
                        remotes: true,
 | 
			
		||||
                        ranges: true,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.$store.dispatch("setEventTimes", {
 | 
			
		||||
                start: payload.start,
 | 
			
		||||
                end: payload.end,
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        onEventChange(payload) {
 | 
			
		||||
            console.log("onEventChange", payload);
 | 
			
		||||
            if (this.$store.state.activity.calendarRange !== null) {
 | 
			
		||||
                throw new Error(
 | 
			
		||||
                    "not allowed to edit a calendar associated with a calendar range",
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            this.$store.dispatch("setEventTimes", {
 | 
			
		||||
                start: payload.event.start,
 | 
			
		||||
                end: payload.event.end,
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        onEventClick(payload) {
 | 
			
		||||
            if (payload.event.extendedProps.is !== "range") {
 | 
			
		||||
                // do nothing when clicking on remote
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // show an alert if changing mainUser
 | 
			
		||||
            if (
 | 
			
		||||
                this.$store.getters.getMainUser !== null &&
 | 
			
		||||
                payload.event.extendedProps.userId !==
 | 
			
		||||
                    this.$store.getters.getMainUser.id
 | 
			
		||||
            ) {
 | 
			
		||||
                if (
 | 
			
		||||
                    !window.confirm(
 | 
			
		||||
                        this.$t("this_calendar_range_will_change_main_user"),
 | 
			
		||||
                    )
 | 
			
		||||
                ) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.$store.dispatch("associateCalendarToRange", {
 | 
			
		||||
                range: payload.event,
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
      return suggested;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    setMainUser({ entity }) {
 | 
			
		||||
      const user = entity;
 | 
			
		||||
      console.log("setMainUser APP", entity);
 | 
			
		||||
 | 
			
		||||
      if (
 | 
			
		||||
        user.id !== this.$store.getters.getMainUser &&
 | 
			
		||||
        (this.$store.state.activity.calendarRange !== null ||
 | 
			
		||||
          this.$store.state.activity.startDate !== null ||
 | 
			
		||||
          this.$store.state.activity.endDate !== null)
 | 
			
		||||
      ) {
 | 
			
		||||
        if (
 | 
			
		||||
          !window.confirm(this.$t("change_main_user_will_reset_event_data"))
 | 
			
		||||
        ) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // add the previous user, if any, in the previous user list (in use for suggestion)
 | 
			
		||||
      if (null !== this.$store.getters.getMainUser) {
 | 
			
		||||
        const suggestedUids = new Set(this.$data.previousUser.map((u) => u.id));
 | 
			
		||||
        if (!suggestedUids.has(this.$store.getters.getMainUser.id)) {
 | 
			
		||||
          this.$data.previousUser.push(this.$store.getters.getMainUser);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.$store.dispatch("setMainUser", user);
 | 
			
		||||
      this.$store.commit("showUserOnCalendar", {
 | 
			
		||||
        user,
 | 
			
		||||
        ranges: true,
 | 
			
		||||
        remotes: true,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    removeMainUser(user) {
 | 
			
		||||
      console.log("removeMainUser APP", user);
 | 
			
		||||
 | 
			
		||||
      window.alert(this.$t("main_user_is_mandatory"));
 | 
			
		||||
      return;
 | 
			
		||||
    },
 | 
			
		||||
    onDatesSet(event) {
 | 
			
		||||
      console.log("onDatesSet", event);
 | 
			
		||||
      this.$store.dispatch("setCurrentDatesView", {
 | 
			
		||||
        start: event.start,
 | 
			
		||||
        end: event.end,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    onDateSelect(payload) {
 | 
			
		||||
      console.log("onDateSelect", payload);
 | 
			
		||||
 | 
			
		||||
      // show an alert if changing mainUser
 | 
			
		||||
      if (
 | 
			
		||||
        (this.$store.getters.getMainUser !== null &&
 | 
			
		||||
          this.$store.state.me.id !== this.$store.getters.getMainUser.id) ||
 | 
			
		||||
        this.$store.getters.getMainUser === null
 | 
			
		||||
      ) {
 | 
			
		||||
        if (!window.confirm(this.$t("will_change_main_user_for_me"))) {
 | 
			
		||||
          return;
 | 
			
		||||
        } else {
 | 
			
		||||
          this.$store.commit("showUserOnCalendar", {
 | 
			
		||||
            user: this.$store.state.me,
 | 
			
		||||
            remotes: true,
 | 
			
		||||
            ranges: true,
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.$store.dispatch("setEventTimes", {
 | 
			
		||||
        start: payload.start,
 | 
			
		||||
        end: payload.end,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    onEventChange(payload) {
 | 
			
		||||
      console.log("onEventChange", payload);
 | 
			
		||||
      if (this.$store.state.activity.calendarRange !== null) {
 | 
			
		||||
        throw new Error(
 | 
			
		||||
          "not allowed to edit a calendar associated with a calendar range",
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      this.$store.dispatch("setEventTimes", {
 | 
			
		||||
        start: payload.event.start,
 | 
			
		||||
        end: payload.event.end,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    onEventClick(payload) {
 | 
			
		||||
      if (payload.event.extendedProps.is !== "range") {
 | 
			
		||||
        // do nothing when clicking on remote
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // show an alert if changing mainUser
 | 
			
		||||
      if (
 | 
			
		||||
        this.$store.getters.getMainUser !== null &&
 | 
			
		||||
        payload.event.extendedProps.userId !==
 | 
			
		||||
          this.$store.getters.getMainUser.id
 | 
			
		||||
      ) {
 | 
			
		||||
        if (
 | 
			
		||||
          !window.confirm(this.$t("this_calendar_range_will_change_main_user"))
 | 
			
		||||
        ) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.$store.dispatch("associateCalendarToRange", {
 | 
			
		||||
        range: payload.event,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.calendar-actives {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.display-options {
 | 
			
		||||
    margin-top: 1rem;
 | 
			
		||||
  margin-top: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* for events which are range */
 | 
			
		||||
.fc-event.isrange {
 | 
			
		||||
    border-width: 3px;
 | 
			
		||||
  border-width: 3px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,119 +1,105 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div :style="style" class="calendar-active">
 | 
			
		||||
        <span class="badge-user">
 | 
			
		||||
            {{ user.text }}
 | 
			
		||||
            <template v-if="invite !== null">
 | 
			
		||||
                <i v-if="invite.status === 'accepted'" class="fa fa-check" />
 | 
			
		||||
                <i
 | 
			
		||||
                    v-else-if="invite.status === 'declined'"
 | 
			
		||||
                    class="fa fa-times"
 | 
			
		||||
                />
 | 
			
		||||
                <i
 | 
			
		||||
                    v-else-if="invite.status === 'pending'"
 | 
			
		||||
                    class="fa fa-question-o"
 | 
			
		||||
                />
 | 
			
		||||
                <i
 | 
			
		||||
                    v-else-if="invite.status === 'tentative'"
 | 
			
		||||
                    class="fa fa-question"
 | 
			
		||||
                />
 | 
			
		||||
                <span v-else="">{{ invite.status }}</span>
 | 
			
		||||
            </template>
 | 
			
		||||
        </span>
 | 
			
		||||
        <span class="form-check-inline form-switch">
 | 
			
		||||
            <input
 | 
			
		||||
                class="form-check-input"
 | 
			
		||||
                type="checkbox"
 | 
			
		||||
                id="flexSwitchCheckDefault"
 | 
			
		||||
                v-model="rangeShow"
 | 
			
		||||
            />
 | 
			
		||||
             <label
 | 
			
		||||
                class="form-check-label"
 | 
			
		||||
                for="flexSwitchCheckDefault"
 | 
			
		||||
                title="Disponibilités"
 | 
			
		||||
                ><i class="fa fa-calendar-check-o"
 | 
			
		||||
            /></label>
 | 
			
		||||
        </span>
 | 
			
		||||
        <span class="form-check-inline form-switch">
 | 
			
		||||
            <input
 | 
			
		||||
                class="form-check-input"
 | 
			
		||||
                type="checkbox"
 | 
			
		||||
                id="flexSwitchCheckDefault"
 | 
			
		||||
                v-model="remoteShow"
 | 
			
		||||
            />
 | 
			
		||||
             <label
 | 
			
		||||
                class="form-check-label"
 | 
			
		||||
                for="flexSwitchCheckDefault"
 | 
			
		||||
                title="Agenda"
 | 
			
		||||
                ><i class="fa fa-calendar"
 | 
			
		||||
            /></label>
 | 
			
		||||
        </span>
 | 
			
		||||
    </div>
 | 
			
		||||
  <div :style="style" class="calendar-active">
 | 
			
		||||
    <span class="badge-user">
 | 
			
		||||
      {{ user.text }}
 | 
			
		||||
      <template v-if="invite !== null">
 | 
			
		||||
        <i v-if="invite.status === 'accepted'" class="fa fa-check" />
 | 
			
		||||
        <i v-else-if="invite.status === 'declined'" class="fa fa-times" />
 | 
			
		||||
        <i v-else-if="invite.status === 'pending'" class="fa fa-question-o" />
 | 
			
		||||
        <i v-else-if="invite.status === 'tentative'" class="fa fa-question" />
 | 
			
		||||
        <span v-else="">{{ invite.status }}</span>
 | 
			
		||||
      </template>
 | 
			
		||||
    </span>
 | 
			
		||||
    <span class="form-check-inline form-switch">
 | 
			
		||||
      <input
 | 
			
		||||
        class="form-check-input"
 | 
			
		||||
        type="checkbox"
 | 
			
		||||
        id="flexSwitchCheckDefault"
 | 
			
		||||
        v-model="rangeShow"
 | 
			
		||||
      />
 | 
			
		||||
       <label
 | 
			
		||||
        class="form-check-label"
 | 
			
		||||
        for="flexSwitchCheckDefault"
 | 
			
		||||
        title="Disponibilités"
 | 
			
		||||
        ><i class="fa fa-calendar-check-o"
 | 
			
		||||
      /></label>
 | 
			
		||||
    </span>
 | 
			
		||||
    <span class="form-check-inline form-switch">
 | 
			
		||||
      <input
 | 
			
		||||
        class="form-check-input"
 | 
			
		||||
        type="checkbox"
 | 
			
		||||
        id="flexSwitchCheckDefault"
 | 
			
		||||
        v-model="remoteShow"
 | 
			
		||||
      />
 | 
			
		||||
       <label
 | 
			
		||||
        class="form-check-label"
 | 
			
		||||
        for="flexSwitchCheckDefault"
 | 
			
		||||
        title="Agenda"
 | 
			
		||||
        ><i class="fa fa-calendar"
 | 
			
		||||
      /></label>
 | 
			
		||||
    </span>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { mapGetters } from "vuex";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "CalendarActive",
 | 
			
		||||
    props: {
 | 
			
		||||
        user: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            required: true,
 | 
			
		||||
        },
 | 
			
		||||
        invite: {
 | 
			
		||||
            type: Object,
 | 
			
		||||
            required: false,
 | 
			
		||||
            default: null,
 | 
			
		||||
        },
 | 
			
		||||
  name: "CalendarActive",
 | 
			
		||||
  props: {
 | 
			
		||||
    user: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      required: true,
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        style() {
 | 
			
		||||
            return {
 | 
			
		||||
                backgroundColor: this.$store.getters.getUserData(this.user)
 | 
			
		||||
                    .mainColor,
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        rangeShow: {
 | 
			
		||||
            set(value) {
 | 
			
		||||
                this.$store.commit("showUserOnCalendar", {
 | 
			
		||||
                    user: this.user,
 | 
			
		||||
                    ranges: value,
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
            get() {
 | 
			
		||||
                return this.$store.getters.isRangeShownOnCalendarForUser(
 | 
			
		||||
                    this.user,
 | 
			
		||||
                );
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        remoteShow: {
 | 
			
		||||
            set(value) {
 | 
			
		||||
                this.$store.commit("showUserOnCalendar", {
 | 
			
		||||
                    user: this.user,
 | 
			
		||||
                    remotes: value,
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
            get() {
 | 
			
		||||
                return this.$store.getters.isRemoteShownOnCalendarForUser(
 | 
			
		||||
                    this.user,
 | 
			
		||||
                );
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    invite: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      required: false,
 | 
			
		||||
      default: null,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    style() {
 | 
			
		||||
      return {
 | 
			
		||||
        backgroundColor: this.$store.getters.getUserData(this.user).mainColor,
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    rangeShow: {
 | 
			
		||||
      set(value) {
 | 
			
		||||
        this.$store.commit("showUserOnCalendar", {
 | 
			
		||||
          user: this.user,
 | 
			
		||||
          ranges: value,
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.$store.getters.isRangeShownOnCalendarForUser(this.user);
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    remoteShow: {
 | 
			
		||||
      set(value) {
 | 
			
		||||
        this.$store.commit("showUserOnCalendar", {
 | 
			
		||||
          user: this.user,
 | 
			
		||||
          remotes: value,
 | 
			
		||||
        });
 | 
			
		||||
      },
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.$store.getters.isRemoteShownOnCalendarForUser(this.user);
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.calendar-active {
 | 
			
		||||
    margin: 0 0.25rem 0.25rem 0;
 | 
			
		||||
    padding: 0.5rem;
 | 
			
		||||
  margin: 0 0.25rem 0.25rem 0;
 | 
			
		||||
  padding: 0.5rem;
 | 
			
		||||
 | 
			
		||||
    border-radius: 0.5rem;
 | 
			
		||||
  border-radius: 0.5rem;
 | 
			
		||||
 | 
			
		||||
    color: var(--bs-blue);
 | 
			
		||||
  color: var(--bs-blue);
 | 
			
		||||
 | 
			
		||||
    & > .badge-user {
 | 
			
		||||
        margin-right: 0.5rem;
 | 
			
		||||
    }
 | 
			
		||||
  & > .badge-user {
 | 
			
		||||
    margin-right: 0.5rem;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -14,37 +14,37 @@ export { whoami } from "../../../../../ChillMainBundle/Resources/public/lib/api/
 | 
			
		||||
 * @return Promise
 | 
			
		||||
 */
 | 
			
		||||
export const fetchCalendarRangeForUser = (
 | 
			
		||||
    user: User,
 | 
			
		||||
    start: Date,
 | 
			
		||||
    end: Date,
 | 
			
		||||
  user: User,
 | 
			
		||||
  start: Date,
 | 
			
		||||
  end: Date,
 | 
			
		||||
): Promise<CalendarRange[]> => {
 | 
			
		||||
    const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
 | 
			
		||||
    const dateFrom = datetimeToISO(start);
 | 
			
		||||
    const dateTo = datetimeToISO(end);
 | 
			
		||||
  const uri = `/api/1.0/calendar/calendar-range-available/${user.id}.json`;
 | 
			
		||||
  const dateFrom = datetimeToISO(start);
 | 
			
		||||
  const dateTo = datetimeToISO(end);
 | 
			
		||||
 | 
			
		||||
    return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
 | 
			
		||||
  return fetchResults<CalendarRange>(uri, { dateFrom, dateTo });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const fetchCalendarRemoteForUser = (
 | 
			
		||||
    user: User,
 | 
			
		||||
    start: Date,
 | 
			
		||||
    end: Date,
 | 
			
		||||
  user: User,
 | 
			
		||||
  start: Date,
 | 
			
		||||
  end: Date,
 | 
			
		||||
): Promise<CalendarRemote[]> => {
 | 
			
		||||
    const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
 | 
			
		||||
    const dateFrom = datetimeToISO(start);
 | 
			
		||||
    const dateTo = datetimeToISO(end);
 | 
			
		||||
  const uri = `/api/1.0/calendar/proxy/calendar/by-user/${user.id}/events`;
 | 
			
		||||
  const dateFrom = datetimeToISO(start);
 | 
			
		||||
  const dateTo = datetimeToISO(end);
 | 
			
		||||
 | 
			
		||||
    return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
 | 
			
		||||
  return fetchResults<CalendarRemote>(uri, { dateFrom, dateTo });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const fetchCalendarLocalForUser = (
 | 
			
		||||
    user: User,
 | 
			
		||||
    start: Date,
 | 
			
		||||
    end: Date,
 | 
			
		||||
  user: User,
 | 
			
		||||
  start: Date,
 | 
			
		||||
  end: Date,
 | 
			
		||||
): Promise<CalendarLight[]> => {
 | 
			
		||||
    const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
 | 
			
		||||
    const dateFrom = datetimeToISO(start);
 | 
			
		||||
    const dateTo = datetimeToISO(end);
 | 
			
		||||
  const uri = `/api/1.0/calendar/calendar/by-user/${user.id}.json`;
 | 
			
		||||
  const dateFrom = datetimeToISO(start);
 | 
			
		||||
  const dateTo = datetimeToISO(end);
 | 
			
		||||
 | 
			
		||||
    return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
 | 
			
		||||
  return fetchResults<CalendarLight>(uri, { dateFrom, dateTo });
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,17 @@
 | 
			
		||||
const COLORS = [
 | 
			
		||||
    /* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
 | 
			
		||||
    "#8dd3c7",
 | 
			
		||||
    "#ffffb3",
 | 
			
		||||
    "#bebada",
 | 
			
		||||
    "#fb8072",
 | 
			
		||||
    "#80b1d3",
 | 
			
		||||
    "#fdb462",
 | 
			
		||||
    "#b3de69",
 | 
			
		||||
    "#fccde5",
 | 
			
		||||
    "#d9d9d9",
 | 
			
		||||
    "#bc80bd",
 | 
			
		||||
    "#ccebc5",
 | 
			
		||||
    "#ffed6f",
 | 
			
		||||
  /* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
 | 
			
		||||
  "#8dd3c7",
 | 
			
		||||
  "#ffffb3",
 | 
			
		||||
  "#bebada",
 | 
			
		||||
  "#fb8072",
 | 
			
		||||
  "#80b1d3",
 | 
			
		||||
  "#fdb462",
 | 
			
		||||
  "#b3de69",
 | 
			
		||||
  "#fccde5",
 | 
			
		||||
  "#d9d9d9",
 | 
			
		||||
  "#bc80bd",
 | 
			
		||||
  "#ccebc5",
 | 
			
		||||
  "#ffed6f",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export { COLORS };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,117 +1,117 @@
 | 
			
		||||
import { COLORS } from "../const";
 | 
			
		||||
import { ISOToDatetime } from "../../../../../../ChillMainBundle/Resources/public/chill/js/date";
 | 
			
		||||
import {
 | 
			
		||||
    DateTime,
 | 
			
		||||
    User,
 | 
			
		||||
  DateTime,
 | 
			
		||||
  User,
 | 
			
		||||
} from "../../../../../../ChillMainBundle/Resources/public/types";
 | 
			
		||||
import { CalendarLight, CalendarRange, CalendarRemote } from "../../../types";
 | 
			
		||||
import type { EventInputCalendarRange } from "../../../types";
 | 
			
		||||
import { EventInput } from "@fullcalendar/core";
 | 
			
		||||
 | 
			
		||||
export interface UserData {
 | 
			
		||||
    user: User;
 | 
			
		||||
    calendarRanges: CalendarRange[];
 | 
			
		||||
    calendarRangesLoaded: {}[];
 | 
			
		||||
    remotes: CalendarRemote[];
 | 
			
		||||
    remotesLoaded: {}[];
 | 
			
		||||
    locals: CalendarRemote[];
 | 
			
		||||
    localsLoaded: {}[];
 | 
			
		||||
    mainColor: string;
 | 
			
		||||
  user: User;
 | 
			
		||||
  calendarRanges: CalendarRange[];
 | 
			
		||||
  calendarRangesLoaded: {}[];
 | 
			
		||||
  remotes: CalendarRemote[];
 | 
			
		||||
  remotesLoaded: {}[];
 | 
			
		||||
  locals: CalendarRemote[];
 | 
			
		||||
  localsLoaded: {}[];
 | 
			
		||||
  mainColor: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const addIdToValue = (string: string, id: number): string => {
 | 
			
		||||
    const array = string ? string.split(",") : [];
 | 
			
		||||
    array.push(id.toString());
 | 
			
		||||
    const str = array.join();
 | 
			
		||||
    return str;
 | 
			
		||||
  const array = string ? string.split(",") : [];
 | 
			
		||||
  array.push(id.toString());
 | 
			
		||||
  const str = array.join();
 | 
			
		||||
  return str;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const removeIdFromValue = (string: string, id: number) => {
 | 
			
		||||
    let array = string.split(",");
 | 
			
		||||
    array = array.filter((el) => el !== id.toString());
 | 
			
		||||
    const str = array.join();
 | 
			
		||||
    return str;
 | 
			
		||||
  let array = string.split(",");
 | 
			
		||||
  array = array.filter((el) => el !== id.toString());
 | 
			
		||||
  const str = array.join();
 | 
			
		||||
  return str;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Assign missing keys for the ConcernedGroups component
 | 
			
		||||
 */
 | 
			
		||||
export const mapEntity = (entity: EventInput): EventInput => {
 | 
			
		||||
    const calendar = { ...entity };
 | 
			
		||||
    Object.assign(calendar, { thirdParties: entity.professionals });
 | 
			
		||||
  const calendar = { ...entity };
 | 
			
		||||
  Object.assign(calendar, { thirdParties: entity.professionals });
 | 
			
		||||
 | 
			
		||||
    if (entity.startDate !== null) {
 | 
			
		||||
        calendar.startDate = ISOToDatetime(entity.startDate.datetime);
 | 
			
		||||
    }
 | 
			
		||||
    if (entity.endDate !== null) {
 | 
			
		||||
        calendar.endDate = ISOToDatetime(entity.endDate.datetime);
 | 
			
		||||
    }
 | 
			
		||||
  if (entity.startDate !== null) {
 | 
			
		||||
    calendar.startDate = ISOToDatetime(entity.startDate.datetime);
 | 
			
		||||
  }
 | 
			
		||||
  if (entity.endDate !== null) {
 | 
			
		||||
    calendar.endDate = ISOToDatetime(entity.endDate.datetime);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    if (entity.calendarRange !== null) {
 | 
			
		||||
        calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
 | 
			
		||||
        calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
 | 
			
		||||
    }
 | 
			
		||||
  if (entity.calendarRange !== null) {
 | 
			
		||||
    calendar.calendarRange.calendarRangeId = entity.calendarRange.id;
 | 
			
		||||
    calendar.calendarRange.id = `range_${entity.calendarRange.id}`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return calendar;
 | 
			
		||||
  return calendar;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const createUserData = (user: User, colorIndex: number): UserData => {
 | 
			
		||||
    const colorId = colorIndex % COLORS.length;
 | 
			
		||||
  const colorId = colorIndex % COLORS.length;
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        user: user,
 | 
			
		||||
        calendarRanges: [],
 | 
			
		||||
        calendarRangesLoaded: [],
 | 
			
		||||
        remotes: [],
 | 
			
		||||
        remotesLoaded: [],
 | 
			
		||||
        locals: [],
 | 
			
		||||
        localsLoaded: [],
 | 
			
		||||
        mainColor: COLORS[colorId],
 | 
			
		||||
    };
 | 
			
		||||
  return {
 | 
			
		||||
    user: user,
 | 
			
		||||
    calendarRanges: [],
 | 
			
		||||
    calendarRangesLoaded: [],
 | 
			
		||||
    remotes: [],
 | 
			
		||||
    remotesLoaded: [],
 | 
			
		||||
    locals: [],
 | 
			
		||||
    localsLoaded: [],
 | 
			
		||||
    mainColor: COLORS[colorId],
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO move this function to a more global namespace, as it is also in use in MyCalendarRange app
 | 
			
		||||
export const calendarRangeToFullCalendarEvent = (
 | 
			
		||||
    entity: CalendarRange,
 | 
			
		||||
  entity: CalendarRange,
 | 
			
		||||
): EventInputCalendarRange => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: `range_${entity.id}`,
 | 
			
		||||
        title: "(" + entity.user.text + ")",
 | 
			
		||||
        start: entity.startDate.datetime8601,
 | 
			
		||||
        end: entity.endDate.datetime8601,
 | 
			
		||||
        allDay: false,
 | 
			
		||||
        userId: entity.user.id,
 | 
			
		||||
        userLabel: entity.user.label,
 | 
			
		||||
        calendarRangeId: entity.id,
 | 
			
		||||
        locationId: entity.location.id,
 | 
			
		||||
        locationName: entity.location.name,
 | 
			
		||||
        is: "range",
 | 
			
		||||
    };
 | 
			
		||||
  return {
 | 
			
		||||
    id: `range_${entity.id}`,
 | 
			
		||||
    title: "(" + entity.user.text + ")",
 | 
			
		||||
    start: entity.startDate.datetime8601,
 | 
			
		||||
    end: entity.endDate.datetime8601,
 | 
			
		||||
    allDay: false,
 | 
			
		||||
    userId: entity.user.id,
 | 
			
		||||
    userLabel: entity.user.label,
 | 
			
		||||
    calendarRangeId: entity.id,
 | 
			
		||||
    locationId: entity.location.id,
 | 
			
		||||
    locationName: entity.location.name,
 | 
			
		||||
    is: "range",
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const remoteToFullCalendarEvent = (
 | 
			
		||||
    entity: CalendarRemote,
 | 
			
		||||
  entity: CalendarRemote,
 | 
			
		||||
): EventInput & { id: string } => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: `range_${entity.id}`,
 | 
			
		||||
        title: entity.title,
 | 
			
		||||
        start: entity.startDate.datetime8601,
 | 
			
		||||
        end: entity.endDate.datetime8601,
 | 
			
		||||
        allDay: entity.isAllDay,
 | 
			
		||||
        is: "remote",
 | 
			
		||||
    };
 | 
			
		||||
  return {
 | 
			
		||||
    id: `range_${entity.id}`,
 | 
			
		||||
    title: entity.title,
 | 
			
		||||
    start: entity.startDate.datetime8601,
 | 
			
		||||
    end: entity.endDate.datetime8601,
 | 
			
		||||
    allDay: entity.isAllDay,
 | 
			
		||||
    is: "remote",
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const localsToFullCalendarEvent = (
 | 
			
		||||
    entity: CalendarLight,
 | 
			
		||||
  entity: CalendarLight,
 | 
			
		||||
): EventInput & { id: string; originId: number } => {
 | 
			
		||||
    return {
 | 
			
		||||
        id: `local_${entity.id}`,
 | 
			
		||||
        title: entity.persons.map((p) => p.text).join(", "),
 | 
			
		||||
        originId: entity.id,
 | 
			
		||||
        start: entity.startDate.datetime8601,
 | 
			
		||||
        end: entity.endDate.datetime8601,
 | 
			
		||||
        allDay: false,
 | 
			
		||||
        is: "local",
 | 
			
		||||
    };
 | 
			
		||||
  return {
 | 
			
		||||
    id: `local_${entity.id}`,
 | 
			
		||||
    title: entity.persons.map((p) => p.text).join(", "),
 | 
			
		||||
    originId: entity.id,
 | 
			
		||||
    start: entity.startDate.datetime8601,
 | 
			
		||||
    end: entity.endDate.datetime8601,
 | 
			
		||||
    allDay: false,
 | 
			
		||||
    is: "local",
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,58 +1,50 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="btn-group" role="group">
 | 
			
		||||
        <button
 | 
			
		||||
            id="btnGroupDrop1"
 | 
			
		||||
            type="button"
 | 
			
		||||
            class="btn btn-misc dropdown-toggle"
 | 
			
		||||
            data-bs-toggle="dropdown"
 | 
			
		||||
            aria-expanded="false"
 | 
			
		||||
  <div class="btn-group" role="group">
 | 
			
		||||
    <button
 | 
			
		||||
      id="btnGroupDrop1"
 | 
			
		||||
      type="button"
 | 
			
		||||
      class="btn btn-misc dropdown-toggle"
 | 
			
		||||
      data-bs-toggle="dropdown"
 | 
			
		||||
      aria-expanded="false"
 | 
			
		||||
    >
 | 
			
		||||
      <template v-if="status === Statuses.PENDING">
 | 
			
		||||
        <span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
 | 
			
		||||
      </template>
 | 
			
		||||
      <template v-else-if="status === Statuses.ACCEPTED">
 | 
			
		||||
        <span class="fa fa-check"></span> {{ $t("Accepted") }}
 | 
			
		||||
      </template>
 | 
			
		||||
      <template v-else-if="status === Statuses.DECLINED">
 | 
			
		||||
        <span class="fa fa-times"></span> {{ $t("Declined") }}
 | 
			
		||||
      </template>
 | 
			
		||||
      <template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
 | 
			
		||||
        <span class="fa fa-question"></span> {{ $t("Tentative") }}
 | 
			
		||||
      </template>
 | 
			
		||||
    </button>
 | 
			
		||||
    <ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
 | 
			
		||||
      <li v-if="status !== Statuses.ACCEPTED">
 | 
			
		||||
        <a class="dropdown-item" @click="changeStatus(Statuses.ACCEPTED)"
 | 
			
		||||
          ><i class="fa fa-check" aria-hidden="true"></i> {{ $t("Accept") }}</a
 | 
			
		||||
        >
 | 
			
		||||
            <template v-if="status === Statuses.PENDING">
 | 
			
		||||
                <span class="fa fa-hourglass"></span> {{ $t("Give_an_answer") }}
 | 
			
		||||
            </template>
 | 
			
		||||
            <template v-else-if="status === Statuses.ACCEPTED">
 | 
			
		||||
                <span class="fa fa-check"></span> {{ $t("Accepted") }}
 | 
			
		||||
            </template>
 | 
			
		||||
            <template v-else-if="status === Statuses.DECLINED">
 | 
			
		||||
                <span class="fa fa-times"></span> {{ $t("Declined") }}
 | 
			
		||||
            </template>
 | 
			
		||||
            <template v-else-if="status === Statuses.TENTATIVELY_ACCEPTED">
 | 
			
		||||
                <span class="fa fa-question"></span> {{ $t("Tentative") }}
 | 
			
		||||
            </template>
 | 
			
		||||
        </button>
 | 
			
		||||
        <ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
 | 
			
		||||
            <li v-if="status !== Statuses.ACCEPTED">
 | 
			
		||||
                <a
 | 
			
		||||
                    class="dropdown-item"
 | 
			
		||||
                    @click="changeStatus(Statuses.ACCEPTED)"
 | 
			
		||||
                    ><i class="fa fa-check" aria-hidden="true"></i>
 | 
			
		||||
                    {{ $t("Accept") }}</a
 | 
			
		||||
                >
 | 
			
		||||
            </li>
 | 
			
		||||
            <li v-if="status !== Statuses.DECLINED">
 | 
			
		||||
                <a
 | 
			
		||||
                    class="dropdown-item"
 | 
			
		||||
                    @click="changeStatus(Statuses.DECLINED)"
 | 
			
		||||
                    ><i class="fa fa-times" aria-hidden="true"></i>
 | 
			
		||||
                    {{ $t("Decline") }}</a
 | 
			
		||||
                >
 | 
			
		||||
            </li>
 | 
			
		||||
            <li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
 | 
			
		||||
                <a
 | 
			
		||||
                    class="dropdown-item"
 | 
			
		||||
                    @click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
 | 
			
		||||
                    ><i class="fa fa-question"></i>
 | 
			
		||||
                    {{ $t("Tentatively_accept") }}</a
 | 
			
		||||
                >
 | 
			
		||||
            </li>
 | 
			
		||||
            <li v-if="status !== Statuses.PENDING">
 | 
			
		||||
                <a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
 | 
			
		||||
                    ><i class="fa fa-hourglass-o"></i>
 | 
			
		||||
                    {{ $t("Set_pending") }}</a
 | 
			
		||||
                >
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li v-if="status !== Statuses.DECLINED">
 | 
			
		||||
        <a class="dropdown-item" @click="changeStatus(Statuses.DECLINED)"
 | 
			
		||||
          ><i class="fa fa-times" aria-hidden="true"></i> {{ $t("Decline") }}</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
      <li v-if="status !== Statuses.TENTATIVELY_ACCEPTED">
 | 
			
		||||
        <a
 | 
			
		||||
          class="dropdown-item"
 | 
			
		||||
          @click="changeStatus(Statuses.TENTATIVELY_ACCEPTED)"
 | 
			
		||||
          ><i class="fa fa-question"></i> {{ $t("Tentatively_accept") }}</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
      <li v-if="status !== Statuses.PENDING">
 | 
			
		||||
        <a class="dropdown-item" @click="changeStatus(Statuses.PENDING)"
 | 
			
		||||
          ><i class="fa fa-hourglass-o"></i> {{ $t("Set_pending") }}</a
 | 
			
		||||
        >
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
@@ -64,69 +56,67 @@ const PENDING = "pending";
 | 
			
		||||
const TENTATIVELY_ACCEPTED = "tentative";
 | 
			
		||||
 | 
			
		||||
const i18n = {
 | 
			
		||||
    messages: {
 | 
			
		||||
        fr: {
 | 
			
		||||
            Give_an_answer: "Répondre",
 | 
			
		||||
            Accepted: "Accepté",
 | 
			
		||||
            Declined: "Refusé",
 | 
			
		||||
            Tentative: "Accepté provisoirement",
 | 
			
		||||
            Accept: "Accepter",
 | 
			
		||||
            Decline: "Refuser",
 | 
			
		||||
            Tentatively_accept: "Accepter provisoirement",
 | 
			
		||||
            Set_pending: "Ne pas répondre",
 | 
			
		||||
        },
 | 
			
		||||
  messages: {
 | 
			
		||||
    fr: {
 | 
			
		||||
      Give_an_answer: "Répondre",
 | 
			
		||||
      Accepted: "Accepté",
 | 
			
		||||
      Declined: "Refusé",
 | 
			
		||||
      Tentative: "Accepté provisoirement",
 | 
			
		||||
      Accept: "Accepter",
 | 
			
		||||
      Decline: "Refuser",
 | 
			
		||||
      Tentatively_accept: "Accepter provisoirement",
 | 
			
		||||
      Set_pending: "Ne pas répondre",
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
    name: "Answer",
 | 
			
		||||
    i18n,
 | 
			
		||||
    props: {
 | 
			
		||||
        calendarId: { type: Number, required: true },
 | 
			
		||||
        status: {
 | 
			
		||||
            type: String as PropType<
 | 
			
		||||
                "accepted" | "declined" | "pending" | "tentative"
 | 
			
		||||
            >,
 | 
			
		||||
            required: true,
 | 
			
		||||
        },
 | 
			
		||||
  name: "Answer",
 | 
			
		||||
  i18n,
 | 
			
		||||
  props: {
 | 
			
		||||
    calendarId: { type: Number, required: true },
 | 
			
		||||
    status: {
 | 
			
		||||
      type: String as PropType<
 | 
			
		||||
        "accepted" | "declined" | "pending" | "tentative"
 | 
			
		||||
      >,
 | 
			
		||||
      required: true,
 | 
			
		||||
    },
 | 
			
		||||
    emits: {
 | 
			
		||||
        statusChanged(
 | 
			
		||||
            payload: "accepted" | "declined" | "pending" | "tentative",
 | 
			
		||||
        ) {
 | 
			
		||||
            return true;
 | 
			
		||||
        },
 | 
			
		||||
  },
 | 
			
		||||
  emits: {
 | 
			
		||||
    statusChanged(payload: "accepted" | "declined" | "pending" | "tentative") {
 | 
			
		||||
      return true;
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            Statuses: {
 | 
			
		||||
                ACCEPTED,
 | 
			
		||||
                DECLINED,
 | 
			
		||||
                PENDING,
 | 
			
		||||
                TENTATIVELY_ACCEPTED,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        changeStatus: function (
 | 
			
		||||
            newStatus: "accepted" | "declined" | "pending" | "tentative",
 | 
			
		||||
        ) {
 | 
			
		||||
            console.log("changeStatus", newStatus);
 | 
			
		||||
            const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
 | 
			
		||||
            window
 | 
			
		||||
                .fetch(url, {
 | 
			
		||||
                    method: "POST",
 | 
			
		||||
                })
 | 
			
		||||
                .then((r: Response) => {
 | 
			
		||||
                    if (!r.ok) {
 | 
			
		||||
                        console.error("could not confirm answer", newStatus);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    console.log("answer sent", newStatus);
 | 
			
		||||
                    this.$emit("statusChanged", newStatus);
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      Statuses: {
 | 
			
		||||
        ACCEPTED,
 | 
			
		||||
        DECLINED,
 | 
			
		||||
        PENDING,
 | 
			
		||||
        TENTATIVELY_ACCEPTED,
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    changeStatus: function (
 | 
			
		||||
      newStatus: "accepted" | "declined" | "pending" | "tentative",
 | 
			
		||||
    ) {
 | 
			
		||||
      console.log("changeStatus", newStatus);
 | 
			
		||||
      const url = `/api/1.0/calendar/calendar/${this.$props.calendarId}/answer/${newStatus}.json`;
 | 
			
		||||
      window
 | 
			
		||||
        .fetch(url, {
 | 
			
		||||
          method: "POST",
 | 
			
		||||
        })
 | 
			
		||||
        .then((r: Response) => {
 | 
			
		||||
          if (!r.ok) {
 | 
			
		||||
            console.error("could not confirm answer", newStatus);
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          console.log("answer sent", newStatus);
 | 
			
		||||
          this.$emit("statusChanged", newStatus);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,228 +1,177 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="row">
 | 
			
		||||
        <div class="col-sm">
 | 
			
		||||
            <label class="form-label">{{ $t("created_availabilities") }}</label>
 | 
			
		||||
            <vue-multiselect
 | 
			
		||||
                v-model="pickedLocation"
 | 
			
		||||
                :options="locations"
 | 
			
		||||
                :label="'name'"
 | 
			
		||||
                :track-by="'id'"
 | 
			
		||||
                :selectLabel="'Presser \'Entrée\' pour choisir'"
 | 
			
		||||
                :selectedLabel="'Choisir'"
 | 
			
		||||
                :deselectLabel="'Presser \'Entrée\' pour enlever'"
 | 
			
		||||
                :placeholder="'Choisir'"
 | 
			
		||||
            ></vue-multiselect>
 | 
			
		||||
        </div>
 | 
			
		||||
  <div class="row">
 | 
			
		||||
    <div class="col-sm">
 | 
			
		||||
      <label class="form-label">{{ $t("created_availabilities") }}</label>
 | 
			
		||||
      <vue-multiselect
 | 
			
		||||
        v-model="pickedLocation"
 | 
			
		||||
        :options="locations"
 | 
			
		||||
        :label="'name'"
 | 
			
		||||
        :track-by="'id'"
 | 
			
		||||
        :selectLabel="'Presser \'Entrée\' pour choisir'"
 | 
			
		||||
        :selectedLabel="'Choisir'"
 | 
			
		||||
        :deselectLabel="'Presser \'Entrée\' pour enlever'"
 | 
			
		||||
        :placeholder="'Choisir'"
 | 
			
		||||
      ></vue-multiselect>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div
 | 
			
		||||
        class="display-options row justify-content-between"
 | 
			
		||||
        style="margin-top: 1rem"
 | 
			
		||||
    >
 | 
			
		||||
        <div class="col-sm-9 col-xs-12">
 | 
			
		||||
            <div class="input-group mb-3">
 | 
			
		||||
                <label class="input-group-text" for="slotDuration"
 | 
			
		||||
                    >Durée des créneaux</label
 | 
			
		||||
                >
 | 
			
		||||
                <select
 | 
			
		||||
                    v-model="slotDuration"
 | 
			
		||||
                    id="slotDuration"
 | 
			
		||||
                    class="form-select"
 | 
			
		||||
                >
 | 
			
		||||
                    <option value="00:05:00">5 minutes</option>
 | 
			
		||||
                    <option value="00:10:00">10 minutes</option>
 | 
			
		||||
                    <option value="00:15:00">15 minutes</option>
 | 
			
		||||
                    <option value="00:30:00">30 minutes</option>
 | 
			
		||||
                    <option value="00:45:00">45 minutes</option>
 | 
			
		||||
                    <option value="00:60:00">60 minutes</option>
 | 
			
		||||
                </select>
 | 
			
		||||
                <label class="input-group-text" for="slotMinTime">De</label>
 | 
			
		||||
                <select
 | 
			
		||||
                    v-model="slotMinTime"
 | 
			
		||||
                    id="slotMinTime"
 | 
			
		||||
                    class="form-select"
 | 
			
		||||
                >
 | 
			
		||||
                    <option value="00:00:00">0h</option>
 | 
			
		||||
                    <option value="01:00:00">1h</option>
 | 
			
		||||
                    <option value="02:00:00">2h</option>
 | 
			
		||||
                    <option value="03:00:00">3h</option>
 | 
			
		||||
                    <option value="04:00:00">4h</option>
 | 
			
		||||
                    <option value="05:00:00">5h</option>
 | 
			
		||||
                    <option value="06:00:00">6h</option>
 | 
			
		||||
                    <option value="07:00:00">7h</option>
 | 
			
		||||
                    <option value="08:00:00">8h</option>
 | 
			
		||||
                    <option value="09:00:00">9h</option>
 | 
			
		||||
                    <option value="10:00:00">10h</option>
 | 
			
		||||
                    <option value="11:00:00">11h</option>
 | 
			
		||||
                    <option value="12:00:00">12h</option>
 | 
			
		||||
                </select>
 | 
			
		||||
                <label class="input-group-text" for="slotMaxTime">À</label>
 | 
			
		||||
                <select
 | 
			
		||||
                    v-model="slotMaxTime"
 | 
			
		||||
                    id="slotMaxTime"
 | 
			
		||||
                    class="form-select"
 | 
			
		||||
                >
 | 
			
		||||
                    <option value="12:00:00">12h</option>
 | 
			
		||||
                    <option value="13:00:00">13h</option>
 | 
			
		||||
                    <option value="14:00:00">14h</option>
 | 
			
		||||
                    <option value="15:00:00">15h</option>
 | 
			
		||||
                    <option value="16:00:00">16h</option>
 | 
			
		||||
                    <option value="17:00:00">17h</option>
 | 
			
		||||
                    <option value="18:00:00">18h</option>
 | 
			
		||||
                    <option value="19:00:00">19h</option>
 | 
			
		||||
                    <option value="20:00:00">20h</option>
 | 
			
		||||
                    <option value="21:00:00">21h</option>
 | 
			
		||||
                    <option value="22:00:00">22h</option>
 | 
			
		||||
                    <option value="23:00:00">23h</option>
 | 
			
		||||
                    <option value="23:59:59">24h</option>
 | 
			
		||||
                </select>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-xs-12 col-sm-3">
 | 
			
		||||
            <div class="float-end">
 | 
			
		||||
                <div class="form-check input-group">
 | 
			
		||||
                    <span class="input-group-text">
 | 
			
		||||
                        <input
 | 
			
		||||
                            id="showHideWE"
 | 
			
		||||
                            class="mt-0"
 | 
			
		||||
                            type="checkbox"
 | 
			
		||||
                            v-model="showWeekends"
 | 
			
		||||
                        />
 | 
			
		||||
                    </span>
 | 
			
		||||
                    <label
 | 
			
		||||
                        for="showHideWE"
 | 
			
		||||
                        class="form-check-label input-group-text"
 | 
			
		||||
                        >Week-ends</label
 | 
			
		||||
                    >
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div
 | 
			
		||||
    class="display-options row justify-content-between"
 | 
			
		||||
    style="margin-top: 1rem"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="col-sm-9 col-xs-12">
 | 
			
		||||
      <div class="input-group mb-3">
 | 
			
		||||
        <label class="input-group-text" for="slotDuration"
 | 
			
		||||
          >Durée des créneaux</label
 | 
			
		||||
        >
 | 
			
		||||
        <select v-model="slotDuration" id="slotDuration" class="form-select">
 | 
			
		||||
          <option value="00:05:00">5 minutes</option>
 | 
			
		||||
          <option value="00:10:00">10 minutes</option>
 | 
			
		||||
          <option value="00:15:00">15 minutes</option>
 | 
			
		||||
          <option value="00:30:00">30 minutes</option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <label class="input-group-text" for="slotMinTime">De</label>
 | 
			
		||||
        <select v-model="slotMinTime" id="slotMinTime" class="form-select">
 | 
			
		||||
          <option value="00:00:00">0h</option>
 | 
			
		||||
          <option value="01:00:00">1h</option>
 | 
			
		||||
          <option value="02:00:00">2h</option>
 | 
			
		||||
          <option value="03:00:00">3h</option>
 | 
			
		||||
          <option value="04:00:00">4h</option>
 | 
			
		||||
          <option value="05:00:00">5h</option>
 | 
			
		||||
          <option value="06:00:00">6h</option>
 | 
			
		||||
          <option value="07:00:00">7h</option>
 | 
			
		||||
          <option value="08:00:00">8h</option>
 | 
			
		||||
          <option value="09:00:00">9h</option>
 | 
			
		||||
          <option value="10:00:00">10h</option>
 | 
			
		||||
          <option value="11:00:00">11h</option>
 | 
			
		||||
          <option value="12:00:00">12h</option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <label class="input-group-text" for="slotMaxTime">À</label>
 | 
			
		||||
        <select v-model="slotMaxTime" id="slotMaxTime" class="form-select">
 | 
			
		||||
          <option value="12:00:00">12h</option>
 | 
			
		||||
          <option value="13:00:00">13h</option>
 | 
			
		||||
          <option value="14:00:00">14h</option>
 | 
			
		||||
          <option value="15:00:00">15h</option>
 | 
			
		||||
          <option value="16:00:00">16h</option>
 | 
			
		||||
          <option value="17:00:00">17h</option>
 | 
			
		||||
          <option value="18:00:00">18h</option>
 | 
			
		||||
          <option value="19:00:00">19h</option>
 | 
			
		||||
          <option value="20:00:00">20h</option>
 | 
			
		||||
          <option value="21:00:00">21h</option>
 | 
			
		||||
          <option value="22:00:00">22h</option>
 | 
			
		||||
          <option value="23:00:00">23h</option>
 | 
			
		||||
          <option value="23:59:59">24h</option>
 | 
			
		||||
        </select>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <FullCalendar :options="calendarOptions" ref="calendarRef">
 | 
			
		||||
        <template v-slot:eventContent="{ event }: { event: EventApi }">
 | 
			
		||||
            <span :class="eventClasses">
 | 
			
		||||
                <b v-if="event.extendedProps.is === 'remote'">{{
 | 
			
		||||
                    event.title
 | 
			
		||||
                }}</b>
 | 
			
		||||
                <b v-else-if="event.extendedProps.is === 'range'"
 | 
			
		||||
                    >{{ formatDate(event.startStr, "time") }} -
 | 
			
		||||
                    {{ formatDate(event.endStr, "time") }}:
 | 
			
		||||
                    {{ event.extendedProps.locationName }}</b
 | 
			
		||||
                >
 | 
			
		||||
                <b v-else-if="event.extendedProps.is === 'local'">{{
 | 
			
		||||
                    event.title
 | 
			
		||||
                }}</b>
 | 
			
		||||
                <b v-else>no 'is'</b>
 | 
			
		||||
                <a
 | 
			
		||||
                    v-if="event.extendedProps.is === 'range'"
 | 
			
		||||
                    class="fa fa-fw fa-times delete"
 | 
			
		||||
                    @click.prevent="onClickDelete(event)"
 | 
			
		||||
                >
 | 
			
		||||
                </a>
 | 
			
		||||
            </span>
 | 
			
		||||
    <div class="col-xs-12 col-sm-3">
 | 
			
		||||
      <div class="float-end">
 | 
			
		||||
        <div class="form-check input-group">
 | 
			
		||||
          <span class="input-group-text">
 | 
			
		||||
            <input
 | 
			
		||||
              id="showHideWE"
 | 
			
		||||
              class="mt-0"
 | 
			
		||||
              type="checkbox"
 | 
			
		||||
              v-model="showWeekends"
 | 
			
		||||
            />
 | 
			
		||||
          </span>
 | 
			
		||||
          <label for="showHideWE" class="form-check-label input-group-text"
 | 
			
		||||
            >Week-ends</label
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <FullCalendar :options="calendarOptions" ref="calendarRef">
 | 
			
		||||
    <template v-slot:eventContent="{ event }: { event: EventApi }">
 | 
			
		||||
      <span :class="eventClasses">
 | 
			
		||||
        <b v-if="event.extendedProps.is === 'remote'">{{ event.title }}</b>
 | 
			
		||||
        <b v-else-if="event.extendedProps.is === 'range'"
 | 
			
		||||
          >{{ formatDate(event.startStr) }} -
 | 
			
		||||
          {{ event.extendedProps.locationName }}</b
 | 
			
		||||
        >
 | 
			
		||||
        <b v-else-if="event.extendedProps.is === 'local'">{{ event.title }}</b>
 | 
			
		||||
        <b v-else>no 'is'</b>
 | 
			
		||||
        <a
 | 
			
		||||
          v-if="event.extendedProps.is === 'range'"
 | 
			
		||||
          class="fa fa-fw fa-times delete"
 | 
			
		||||
          @click.prevent="onClickDelete(event)"
 | 
			
		||||
        >
 | 
			
		||||
        </a>
 | 
			
		||||
      </span>
 | 
			
		||||
    </template>
 | 
			
		||||
  </FullCalendar>
 | 
			
		||||
 | 
			
		||||
  <div id="copy-widget">
 | 
			
		||||
    <div class="container mt-2 mb-2">
 | 
			
		||||
      <div class="row justify-content-between align-items-center mb-4">
 | 
			
		||||
        <div class="col-xs-12 col-sm-3 col-md-2">
 | 
			
		||||
          <h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-xs-12 col-sm-9 col-md-2">
 | 
			
		||||
          <select v-model="dayOrWeek" id="dayOrWeek" class="form-select">
 | 
			
		||||
            <option value="day">{{ $t("from_day_to_day") }}</option>
 | 
			
		||||
            <option value="week">
 | 
			
		||||
              {{ $t("from_week_to_week") }}
 | 
			
		||||
            </option>
 | 
			
		||||
          </select>
 | 
			
		||||
        </div>
 | 
			
		||||
        <template v-if="dayOrWeek === 'day'">
 | 
			
		||||
          <div class="col-xs-12 col-sm-3 col-md-3">
 | 
			
		||||
            <input class="form-control" type="date" v-model="copyFrom" />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
 | 
			
		||||
            <i class="fa fa-angle-double-right"></i>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="col-xs-12 col-sm-3 col-md-3">
 | 
			
		||||
            <input class="form-control" type="date" v-model="copyTo" />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="col-xs-12 col-sm-5 col-md-1">
 | 
			
		||||
            <button class="btn btn-action float-end" @click="copyDay">
 | 
			
		||||
              {{ $t("copy_range") }}
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </template>
 | 
			
		||||
    </FullCalendar>
 | 
			
		||||
 | 
			
		||||
    <div id="copy-widget">
 | 
			
		||||
        <div class="container mt-2 mb-2">
 | 
			
		||||
            <div class="row justify-content-between align-items-center mb-4">
 | 
			
		||||
                <div class="col-xs-12 col-sm-3 col-md-2">
 | 
			
		||||
                    <h6 class="chill-red">{{ $t("copy_range_from_to") }}</h6>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-xs-12 col-sm-9 col-md-2">
 | 
			
		||||
                    <select
 | 
			
		||||
                        v-model="dayOrWeek"
 | 
			
		||||
                        id="dayOrWeek"
 | 
			
		||||
                        class="form-select"
 | 
			
		||||
                    >
 | 
			
		||||
                        <option value="day">{{ $t("from_day_to_day") }}</option>
 | 
			
		||||
                        <option value="week">
 | 
			
		||||
                            {{ $t("from_week_to_week") }}
 | 
			
		||||
                        </option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                </div>
 | 
			
		||||
                <template v-if="dayOrWeek === 'day'">
 | 
			
		||||
                    <div class="col-xs-12 col-sm-3 col-md-3">
 | 
			
		||||
                        <input
 | 
			
		||||
                            class="form-control"
 | 
			
		||||
                            type="date"
 | 
			
		||||
                            v-model="copyFrom"
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
 | 
			
		||||
                        <i class="fa fa-angle-double-right"></i>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-xs-12 col-sm-3 col-md-3">
 | 
			
		||||
                        <input
 | 
			
		||||
                            class="form-control"
 | 
			
		||||
                            type="date"
 | 
			
		||||
                            v-model="copyTo"
 | 
			
		||||
                        />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-xs-12 col-sm-5 col-md-1">
 | 
			
		||||
                        <button
 | 
			
		||||
                            class="btn btn-action float-end"
 | 
			
		||||
                            @click="copyDay"
 | 
			
		||||
                        >
 | 
			
		||||
                            {{ $t("copy_range") }}
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </template>
 | 
			
		||||
                <template v-else>
 | 
			
		||||
                    <div class="col-xs-12 col-sm-3 col-md-3">
 | 
			
		||||
                        <select
 | 
			
		||||
                            v-model="copyFromWeek"
 | 
			
		||||
                            id="copyFromWeek"
 | 
			
		||||
                            class="form-select"
 | 
			
		||||
                        >
 | 
			
		||||
                            <option
 | 
			
		||||
                                v-for="w in lastWeeks"
 | 
			
		||||
                                :value="w.value"
 | 
			
		||||
                                :key="w.value"
 | 
			
		||||
                            >
 | 
			
		||||
                                {{ w.text }}
 | 
			
		||||
                            </option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
 | 
			
		||||
                        <i class="fa fa-angle-double-right"></i>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-xs-12 col-sm-3 col-md-3">
 | 
			
		||||
                        <select
 | 
			
		||||
                            v-model="copyToWeek"
 | 
			
		||||
                            id="copyToWeek"
 | 
			
		||||
                            class="form-select"
 | 
			
		||||
                        >
 | 
			
		||||
                            <option
 | 
			
		||||
                                v-for="w in nextWeeks"
 | 
			
		||||
                                :value="w.value"
 | 
			
		||||
                                :key="w.value"
 | 
			
		||||
                            >
 | 
			
		||||
                                {{ w.text }}
 | 
			
		||||
                            </option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-xs-12 col-sm-5 col-md-1">
 | 
			
		||||
                        <button
 | 
			
		||||
                            class="btn btn-action float-end"
 | 
			
		||||
                            @click="copyWeek"
 | 
			
		||||
                        >
 | 
			
		||||
                            {{ $t("copy_range") }}
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </template>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <template v-else>
 | 
			
		||||
          <div class="col-xs-12 col-sm-3 col-md-3">
 | 
			
		||||
            <select
 | 
			
		||||
              v-model="copyFromWeek"
 | 
			
		||||
              id="copyFromWeek"
 | 
			
		||||
              class="form-select"
 | 
			
		||||
            >
 | 
			
		||||
              <option v-for="w in lastWeeks" :value="w.value" :key="w.value">
 | 
			
		||||
                {{ w.text }}
 | 
			
		||||
              </option>
 | 
			
		||||
            </select>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="col-xs-12 col-sm-1 col-md-1 copy-chevron">
 | 
			
		||||
            <i class="fa fa-angle-double-right"></i>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="col-xs-12 col-sm-3 col-md-3">
 | 
			
		||||
            <select v-model="copyToWeek" id="copyToWeek" class="form-select">
 | 
			
		||||
              <option v-for="w in nextWeeks" :value="w.value" :key="w.value">
 | 
			
		||||
                {{ w.text }}
 | 
			
		||||
              </option>
 | 
			
		||||
            </select>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="col-xs-12 col-sm-5 col-md-1">
 | 
			
		||||
            <button class="btn btn-action float-end" @click="copyWeek">
 | 
			
		||||
              {{ $t("copy_range") }}
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </template>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
    <!-- not directly seen, but include in a modal -->
 | 
			
		||||
    <edit-location ref="editLocation"></edit-location>
 | 
			
		||||
  <!-- not directly seen, but include in a modal -->
 | 
			
		||||
  <edit-location ref="editLocation"></edit-location>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import type {
 | 
			
		||||
    CalendarOptions,
 | 
			
		||||
    DatesSetArg,
 | 
			
		||||
    EventInput,
 | 
			
		||||
  CalendarOptions,
 | 
			
		||||
  DatesSetArg,
 | 
			
		||||
  EventInput,
 | 
			
		||||
} from "@fullcalendar/core";
 | 
			
		||||
import { computed, ref, onMounted } from "vue";
 | 
			
		||||
import { useStore } from "vuex";
 | 
			
		||||
@@ -230,14 +179,14 @@ import { key } from "./store";
 | 
			
		||||
import FullCalendar from "@fullcalendar/vue3";
 | 
			
		||||
import frLocale from "@fullcalendar/core/locales/fr";
 | 
			
		||||
import interactionPlugin, {
 | 
			
		||||
    EventResizeDoneArg,
 | 
			
		||||
  EventResizeDoneArg,
 | 
			
		||||
} from "@fullcalendar/interaction";
 | 
			
		||||
import timeGridPlugin from "@fullcalendar/timegrid";
 | 
			
		||||
import {
 | 
			
		||||
    EventApi,
 | 
			
		||||
    DateSelectArg,
 | 
			
		||||
    EventDropArg,
 | 
			
		||||
    EventClickArg,
 | 
			
		||||
  EventApi,
 | 
			
		||||
  DateSelectArg,
 | 
			
		||||
  EventDropArg,
 | 
			
		||||
  EventClickArg,
 | 
			
		||||
} from "@fullcalendar/core";
 | 
			
		||||
import { dateToISO, ISOToDate } from "ChillMainAssets/chill/js/date";
 | 
			
		||||
import VueMultiselect from "vue-multiselect";
 | 
			
		||||
@@ -258,113 +207,96 @@ const copyFromWeek = ref<string | null>(null);
 | 
			
		||||
const copyToWeek = ref<string | null>(null);
 | 
			
		||||
 | 
			
		||||
interface Weeks {
 | 
			
		||||
    value: string | null;
 | 
			
		||||
    text: string;
 | 
			
		||||
  value: string | null;
 | 
			
		||||
  text: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getMonday = (week: number): Date => {
 | 
			
		||||
    const lastMonday = new Date();
 | 
			
		||||
    lastMonday.setDate(
 | 
			
		||||
        lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
 | 
			
		||||
    );
 | 
			
		||||
    return lastMonday;
 | 
			
		||||
  const lastMonday = new Date();
 | 
			
		||||
  lastMonday.setDate(
 | 
			
		||||
    lastMonday.getDate() - ((lastMonday.getDay() + 6) % 7) + week * 7,
 | 
			
		||||
  );
 | 
			
		||||
  return lastMonday;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const dateOptions: Intl.DateTimeFormatOptions = {
 | 
			
		||||
    weekday: "long",
 | 
			
		||||
    year: "numeric",
 | 
			
		||||
    month: "long",
 | 
			
		||||
    day: "numeric",
 | 
			
		||||
  weekday: "long",
 | 
			
		||||
  year: "numeric",
 | 
			
		||||
  month: "long",
 | 
			
		||||
  day: "numeric",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const lastWeeks = computed((): Weeks[] =>
 | 
			
		||||
    Array.from(Array(30).keys()).map((w) => {
 | 
			
		||||
        const lastMonday = getMonday(15 - w);
 | 
			
		||||
        return {
 | 
			
		||||
            value: dateToISO(lastMonday),
 | 
			
		||||
            text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
 | 
			
		||||
        };
 | 
			
		||||
    }),
 | 
			
		||||
  Array.from(Array(30).keys()).map((w) => {
 | 
			
		||||
    const lastMonday = getMonday(15 - w);
 | 
			
		||||
    return {
 | 
			
		||||
      value: dateToISO(lastMonday),
 | 
			
		||||
      text: `Semaine du ${lastMonday.toLocaleDateString("fr-FR", dateOptions)}`,
 | 
			
		||||
    };
 | 
			
		||||
  }),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const nextWeeks = computed((): Weeks[] =>
 | 
			
		||||
    Array.from(Array(52).keys()).map((w) => {
 | 
			
		||||
        const nextMonday = getMonday(w + 1);
 | 
			
		||||
        return {
 | 
			
		||||
            value: dateToISO(nextMonday),
 | 
			
		||||
            text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
 | 
			
		||||
        };
 | 
			
		||||
    }),
 | 
			
		||||
  Array.from(Array(52).keys()).map((w) => {
 | 
			
		||||
    const nextMonday = getMonday(w + 1);
 | 
			
		||||
    return {
 | 
			
		||||
      value: dateToISO(nextMonday),
 | 
			
		||||
      text: `Semaine du ${nextMonday.toLocaleDateString("fr-FR", dateOptions)}`,
 | 
			
		||||
    };
 | 
			
		||||
  }),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const formatDate = (datetime: string, format: null | "time" = null) => {
 | 
			
		||||
    const date = ISOToDate(datetime);
 | 
			
		||||
    if (!date) return "";
 | 
			
		||||
 | 
			
		||||
    if (format === "time") {
 | 
			
		||||
        return date.toLocaleTimeString("fr-FR", {
 | 
			
		||||
            hour: "2-digit",
 | 
			
		||||
            minute: "2-digit",
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // French date formatting
 | 
			
		||||
    return date.toLocaleDateString("fr-FR", {
 | 
			
		||||
        weekday: "short",
 | 
			
		||||
        year: "numeric",
 | 
			
		||||
        month: "short",
 | 
			
		||||
        day: "numeric",
 | 
			
		||||
        hour: "2-digit",
 | 
			
		||||
        minute: "2-digit",
 | 
			
		||||
    });
 | 
			
		||||
const formatDate = (datetime: string) => {
 | 
			
		||||
  console.log(typeof datetime);
 | 
			
		||||
  return ISOToDate(datetime);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const baseOptions = ref<CalendarOptions>({
 | 
			
		||||
    locale: frLocale,
 | 
			
		||||
    plugins: [interactionPlugin, timeGridPlugin],
 | 
			
		||||
    initialView: "timeGridWeek",
 | 
			
		||||
    initialDate: new Date(),
 | 
			
		||||
    scrollTimeReset: false,
 | 
			
		||||
    selectable: true,
 | 
			
		||||
    // when the dates are changes in the fullcalendar view OR when new events are added
 | 
			
		||||
    datesSet: onDatesSet,
 | 
			
		||||
    // when a date is selected
 | 
			
		||||
    select: onDateSelect,
 | 
			
		||||
    // when a event is resized
 | 
			
		||||
    eventResize: onEventDropOrResize,
 | 
			
		||||
    // when an event is moved
 | 
			
		||||
    eventDrop: onEventDropOrResize,
 | 
			
		||||
    // when an event si clicked
 | 
			
		||||
    eventClick: onEventClick,
 | 
			
		||||
    selectMirror: false,
 | 
			
		||||
    editable: true,
 | 
			
		||||
    headerToolbar: {
 | 
			
		||||
        left: "prev,next today",
 | 
			
		||||
        center: "title",
 | 
			
		||||
        right: "timeGridWeek,timeGridDay",
 | 
			
		||||
    },
 | 
			
		||||
  locale: frLocale,
 | 
			
		||||
  plugins: [interactionPlugin, timeGridPlugin],
 | 
			
		||||
  initialView: "timeGridWeek",
 | 
			
		||||
  initialDate: new Date(),
 | 
			
		||||
  scrollTimeReset: false,
 | 
			
		||||
  selectable: true,
 | 
			
		||||
  // when the dates are changes in the fullcalendar view OR when new events are added
 | 
			
		||||
  datesSet: onDatesSet,
 | 
			
		||||
  // when a date is selected
 | 
			
		||||
  select: onDateSelect,
 | 
			
		||||
  // when a event is resized
 | 
			
		||||
  eventResize: onEventDropOrResize,
 | 
			
		||||
  // when an event is moved
 | 
			
		||||
  eventDrop: onEventDropOrResize,
 | 
			
		||||
  // when an event si clicked
 | 
			
		||||
  eventClick: onEventClick,
 | 
			
		||||
  selectMirror: false,
 | 
			
		||||
  editable: true,
 | 
			
		||||
  headerToolbar: {
 | 
			
		||||
    left: "prev,next today",
 | 
			
		||||
    center: "title",
 | 
			
		||||
    right: "timeGridWeek,timeGridDay",
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const ranges = computed<EventInput[]>(() => {
 | 
			
		||||
    return store.state.calendarRanges.ranges;
 | 
			
		||||
  return store.state.calendarRanges.ranges;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const locations = computed<Location[]>(() => {
 | 
			
		||||
    return store.state.locations.locations;
 | 
			
		||||
  return store.state.locations.locations;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const pickedLocation = computed<Location | null>({
 | 
			
		||||
    get(): Location | null {
 | 
			
		||||
        return (
 | 
			
		||||
            store.state.locations.locationPicked ||
 | 
			
		||||
            store.state.locations.currentLocation
 | 
			
		||||
        );
 | 
			
		||||
    },
 | 
			
		||||
    set(newLocation: Location | null): void {
 | 
			
		||||
        store.commit("locations/setLocationPicked", newLocation, {
 | 
			
		||||
            root: true,
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
  get(): Location | null {
 | 
			
		||||
    return (
 | 
			
		||||
      store.state.locations.locationPicked ||
 | 
			
		||||
      store.state.locations.currentLocation
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
  set(newLocation: Location | null): void {
 | 
			
		||||
    store.commit("locations/setLocationPicked", newLocation, {
 | 
			
		||||
      root: true,
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -393,116 +325,116 @@ const sources = computed<EventSourceInput[]>(() => {
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
const calendarOptions = computed((): CalendarOptions => {
 | 
			
		||||
    return {
 | 
			
		||||
        ...baseOptions.value,
 | 
			
		||||
        weekends: showWeekends.value,
 | 
			
		||||
        slotDuration: slotDuration.value,
 | 
			
		||||
        events: ranges.value,
 | 
			
		||||
        slotMinTime: slotMinTime.value,
 | 
			
		||||
        slotMaxTime: slotMaxTime.value,
 | 
			
		||||
    };
 | 
			
		||||
  return {
 | 
			
		||||
    ...baseOptions.value,
 | 
			
		||||
    weekends: showWeekends.value,
 | 
			
		||||
    slotDuration: slotDuration.value,
 | 
			
		||||
    events: ranges.value,
 | 
			
		||||
    slotMinTime: slotMinTime.value,
 | 
			
		||||
    slotMaxTime: slotMaxTime.value,
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * launched when the calendar range date change
 | 
			
		||||
 */
 | 
			
		||||
function onDatesSet(event: DatesSetArg): void {
 | 
			
		||||
    store.dispatch("fullCalendar/setCurrentDatesView", {
 | 
			
		||||
        start: event.start,
 | 
			
		||||
        end: event.end,
 | 
			
		||||
    });
 | 
			
		||||
  store.dispatch("fullCalendar/setCurrentDatesView", {
 | 
			
		||||
    start: event.start,
 | 
			
		||||
    end: event.end,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onDateSelect(event: DateSelectArg): void {
 | 
			
		||||
    if (null === pickedLocation.value) {
 | 
			
		||||
        window.alert(
 | 
			
		||||
            "Indiquez une localisation avant de créer une période de disponibilité.",
 | 
			
		||||
        );
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
  if (null === pickedLocation.value) {
 | 
			
		||||
    window.alert(
 | 
			
		||||
      "Indiquez une localisation avant de créer une période de disponibilité.",
 | 
			
		||||
    );
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    store.dispatch("calendarRanges/createRange", {
 | 
			
		||||
        start: event.start,
 | 
			
		||||
        end: event.end,
 | 
			
		||||
        location: pickedLocation.value,
 | 
			
		||||
    });
 | 
			
		||||
  store.dispatch("calendarRanges/createRange", {
 | 
			
		||||
    start: event.start,
 | 
			
		||||
    end: event.end,
 | 
			
		||||
    location: pickedLocation.value,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * When a calendar range is deleted
 | 
			
		||||
 */
 | 
			
		||||
function onClickDelete(event: EventApi): void {
 | 
			
		||||
    if (event.extendedProps.is !== "range") {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
  if (event.extendedProps.is !== "range") {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    store.dispatch(
 | 
			
		||||
        "calendarRanges/deleteRange",
 | 
			
		||||
        event.extendedProps.calendarRangeId,
 | 
			
		||||
    );
 | 
			
		||||
  store.dispatch(
 | 
			
		||||
    "calendarRanges/deleteRange",
 | 
			
		||||
    event.extendedProps.calendarRangeId,
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onEventDropOrResize(payload: EventDropArg | EventResizeDoneArg) {
 | 
			
		||||
    if (payload.event.extendedProps.is !== "range") {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
  if (payload.event.extendedProps.is !== "range") {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    store.dispatch("calendarRanges/patchRangeTime", {
 | 
			
		||||
        calendarRangeId: payload.event.extendedProps.calendarRangeId,
 | 
			
		||||
        start: payload.event.start,
 | 
			
		||||
        end: payload.event.end,
 | 
			
		||||
    });
 | 
			
		||||
  store.dispatch("calendarRanges/patchRangeTime", {
 | 
			
		||||
    calendarRangeId: payload.event.extendedProps.calendarRangeId,
 | 
			
		||||
    start: payload.event.start,
 | 
			
		||||
    end: payload.event.end,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onEventClick(payload: EventClickArg): void {
 | 
			
		||||
    // @ts-ignore TS does not recognize the target. But it does exists.
 | 
			
		||||
    if (payload.jsEvent.target.classList.contains("delete")) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (payload.event.extendedProps.is !== "range") {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
  // @ts-ignore TS does not recognize the target. But it does exists.
 | 
			
		||||
  if (payload.jsEvent.target.classList.contains("delete")) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (payload.event.extendedProps.is !== "range") {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    editLocation.value?.startEdit(payload.event);
 | 
			
		||||
  editLocation.value?.startEdit(payload.event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function copyDay() {
 | 
			
		||||
    if (null === copyFrom.value || null === copyTo.value) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
 | 
			
		||||
        from: ISOToDate(copyFrom.value),
 | 
			
		||||
        to: ISOToDate(copyTo.value),
 | 
			
		||||
    });
 | 
			
		||||
  if (null === copyFrom.value || null === copyTo.value) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  store.dispatch("calendarRanges/copyFromDayToAnotherDay", {
 | 
			
		||||
    from: ISOToDate(copyFrom.value),
 | 
			
		||||
    to: ISOToDate(copyTo.value),
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function copyWeek() {
 | 
			
		||||
    if (null === copyFromWeek.value || null === copyToWeek.value) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
 | 
			
		||||
        fromMonday: ISOToDate(copyFromWeek.value),
 | 
			
		||||
        toMonday: ISOToDate(copyToWeek.value),
 | 
			
		||||
    });
 | 
			
		||||
  if (null === copyFromWeek.value || null === copyToWeek.value) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  store.dispatch("calendarRanges/copyFromWeekToAnotherWeek", {
 | 
			
		||||
    fromMonday: ISOToDate(copyFromWeek.value),
 | 
			
		||||
    toMonday: ISOToDate(copyToWeek.value),
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    copyFromWeek.value = dateToISO(getMonday(0));
 | 
			
		||||
    copyToWeek.value = dateToISO(getMonday(1));
 | 
			
		||||
  copyFromWeek.value = dateToISO(getMonday(0));
 | 
			
		||||
  copyToWeek.value = dateToISO(getMonday(1));
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
#copy-widget {
 | 
			
		||||
    position: sticky;
 | 
			
		||||
    bottom: 0px;
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    z-index: 9999999999;
 | 
			
		||||
    padding: 0.25rem 0 0.25rem;
 | 
			
		||||
  position: sticky;
 | 
			
		||||
  bottom: 0px;
 | 
			
		||||
  background-color: white;
 | 
			
		||||
  z-index: 9999999999;
 | 
			
		||||
  padding: 0.25rem 0 0.25rem;
 | 
			
		||||
}
 | 
			
		||||
div.copy-chevron {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    font-size: x-large;
 | 
			
		||||
    width: 2rem;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  font-size: x-large;
 | 
			
		||||
  width: 2rem;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,28 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <component :is="Teleport" to="body">
 | 
			
		||||
        <modal v-if="showModal" @close="closeModal">
 | 
			
		||||
            <template v-slot:header>
 | 
			
		||||
                <h3>{{ "Modifier le lieu" }}</h3>
 | 
			
		||||
            </template>
 | 
			
		||||
  <component :is="Teleport" to="body">
 | 
			
		||||
    <modal v-if="showModal" @close="closeModal">
 | 
			
		||||
      <template v-slot:header>
 | 
			
		||||
        <h3>{{ "Modifier le lieu" }}</h3>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
            <template v-slot:body>
 | 
			
		||||
                <div></div>
 | 
			
		||||
                <label>Localisation</label>
 | 
			
		||||
                <vue-multiselect
 | 
			
		||||
                    v-model="location"
 | 
			
		||||
                    :options="locations"
 | 
			
		||||
                    :label="'name'"
 | 
			
		||||
                    :track-by="'id'"
 | 
			
		||||
                ></vue-multiselect>
 | 
			
		||||
            </template>
 | 
			
		||||
      <template v-slot:body>
 | 
			
		||||
        <div></div>
 | 
			
		||||
        <label>Localisation</label>
 | 
			
		||||
        <vue-multiselect
 | 
			
		||||
          v-model="location"
 | 
			
		||||
          :options="locations"
 | 
			
		||||
          :label="'name'"
 | 
			
		||||
          :track-by="'id'"
 | 
			
		||||
        ></vue-multiselect>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
            <template v-slot:footer>
 | 
			
		||||
                <button class="btn btn-save" @click="saveAndClose">
 | 
			
		||||
                    {{ "Enregistrer" }}
 | 
			
		||||
                </button>
 | 
			
		||||
            </template>
 | 
			
		||||
        </modal>
 | 
			
		||||
    </component>
 | 
			
		||||
      <template v-slot:footer>
 | 
			
		||||
        <button class="btn btn-save" @click="saveAndClose">
 | 
			
		||||
          {{ "Enregistrer" }}
 | 
			
		||||
        </button>
 | 
			
		||||
      </template>
 | 
			
		||||
    </modal>
 | 
			
		||||
  </component>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
@@ -39,7 +39,7 @@ import VueMultiselect from "vue-multiselect";
 | 
			
		||||
import { Teleport as teleport_, TeleportProps, VNodeProps } from "vue";
 | 
			
		||||
 | 
			
		||||
const Teleport = teleport_ as new () => {
 | 
			
		||||
    $props: VNodeProps & TeleportProps;
 | 
			
		||||
  $props: VNodeProps & TeleportProps;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const store = useStore(key);
 | 
			
		||||
@@ -50,37 +50,37 @@ const showModal = ref(false);
 | 
			
		||||
//const tele = ref<InstanceType<typeof Teleport> | null>(null);
 | 
			
		||||
 | 
			
		||||
const locations = computed<Location[]>(() => {
 | 
			
		||||
    return store.state.locations.locations;
 | 
			
		||||
  return store.state.locations.locations;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const startEdit = function (event: EventApi): void {
 | 
			
		||||
    console.log("startEditing", event);
 | 
			
		||||
    calendarRangeId.value = event.extendedProps.calendarRangeId;
 | 
			
		||||
    location.value =
 | 
			
		||||
        store.getters["locations/getLocationById"](
 | 
			
		||||
            event.extendedProps.locationId,
 | 
			
		||||
        ) || null;
 | 
			
		||||
  console.log("startEditing", event);
 | 
			
		||||
  calendarRangeId.value = event.extendedProps.calendarRangeId;
 | 
			
		||||
  location.value =
 | 
			
		||||
    store.getters["locations/getLocationById"](
 | 
			
		||||
      event.extendedProps.locationId,
 | 
			
		||||
    ) || null;
 | 
			
		||||
 | 
			
		||||
    console.log("new location value", location.value);
 | 
			
		||||
    console.log("calendar range id", calendarRangeId.value);
 | 
			
		||||
    showModal.value = true;
 | 
			
		||||
  console.log("new location value", location.value);
 | 
			
		||||
  console.log("calendar range id", calendarRangeId.value);
 | 
			
		||||
  showModal.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const saveAndClose = function (e: Event): void {
 | 
			
		||||
    console.log("saveEditAndClose", e);
 | 
			
		||||
  console.log("saveEditAndClose", e);
 | 
			
		||||
 | 
			
		||||
    store
 | 
			
		||||
        .dispatch("calendarRanges/patchRangeLocation", {
 | 
			
		||||
            location: location.value,
 | 
			
		||||
            calendarRangeId: calendarRangeId.value,
 | 
			
		||||
        })
 | 
			
		||||
        .then((_) => {
 | 
			
		||||
            showModal.value = false;
 | 
			
		||||
        });
 | 
			
		||||
  store
 | 
			
		||||
    .dispatch("calendarRanges/patchRangeLocation", {
 | 
			
		||||
      location: location.value,
 | 
			
		||||
      calendarRangeId: calendarRangeId.value,
 | 
			
		||||
    })
 | 
			
		||||
    .then((_) => {
 | 
			
		||||
      showModal.value = false;
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const closeModal = function (_: any): void {
 | 
			
		||||
    showModal.value = false;
 | 
			
		||||
  showModal.value = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
defineExpose({ startEdit });
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,27 @@
 | 
			
		||||
const appMessages = {
 | 
			
		||||
    fr: {
 | 
			
		||||
        created_availabilities: "Lieu des plages de disponibilités créées",
 | 
			
		||||
        edit_your_calendar_range: "Planifiez vos plages de disponibilités",
 | 
			
		||||
        show_my_calendar: "Afficher mon calendrier",
 | 
			
		||||
        show_weekends: "Afficher les week-ends",
 | 
			
		||||
        copy_range: "Copier",
 | 
			
		||||
        copy_range_from_to: "Copier les plages",
 | 
			
		||||
        from_day_to_day: "d'un jour à l'autre",
 | 
			
		||||
        from_week_to_week: "d'une semaine à l'autre",
 | 
			
		||||
        copy_range_how_to:
 | 
			
		||||
            "Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
 | 
			
		||||
        new_range_to_save: "Nouvelles plages à enregistrer",
 | 
			
		||||
        update_range_to_save: "Plages à modifier",
 | 
			
		||||
        delete_range_to_save: "Plages à supprimer",
 | 
			
		||||
        by: "Par",
 | 
			
		||||
        main_user_concerned: "Utilisateur concerné",
 | 
			
		||||
        dateFrom: "De",
 | 
			
		||||
        dateTo: "à",
 | 
			
		||||
        day: "Jour",
 | 
			
		||||
        week: "Semaine",
 | 
			
		||||
        month: "Mois",
 | 
			
		||||
        today: "Aujourd'hui",
 | 
			
		||||
    },
 | 
			
		||||
  fr: {
 | 
			
		||||
    created_availabilities: "Lieu des plages de disponibilités créées",
 | 
			
		||||
    edit_your_calendar_range: "Planifiez vos plages de disponibilités",
 | 
			
		||||
    show_my_calendar: "Afficher mon calendrier",
 | 
			
		||||
    show_weekends: "Afficher les week-ends",
 | 
			
		||||
    copy_range: "Copier",
 | 
			
		||||
    copy_range_from_to: "Copier les plages",
 | 
			
		||||
    from_day_to_day: "d'un jour à l'autre",
 | 
			
		||||
    from_week_to_week: "d'une semaine à l'autre",
 | 
			
		||||
    copy_range_how_to:
 | 
			
		||||
      "Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.",
 | 
			
		||||
    new_range_to_save: "Nouvelles plages à enregistrer",
 | 
			
		||||
    update_range_to_save: "Plages à modifier",
 | 
			
		||||
    delete_range_to_save: "Plages à supprimer",
 | 
			
		||||
    by: "Par",
 | 
			
		||||
    main_user_concerned: "Utilisateur concerné",
 | 
			
		||||
    dateFrom: "De",
 | 
			
		||||
    dateTo: "à",
 | 
			
		||||
    day: "Jour",
 | 
			
		||||
    week: "Semaine",
 | 
			
		||||
    month: "Mois",
 | 
			
		||||
    today: "Aujourd'hui",
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { appMessages };
 | 
			
		||||
 
 | 
			
		||||
@@ -7,13 +7,13 @@ import App2 from "./App2.vue";
 | 
			
		||||
import { useI18n } from "vue-i18n";
 | 
			
		||||
 | 
			
		||||
futureStore().then((store) => {
 | 
			
		||||
    const i18n = _createI18n(appMessages, false);
 | 
			
		||||
  const i18n = _createI18n(appMessages, false);
 | 
			
		||||
 | 
			
		||||
    const app = createApp({
 | 
			
		||||
        template: `<app></app>`,
 | 
			
		||||
    })
 | 
			
		||||
        .use(store, key)
 | 
			
		||||
        .use(i18n)
 | 
			
		||||
        .component("app", App2)
 | 
			
		||||
        .mount("#myCalendar");
 | 
			
		||||
  const app = createApp({
 | 
			
		||||
    template: `<app></app>`,
 | 
			
		||||
  })
 | 
			
		||||
    .use(store, key)
 | 
			
		||||
    .use(i18n)
 | 
			
		||||
    .component("app", App2)
 | 
			
		||||
    .mount("#myCalendar");
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import me, { MeState } from "./modules/me";
 | 
			
		||||
import fullCalendar, { FullCalendarState } from "./modules/fullcalendar";
 | 
			
		||||
import calendarRanges, { CalendarRangesState } from "./modules/calendarRanges";
 | 
			
		||||
import calendarRemotes, {
 | 
			
		||||
    CalendarRemotesState,
 | 
			
		||||
  CalendarRemotesState,
 | 
			
		||||
} from "./modules/calendarRemotes";
 | 
			
		||||
import { whoami } from "../../../../../../ChillMainBundle/Resources/public/lib/api/user";
 | 
			
		||||
import { User } from "../../../../../../ChillMainBundle/Resources/public/types";
 | 
			
		||||
@@ -15,42 +15,40 @@ import calendarLocals, { CalendarLocalsState } from "./modules/calendarLocals";
 | 
			
		||||
const debug = process.env.NODE_ENV !== "production";
 | 
			
		||||
 | 
			
		||||
export interface State {
 | 
			
		||||
    calendarRanges: CalendarRangesState;
 | 
			
		||||
    calendarRemotes: CalendarRemotesState;
 | 
			
		||||
    calendarLocals: CalendarLocalsState;
 | 
			
		||||
    fullCalendar: FullCalendarState;
 | 
			
		||||
    me: MeState;
 | 
			
		||||
    locations: LocationState;
 | 
			
		||||
  calendarRanges: CalendarRangesState;
 | 
			
		||||
  calendarRemotes: CalendarRemotesState;
 | 
			
		||||
  calendarLocals: CalendarLocalsState;
 | 
			
		||||
  fullCalendar: FullCalendarState;
 | 
			
		||||
  me: MeState;
 | 
			
		||||
  locations: LocationState;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const key: InjectionKey<Store<State>> = Symbol();
 | 
			
		||||
 | 
			
		||||
const futureStore = function (): Promise<Store<State>> {
 | 
			
		||||
    return whoami().then((user: User) => {
 | 
			
		||||
        const store = createStore<State>({
 | 
			
		||||
            strict: debug,
 | 
			
		||||
            modules: {
 | 
			
		||||
                me,
 | 
			
		||||
                fullCalendar,
 | 
			
		||||
                calendarRanges,
 | 
			
		||||
                calendarRemotes,
 | 
			
		||||
                calendarLocals,
 | 
			
		||||
                locations,
 | 
			
		||||
            },
 | 
			
		||||
            mutations: {},
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        store.commit("me/setWhoAmi", user, { root: true });
 | 
			
		||||
        store
 | 
			
		||||
            .dispatch("locations/getLocations", null, { root: true })
 | 
			
		||||
            .then((_) => {
 | 
			
		||||
                return store.dispatch("locations/getCurrentLocation", null, {
 | 
			
		||||
                    root: true,
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve(store);
 | 
			
		||||
  return whoami().then((user: User) => {
 | 
			
		||||
    const store = createStore<State>({
 | 
			
		||||
      strict: debug,
 | 
			
		||||
      modules: {
 | 
			
		||||
        me,
 | 
			
		||||
        fullCalendar,
 | 
			
		||||
        calendarRanges,
 | 
			
		||||
        calendarRemotes,
 | 
			
		||||
        calendarLocals,
 | 
			
		||||
        locations,
 | 
			
		||||
      },
 | 
			
		||||
      mutations: {},
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    store.commit("me/setWhoAmi", user, { root: true });
 | 
			
		||||
    store.dispatch("locations/getLocations", null, { root: true }).then((_) => {
 | 
			
		||||
      return store.dispatch("locations/getCurrentLocation", null, {
 | 
			
		||||
        root: true,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return Promise.resolve(store);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default futureStore;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,109 +8,99 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
 | 
			
		||||
import { COLORS } from "../../../Calendar/const";
 | 
			
		||||
 | 
			
		||||
export interface CalendarLocalsState {
 | 
			
		||||
    locals: EventInput[];
 | 
			
		||||
    localsLoaded: { start: number; end: number }[];
 | 
			
		||||
    localsIndex: Set<string>;
 | 
			
		||||
    key: number;
 | 
			
		||||
  locals: EventInput[];
 | 
			
		||||
  localsLoaded: { start: number; end: number }[];
 | 
			
		||||
  localsIndex: Set<string>;
 | 
			
		||||
  key: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Context = ActionContext<CalendarLocalsState, State>;
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    namespaced: true,
 | 
			
		||||
    state: (): CalendarLocalsState => ({
 | 
			
		||||
        locals: [],
 | 
			
		||||
        localsLoaded: [],
 | 
			
		||||
        localsIndex: new Set<string>(),
 | 
			
		||||
        key: 0,
 | 
			
		||||
    }),
 | 
			
		||||
    getters: {
 | 
			
		||||
        isLocalsLoaded:
 | 
			
		||||
            (state: CalendarLocalsState) =>
 | 
			
		||||
            ({ start, end }: { start: Date; end: Date }): boolean => {
 | 
			
		||||
                for (const range of state.localsLoaded) {
 | 
			
		||||
                    if (
 | 
			
		||||
                        start.getTime() === range.start &&
 | 
			
		||||
                        end.getTime() === range.end
 | 
			
		||||
                    ) {
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
  namespaced: true,
 | 
			
		||||
  state: (): CalendarLocalsState => ({
 | 
			
		||||
    locals: [],
 | 
			
		||||
    localsLoaded: [],
 | 
			
		||||
    localsIndex: new Set<string>(),
 | 
			
		||||
    key: 0,
 | 
			
		||||
  }),
 | 
			
		||||
  getters: {
 | 
			
		||||
    isLocalsLoaded:
 | 
			
		||||
      (state: CalendarLocalsState) =>
 | 
			
		||||
      ({ start, end }: { start: Date; end: Date }): boolean => {
 | 
			
		||||
        for (const range of state.localsLoaded) {
 | 
			
		||||
          if (start.getTime() === range.start && end.getTime() === range.end) {
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                return false;
 | 
			
		||||
            },
 | 
			
		||||
        return false;
 | 
			
		||||
      },
 | 
			
		||||
  },
 | 
			
		||||
  mutations: {
 | 
			
		||||
    addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
 | 
			
		||||
      console.log("addLocals", ranges);
 | 
			
		||||
 | 
			
		||||
      const toAdd = ranges
 | 
			
		||||
        .map((cr) => localsToFullCalendarEvent(cr))
 | 
			
		||||
        .filter((r) => !state.localsIndex.has(r.id));
 | 
			
		||||
 | 
			
		||||
      toAdd.forEach((r) => {
 | 
			
		||||
        state.localsIndex.add(r.id);
 | 
			
		||||
        state.locals.push(r);
 | 
			
		||||
      });
 | 
			
		||||
      state.key = state.key + toAdd.length;
 | 
			
		||||
    },
 | 
			
		||||
    mutations: {
 | 
			
		||||
        addLocals(state: CalendarLocalsState, ranges: CalendarLight[]) {
 | 
			
		||||
            console.log("addLocals", ranges);
 | 
			
		||||
 | 
			
		||||
            const toAdd = ranges
 | 
			
		||||
                .map((cr) => localsToFullCalendarEvent(cr))
 | 
			
		||||
                .filter((r) => !state.localsIndex.has(r.id));
 | 
			
		||||
 | 
			
		||||
            toAdd.forEach((r) => {
 | 
			
		||||
                state.localsIndex.add(r.id);
 | 
			
		||||
                state.locals.push(r);
 | 
			
		||||
            });
 | 
			
		||||
            state.key = state.key + toAdd.length;
 | 
			
		||||
        },
 | 
			
		||||
        addLoaded(
 | 
			
		||||
            state: CalendarLocalsState,
 | 
			
		||||
            payload: { start: Date; end: Date },
 | 
			
		||||
        ) {
 | 
			
		||||
            state.localsLoaded.push({
 | 
			
		||||
                start: payload.start.getTime(),
 | 
			
		||||
                end: payload.end.getTime(),
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
    addLoaded(state: CalendarLocalsState, payload: { start: Date; end: Date }) {
 | 
			
		||||
      state.localsLoaded.push({
 | 
			
		||||
        start: payload.start.getTime(),
 | 
			
		||||
        end: payload.end.getTime(),
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    actions: {
 | 
			
		||||
        fetchLocals(
 | 
			
		||||
            ctx: Context,
 | 
			
		||||
            payload: { start: Date; end: Date },
 | 
			
		||||
        ): Promise<null> {
 | 
			
		||||
            const start = payload.start;
 | 
			
		||||
            const end = payload.end;
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    fetchLocals(
 | 
			
		||||
      ctx: Context,
 | 
			
		||||
      payload: { start: Date; end: Date },
 | 
			
		||||
    ): Promise<null> {
 | 
			
		||||
      const start = payload.start;
 | 
			
		||||
      const end = payload.end;
 | 
			
		||||
 | 
			
		||||
            if (ctx.rootGetters["me/getMe"] === null) {
 | 
			
		||||
                return Promise.resolve(null);
 | 
			
		||||
            }
 | 
			
		||||
      if (ctx.rootGetters["me/getMe"] === null) {
 | 
			
		||||
        return Promise.resolve(null);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
            if (ctx.getters.isLocalsLoaded({ start, end })) {
 | 
			
		||||
                return Promise.resolve(ctx.getters.getRangeSource);
 | 
			
		||||
            }
 | 
			
		||||
      if (ctx.getters.isLocalsLoaded({ start, end })) {
 | 
			
		||||
        return Promise.resolve(ctx.getters.getRangeSource);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
            ctx.commit("addLoaded", {
 | 
			
		||||
                start: start,
 | 
			
		||||
                end: end,
 | 
			
		||||
            });
 | 
			
		||||
      ctx.commit("addLoaded", {
 | 
			
		||||
        start: start,
 | 
			
		||||
        end: end,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
            return fetchCalendarLocalForUser(
 | 
			
		||||
                ctx.rootGetters["me/getMe"],
 | 
			
		||||
                start,
 | 
			
		||||
                end,
 | 
			
		||||
            )
 | 
			
		||||
                .then((remotes: CalendarLight[]) => {
 | 
			
		||||
                    // to be add when reactivity problem will be solve ?
 | 
			
		||||
                    //ctx.commit('addRemotes', remotes);
 | 
			
		||||
                    const inputs = remotes
 | 
			
		||||
                        .map((cr) => localsToFullCalendarEvent(cr))
 | 
			
		||||
                        .map((cr) => ({
 | 
			
		||||
                            ...cr,
 | 
			
		||||
                            backgroundColor: COLORS[0],
 | 
			
		||||
                            textColor: "black",
 | 
			
		||||
                            editable: false,
 | 
			
		||||
                        }));
 | 
			
		||||
                    ctx.commit("calendarRanges/addExternals", inputs, {
 | 
			
		||||
                        root: true,
 | 
			
		||||
                    });
 | 
			
		||||
                    return Promise.resolve(null);
 | 
			
		||||
                })
 | 
			
		||||
                .catch((e: TransportExceptionInterface) => {
 | 
			
		||||
                    console.error(e);
 | 
			
		||||
      return fetchCalendarLocalForUser(ctx.rootGetters["me/getMe"], start, end)
 | 
			
		||||
        .then((remotes: CalendarLight[]) => {
 | 
			
		||||
          // to be add when reactivity problem will be solve ?
 | 
			
		||||
          //ctx.commit('addRemotes', remotes);
 | 
			
		||||
          const inputs = remotes
 | 
			
		||||
            .map((cr) => localsToFullCalendarEvent(cr))
 | 
			
		||||
            .map((cr) => ({
 | 
			
		||||
              ...cr,
 | 
			
		||||
              backgroundColor: COLORS[0],
 | 
			
		||||
              textColor: "black",
 | 
			
		||||
              editable: false,
 | 
			
		||||
            }));
 | 
			
		||||
          ctx.commit("calendarRanges/addExternals", inputs, {
 | 
			
		||||
            root: true,
 | 
			
		||||
          });
 | 
			
		||||
          return Promise.resolve(null);
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e: TransportExceptionInterface) => {
 | 
			
		||||
          console.error(e);
 | 
			
		||||
 | 
			
		||||
                    return Promise.resolve(null);
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
          return Promise.resolve(null);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
} as Module<CalendarLocalsState, State>;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
import { State } from "./../index";
 | 
			
		||||
import { ActionContext, Module } from "vuex";
 | 
			
		||||
import {
 | 
			
		||||
    CalendarRange,
 | 
			
		||||
    CalendarRangeCreate,
 | 
			
		||||
    CalendarRangeEdit,
 | 
			
		||||
    isEventInputCalendarRange,
 | 
			
		||||
  CalendarRange,
 | 
			
		||||
  CalendarRangeCreate,
 | 
			
		||||
  CalendarRangeEdit,
 | 
			
		||||
  isEventInputCalendarRange,
 | 
			
		||||
} from "../../../../types";
 | 
			
		||||
import { Location } from "../../../../../../../ChillMainBundle/Resources/public/types";
 | 
			
		||||
import { fetchCalendarRangeForUser } from "../../../Calendar/api";
 | 
			
		||||
@@ -12,369 +12,332 @@ import { calendarRangeToFullCalendarEvent } from "../../../Calendar/store/utils"
 | 
			
		||||
import { EventInput } from "@fullcalendar/core";
 | 
			
		||||
import { makeFetch } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
 | 
			
		||||
import {
 | 
			
		||||
    datetimeToISO,
 | 
			
		||||
    dateToISO,
 | 
			
		||||
    ISOToDatetime,
 | 
			
		||||
  datetimeToISO,
 | 
			
		||||
  dateToISO,
 | 
			
		||||
  ISOToDatetime,
 | 
			
		||||
} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
 | 
			
		||||
import type { EventInputCalendarRange } from "../../../../types";
 | 
			
		||||
 | 
			
		||||
export interface CalendarRangesState {
 | 
			
		||||
    ranges: (EventInput | EventInputCalendarRange)[];
 | 
			
		||||
    rangesLoaded: { start: number; end: number }[];
 | 
			
		||||
    rangesIndex: Set<string>;
 | 
			
		||||
    key: number;
 | 
			
		||||
  ranges: (EventInput | EventInputCalendarRange)[];
 | 
			
		||||
  rangesLoaded: { start: number; end: number }[];
 | 
			
		||||
  rangesIndex: Set<string>;
 | 
			
		||||
  key: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Context = ActionContext<CalendarRangesState, State>;
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    namespaced: true,
 | 
			
		||||
    state: (): CalendarRangesState => ({
 | 
			
		||||
        ranges: [],
 | 
			
		||||
        rangesLoaded: [],
 | 
			
		||||
        rangesIndex: new Set<string>(),
 | 
			
		||||
        key: 0,
 | 
			
		||||
    }),
 | 
			
		||||
    getters: {
 | 
			
		||||
        isRangeLoaded:
 | 
			
		||||
            (state: CalendarRangesState) =>
 | 
			
		||||
            ({ start, end }: { start: Date; end: Date }): boolean => {
 | 
			
		||||
                for (const range of state.rangesLoaded) {
 | 
			
		||||
                    if (
 | 
			
		||||
                        start.getTime() === range.start &&
 | 
			
		||||
                        end.getTime() === range.end
 | 
			
		||||
                    ) {
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
  namespaced: true,
 | 
			
		||||
  state: (): CalendarRangesState => ({
 | 
			
		||||
    ranges: [],
 | 
			
		||||
    rangesLoaded: [],
 | 
			
		||||
    rangesIndex: new Set<string>(),
 | 
			
		||||
    key: 0,
 | 
			
		||||
  }),
 | 
			
		||||
  getters: {
 | 
			
		||||
    isRangeLoaded:
 | 
			
		||||
      (state: CalendarRangesState) =>
 | 
			
		||||
      ({ start, end }: { start: Date; end: Date }): boolean => {
 | 
			
		||||
        for (const range of state.rangesLoaded) {
 | 
			
		||||
          if (start.getTime() === range.start && end.getTime() === range.end) {
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                return false;
 | 
			
		||||
            },
 | 
			
		||||
        getRangesOnDate:
 | 
			
		||||
            (state: CalendarRangesState) =>
 | 
			
		||||
            (date: Date): EventInputCalendarRange[] => {
 | 
			
		||||
                const founds = [];
 | 
			
		||||
                const dateStr = dateToISO(date) as string;
 | 
			
		||||
        return false;
 | 
			
		||||
      },
 | 
			
		||||
    getRangesOnDate:
 | 
			
		||||
      (state: CalendarRangesState) =>
 | 
			
		||||
      (date: Date): EventInputCalendarRange[] => {
 | 
			
		||||
        const founds = [];
 | 
			
		||||
        const dateStr = dateToISO(date) as string;
 | 
			
		||||
 | 
			
		||||
                for (const range of state.ranges) {
 | 
			
		||||
                    if (
 | 
			
		||||
                        isEventInputCalendarRange(range) &&
 | 
			
		||||
                        range.start.startsWith(dateStr)
 | 
			
		||||
                    ) {
 | 
			
		||||
                        founds.push(range);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
        for (const range of state.ranges) {
 | 
			
		||||
          if (
 | 
			
		||||
            isEventInputCalendarRange(range) &&
 | 
			
		||||
            range.start.startsWith(dateStr)
 | 
			
		||||
          ) {
 | 
			
		||||
            founds.push(range);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                return founds;
 | 
			
		||||
            },
 | 
			
		||||
        getRangesOnWeek:
 | 
			
		||||
            (state: CalendarRangesState) =>
 | 
			
		||||
            (mondayDate: Date): EventInputCalendarRange[] => {
 | 
			
		||||
                const founds = [];
 | 
			
		||||
                for (const d of Array.from(Array(7).keys())) {
 | 
			
		||||
                    const dateOfWeek = new Date(mondayDate);
 | 
			
		||||
                    dateOfWeek.setDate(mondayDate.getDate() + d);
 | 
			
		||||
                    const dateStr = dateToISO(dateOfWeek) as string;
 | 
			
		||||
                    for (const range of state.ranges) {
 | 
			
		||||
                        if (
 | 
			
		||||
                            isEventInputCalendarRange(range) &&
 | 
			
		||||
                            range.start.startsWith(dateStr)
 | 
			
		||||
                        ) {
 | 
			
		||||
                            founds.push(range);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
        return founds;
 | 
			
		||||
      },
 | 
			
		||||
    getRangesOnWeek:
 | 
			
		||||
      (state: CalendarRangesState) =>
 | 
			
		||||
      (mondayDate: Date): EventInputCalendarRange[] => {
 | 
			
		||||
        const founds = [];
 | 
			
		||||
        for (const d of Array.from(Array(7).keys())) {
 | 
			
		||||
          const dateOfWeek = new Date(mondayDate);
 | 
			
		||||
          dateOfWeek.setDate(mondayDate.getDate() + d);
 | 
			
		||||
          const dateStr = dateToISO(dateOfWeek) as string;
 | 
			
		||||
          for (const range of state.ranges) {
 | 
			
		||||
            if (
 | 
			
		||||
              isEventInputCalendarRange(range) &&
 | 
			
		||||
              range.start.startsWith(dateStr)
 | 
			
		||||
            ) {
 | 
			
		||||
              founds.push(range);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                return founds;
 | 
			
		||||
            },
 | 
			
		||||
        return founds;
 | 
			
		||||
      },
 | 
			
		||||
  },
 | 
			
		||||
  mutations: {
 | 
			
		||||
    addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
 | 
			
		||||
      const toAdd = ranges
 | 
			
		||||
        .map((cr) => calendarRangeToFullCalendarEvent(cr))
 | 
			
		||||
        .map((cr) => ({
 | 
			
		||||
          ...cr,
 | 
			
		||||
          backgroundColor: "white",
 | 
			
		||||
          borderColor: "#3788d8",
 | 
			
		||||
          textColor: "black",
 | 
			
		||||
        }))
 | 
			
		||||
        .filter((r) => !state.rangesIndex.has(r.id));
 | 
			
		||||
 | 
			
		||||
      toAdd.forEach((r) => {
 | 
			
		||||
        state.rangesIndex.add(r.id);
 | 
			
		||||
        state.ranges.push(r);
 | 
			
		||||
      });
 | 
			
		||||
      state.key = state.key + toAdd.length;
 | 
			
		||||
    },
 | 
			
		||||
    mutations: {
 | 
			
		||||
        addRanges(state: CalendarRangesState, ranges: CalendarRange[]) {
 | 
			
		||||
            const toAdd = ranges
 | 
			
		||||
                .map((cr) => calendarRangeToFullCalendarEvent(cr))
 | 
			
		||||
                .map((cr) => ({
 | 
			
		||||
                    ...cr,
 | 
			
		||||
                    backgroundColor: "white",
 | 
			
		||||
                    borderColor: "#3788d8",
 | 
			
		||||
                    textColor: "black",
 | 
			
		||||
                }))
 | 
			
		||||
                .filter((r) => !state.rangesIndex.has(r.id));
 | 
			
		||||
    addExternals(
 | 
			
		||||
      state: CalendarRangesState,
 | 
			
		||||
      externalEvents: (EventInput & { id: string })[],
 | 
			
		||||
    ) {
 | 
			
		||||
      const toAdd = externalEvents.filter((r) => !state.rangesIndex.has(r.id));
 | 
			
		||||
 | 
			
		||||
            toAdd.forEach((r) => {
 | 
			
		||||
                state.rangesIndex.add(r.id);
 | 
			
		||||
                state.ranges.push(r);
 | 
			
		||||
            });
 | 
			
		||||
            state.key = state.key + toAdd.length;
 | 
			
		||||
        },
 | 
			
		||||
        addExternals(
 | 
			
		||||
            state: CalendarRangesState,
 | 
			
		||||
            externalEvents: (EventInput & { id: string })[],
 | 
			
		||||
        ) {
 | 
			
		||||
            const toAdd = externalEvents.filter(
 | 
			
		||||
                (r) => !state.rangesIndex.has(r.id),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            toAdd.forEach((r) => {
 | 
			
		||||
                state.rangesIndex.add(r.id);
 | 
			
		||||
                state.ranges.push(r);
 | 
			
		||||
            });
 | 
			
		||||
            state.key = state.key + toAdd.length;
 | 
			
		||||
        },
 | 
			
		||||
        addLoaded(
 | 
			
		||||
            state: CalendarRangesState,
 | 
			
		||||
            payload: { start: Date; end: Date },
 | 
			
		||||
        ) {
 | 
			
		||||
            state.rangesLoaded.push({
 | 
			
		||||
                start: payload.start.getTime(),
 | 
			
		||||
                end: payload.end.getTime(),
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        addRange(state: CalendarRangesState, payload: CalendarRange) {
 | 
			
		||||
            const asEvent = calendarRangeToFullCalendarEvent(payload);
 | 
			
		||||
            state.ranges.push({
 | 
			
		||||
                ...asEvent,
 | 
			
		||||
                backgroundColor: "white",
 | 
			
		||||
                borderColor: "#3788d8",
 | 
			
		||||
                textColor: "black",
 | 
			
		||||
            });
 | 
			
		||||
            state.rangesIndex.add(asEvent.id);
 | 
			
		||||
            state.key = state.key + 1;
 | 
			
		||||
        },
 | 
			
		||||
        removeRange(state: CalendarRangesState, calendarRangeId: number) {
 | 
			
		||||
            const found = state.ranges.find(
 | 
			
		||||
                (r) =>
 | 
			
		||||
                    r.calendarRangeId === calendarRangeId && r.is === "range",
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            if (found !== undefined) {
 | 
			
		||||
                state.ranges = state.ranges.filter(
 | 
			
		||||
                    (r) =>
 | 
			
		||||
                        !(
 | 
			
		||||
                            r.calendarRangeId === calendarRangeId &&
 | 
			
		||||
                            r.is === "range"
 | 
			
		||||
                        ),
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                if (typeof found.id === "string") {
 | 
			
		||||
                    // should always be true
 | 
			
		||||
                    state.rangesIndex.delete(found.id);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                state.key = state.key + 1;
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        updateRange(state: CalendarRangesState, range: CalendarRange) {
 | 
			
		||||
            const found = state.ranges.find(
 | 
			
		||||
                (r) => r.calendarRangeId === range.id && r.is === "range",
 | 
			
		||||
            );
 | 
			
		||||
            const newEvent = calendarRangeToFullCalendarEvent(range);
 | 
			
		||||
 | 
			
		||||
            if (found !== undefined) {
 | 
			
		||||
                found.start = newEvent.start;
 | 
			
		||||
                found.end = newEvent.end;
 | 
			
		||||
                found.locationId = range.location.id;
 | 
			
		||||
                found.locationName = range.location.name;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            state.key = state.key + 1;
 | 
			
		||||
        },
 | 
			
		||||
      toAdd.forEach((r) => {
 | 
			
		||||
        state.rangesIndex.add(r.id);
 | 
			
		||||
        state.ranges.push(r);
 | 
			
		||||
      });
 | 
			
		||||
      state.key = state.key + toAdd.length;
 | 
			
		||||
    },
 | 
			
		||||
    actions: {
 | 
			
		||||
        fetchRanges(
 | 
			
		||||
            ctx: Context,
 | 
			
		||||
            payload: { start: Date; end: Date },
 | 
			
		||||
        ): Promise<null> {
 | 
			
		||||
            const start = payload.start;
 | 
			
		||||
            const end = payload.end;
 | 
			
		||||
 | 
			
		||||
            if (ctx.rootGetters["me/getMe"] === null) {
 | 
			
		||||
                return Promise.resolve(ctx.getters.getRangeSource);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (ctx.getters.isRangeLoaded({ start, end })) {
 | 
			
		||||
                return Promise.resolve(ctx.getters.getRangeSource);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ctx.commit("addLoaded", {
 | 
			
		||||
                start: start,
 | 
			
		||||
                end: end,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return fetchCalendarRangeForUser(
 | 
			
		||||
                ctx.rootGetters["me/getMe"],
 | 
			
		||||
                start,
 | 
			
		||||
                end,
 | 
			
		||||
            ).then((ranges: CalendarRange[]) => {
 | 
			
		||||
                ctx.commit("addRanges", ranges);
 | 
			
		||||
                return Promise.resolve(null);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        createRange(
 | 
			
		||||
            ctx: Context,
 | 
			
		||||
            {
 | 
			
		||||
                start,
 | 
			
		||||
                end,
 | 
			
		||||
                location,
 | 
			
		||||
            }: { start: Date; end: Date; location: Location },
 | 
			
		||||
        ): Promise<null> {
 | 
			
		||||
            const url = `/api/1.0/calendar/calendar-range.json?`;
 | 
			
		||||
 | 
			
		||||
            if (ctx.rootState.me.me === null) {
 | 
			
		||||
                throw new Error("user is currently null");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const body = {
 | 
			
		||||
                user: {
 | 
			
		||||
                    id: ctx.rootState.me.me.id,
 | 
			
		||||
                    type: "user",
 | 
			
		||||
                },
 | 
			
		||||
                startDate: {
 | 
			
		||||
                    datetime: datetimeToISO(start),
 | 
			
		||||
                },
 | 
			
		||||
                endDate: {
 | 
			
		||||
                    datetime: datetimeToISO(end),
 | 
			
		||||
                },
 | 
			
		||||
                location: {
 | 
			
		||||
                    id: location.id,
 | 
			
		||||
                    type: "location",
 | 
			
		||||
                },
 | 
			
		||||
            } as CalendarRangeCreate;
 | 
			
		||||
 | 
			
		||||
            return makeFetch<CalendarRangeCreate, CalendarRange>(
 | 
			
		||||
                "POST",
 | 
			
		||||
                url,
 | 
			
		||||
                body,
 | 
			
		||||
            )
 | 
			
		||||
                .then((newRange) => {
 | 
			
		||||
                    ctx.commit("addRange", newRange);
 | 
			
		||||
 | 
			
		||||
                    return Promise.resolve(null);
 | 
			
		||||
                })
 | 
			
		||||
                .catch((error) => {
 | 
			
		||||
                    console.error(error);
 | 
			
		||||
 | 
			
		||||
                    throw error;
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
        deleteRange(ctx: Context, calendarRangeId: number) {
 | 
			
		||||
            const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
 | 
			
		||||
 | 
			
		||||
            makeFetch<undefined, never>("DELETE", url).then(() => {
 | 
			
		||||
                ctx.commit("removeRange", calendarRangeId);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        patchRangeTime(
 | 
			
		||||
            ctx,
 | 
			
		||||
            {
 | 
			
		||||
                calendarRangeId,
 | 
			
		||||
                start,
 | 
			
		||||
                end,
 | 
			
		||||
            }: { calendarRangeId: number; start: Date; end: Date },
 | 
			
		||||
        ): Promise<null> {
 | 
			
		||||
            const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
 | 
			
		||||
            const body = {
 | 
			
		||||
                startDate: {
 | 
			
		||||
                    datetime: datetimeToISO(start),
 | 
			
		||||
                },
 | 
			
		||||
                endDate: {
 | 
			
		||||
                    datetime: datetimeToISO(end),
 | 
			
		||||
                },
 | 
			
		||||
            } as CalendarRangeEdit;
 | 
			
		||||
 | 
			
		||||
            return makeFetch<CalendarRangeEdit, CalendarRange>(
 | 
			
		||||
                "PATCH",
 | 
			
		||||
                url,
 | 
			
		||||
                body,
 | 
			
		||||
            )
 | 
			
		||||
                .then((range) => {
 | 
			
		||||
                    ctx.commit("updateRange", range);
 | 
			
		||||
                    return Promise.resolve(null);
 | 
			
		||||
                })
 | 
			
		||||
                .catch((error) => {
 | 
			
		||||
                    console.error(error);
 | 
			
		||||
                    return Promise.resolve(null);
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
        patchRangeLocation(
 | 
			
		||||
            ctx,
 | 
			
		||||
            {
 | 
			
		||||
                location,
 | 
			
		||||
                calendarRangeId,
 | 
			
		||||
            }: { location: Location; calendarRangeId: number },
 | 
			
		||||
        ): Promise<null> {
 | 
			
		||||
            const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
 | 
			
		||||
            const body = {
 | 
			
		||||
                location: {
 | 
			
		||||
                    id: location.id,
 | 
			
		||||
                    type: "location",
 | 
			
		||||
                },
 | 
			
		||||
            } as CalendarRangeEdit;
 | 
			
		||||
 | 
			
		||||
            return makeFetch<CalendarRangeEdit, CalendarRange>(
 | 
			
		||||
                "PATCH",
 | 
			
		||||
                url,
 | 
			
		||||
                body,
 | 
			
		||||
            )
 | 
			
		||||
                .then((range) => {
 | 
			
		||||
                    ctx.commit("updateRange", range);
 | 
			
		||||
                    return Promise.resolve(null);
 | 
			
		||||
                })
 | 
			
		||||
                .catch((error) => {
 | 
			
		||||
                    console.error(error);
 | 
			
		||||
                    return Promise.resolve(null);
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
        copyFromDayToAnotherDay(
 | 
			
		||||
            ctx,
 | 
			
		||||
            { from, to }: { from: Date; to: Date },
 | 
			
		||||
        ): Promise<null> {
 | 
			
		||||
            const rangesToCopy: EventInputCalendarRange[] =
 | 
			
		||||
                ctx.getters["getRangesOnDate"](from);
 | 
			
		||||
            const promises = [];
 | 
			
		||||
 | 
			
		||||
            for (const r of rangesToCopy) {
 | 
			
		||||
                const start = new Date(ISOToDatetime(r.start) as Date);
 | 
			
		||||
                start.setFullYear(
 | 
			
		||||
                    to.getFullYear(),
 | 
			
		||||
                    to.getMonth(),
 | 
			
		||||
                    to.getDate(),
 | 
			
		||||
                );
 | 
			
		||||
                const end = new Date(ISOToDatetime(r.end) as Date);
 | 
			
		||||
                end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
 | 
			
		||||
                const location = ctx.rootGetters["locations/getLocationById"](
 | 
			
		||||
                    r.locationId,
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                promises.push(
 | 
			
		||||
                    ctx.dispatch("createRange", { start, end, location }),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Promise.all(promises).then(() => Promise.resolve(null));
 | 
			
		||||
        },
 | 
			
		||||
        copyFromWeekToAnotherWeek(
 | 
			
		||||
            ctx: Context,
 | 
			
		||||
            { fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
 | 
			
		||||
        ): Promise<null> {
 | 
			
		||||
            const rangesToCopy: EventInputCalendarRange[] =
 | 
			
		||||
                ctx.getters["getRangesOnWeek"](fromMonday);
 | 
			
		||||
            const promises = [];
 | 
			
		||||
            const diffTime = toMonday.getTime() - fromMonday.getTime();
 | 
			
		||||
            for (const r of rangesToCopy) {
 | 
			
		||||
                const start = new Date(ISOToDatetime(r.start) as Date);
 | 
			
		||||
                const end = new Date(ISOToDatetime(r.end) as Date);
 | 
			
		||||
                start.setTime(start.getTime() + diffTime);
 | 
			
		||||
                end.setTime(end.getTime() + diffTime);
 | 
			
		||||
                const location = ctx.rootGetters["locations/getLocationById"](
 | 
			
		||||
                    r.locationId,
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                promises.push(
 | 
			
		||||
                    ctx.dispatch("createRange", { start, end, location }),
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Promise.all(promises).then(() => Promise.resolve(null));
 | 
			
		||||
        },
 | 
			
		||||
    addLoaded(state: CalendarRangesState, payload: { start: Date; end: Date }) {
 | 
			
		||||
      state.rangesLoaded.push({
 | 
			
		||||
        start: payload.start.getTime(),
 | 
			
		||||
        end: payload.end.getTime(),
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    addRange(state: CalendarRangesState, payload: CalendarRange) {
 | 
			
		||||
      const asEvent = calendarRangeToFullCalendarEvent(payload);
 | 
			
		||||
      state.ranges.push({
 | 
			
		||||
        ...asEvent,
 | 
			
		||||
        backgroundColor: "white",
 | 
			
		||||
        borderColor: "#3788d8",
 | 
			
		||||
        textColor: "black",
 | 
			
		||||
      });
 | 
			
		||||
      state.rangesIndex.add(asEvent.id);
 | 
			
		||||
      state.key = state.key + 1;
 | 
			
		||||
    },
 | 
			
		||||
    removeRange(state: CalendarRangesState, calendarRangeId: number) {
 | 
			
		||||
      const found = state.ranges.find(
 | 
			
		||||
        (r) => r.calendarRangeId === calendarRangeId && r.is === "range",
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (found !== undefined) {
 | 
			
		||||
        state.ranges = state.ranges.filter(
 | 
			
		||||
          (r) => !(r.calendarRangeId === calendarRangeId && r.is === "range"),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if (typeof found.id === "string") {
 | 
			
		||||
          // should always be true
 | 
			
		||||
          state.rangesIndex.delete(found.id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        state.key = state.key + 1;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    updateRange(state: CalendarRangesState, range: CalendarRange) {
 | 
			
		||||
      const found = state.ranges.find(
 | 
			
		||||
        (r) => r.calendarRangeId === range.id && r.is === "range",
 | 
			
		||||
      );
 | 
			
		||||
      const newEvent = calendarRangeToFullCalendarEvent(range);
 | 
			
		||||
 | 
			
		||||
      if (found !== undefined) {
 | 
			
		||||
        found.start = newEvent.start;
 | 
			
		||||
        found.end = newEvent.end;
 | 
			
		||||
        found.locationId = range.location.id;
 | 
			
		||||
        found.locationName = range.location.name;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      state.key = state.key + 1;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    fetchRanges(
 | 
			
		||||
      ctx: Context,
 | 
			
		||||
      payload: { start: Date; end: Date },
 | 
			
		||||
    ): Promise<null> {
 | 
			
		||||
      const start = payload.start;
 | 
			
		||||
      const end = payload.end;
 | 
			
		||||
 | 
			
		||||
      if (ctx.rootGetters["me/getMe"] === null) {
 | 
			
		||||
        return Promise.resolve(ctx.getters.getRangeSource);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (ctx.getters.isRangeLoaded({ start, end })) {
 | 
			
		||||
        return Promise.resolve(ctx.getters.getRangeSource);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      ctx.commit("addLoaded", {
 | 
			
		||||
        start: start,
 | 
			
		||||
        end: end,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return fetchCalendarRangeForUser(
 | 
			
		||||
        ctx.rootGetters["me/getMe"],
 | 
			
		||||
        start,
 | 
			
		||||
        end,
 | 
			
		||||
      ).then((ranges: CalendarRange[]) => {
 | 
			
		||||
        ctx.commit("addRanges", ranges);
 | 
			
		||||
        return Promise.resolve(null);
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    createRange(
 | 
			
		||||
      ctx: Context,
 | 
			
		||||
      { start, end, location }: { start: Date; end: Date; location: Location },
 | 
			
		||||
    ): Promise<null> {
 | 
			
		||||
      const url = `/api/1.0/calendar/calendar-range.json?`;
 | 
			
		||||
 | 
			
		||||
      if (ctx.rootState.me.me === null) {
 | 
			
		||||
        throw new Error("user is currently null");
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const body = {
 | 
			
		||||
        user: {
 | 
			
		||||
          id: ctx.rootState.me.me.id,
 | 
			
		||||
          type: "user",
 | 
			
		||||
        },
 | 
			
		||||
        startDate: {
 | 
			
		||||
          datetime: datetimeToISO(start),
 | 
			
		||||
        },
 | 
			
		||||
        endDate: {
 | 
			
		||||
          datetime: datetimeToISO(end),
 | 
			
		||||
        },
 | 
			
		||||
        location: {
 | 
			
		||||
          id: location.id,
 | 
			
		||||
          type: "location",
 | 
			
		||||
        },
 | 
			
		||||
      } as CalendarRangeCreate;
 | 
			
		||||
 | 
			
		||||
      return makeFetch<CalendarRangeCreate, CalendarRange>("POST", url, body)
 | 
			
		||||
        .then((newRange) => {
 | 
			
		||||
          ctx.commit("addRange", newRange);
 | 
			
		||||
 | 
			
		||||
          return Promise.resolve(null);
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error(error);
 | 
			
		||||
 | 
			
		||||
          throw error;
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    deleteRange(ctx: Context, calendarRangeId: number) {
 | 
			
		||||
      const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
 | 
			
		||||
 | 
			
		||||
      makeFetch<undefined, never>("DELETE", url).then(() => {
 | 
			
		||||
        ctx.commit("removeRange", calendarRangeId);
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    patchRangeTime(
 | 
			
		||||
      ctx,
 | 
			
		||||
      {
 | 
			
		||||
        calendarRangeId,
 | 
			
		||||
        start,
 | 
			
		||||
        end,
 | 
			
		||||
      }: { calendarRangeId: number; start: Date; end: Date },
 | 
			
		||||
    ): Promise<null> {
 | 
			
		||||
      const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
 | 
			
		||||
      const body = {
 | 
			
		||||
        startDate: {
 | 
			
		||||
          datetime: datetimeToISO(start),
 | 
			
		||||
        },
 | 
			
		||||
        endDate: {
 | 
			
		||||
          datetime: datetimeToISO(end),
 | 
			
		||||
        },
 | 
			
		||||
      } as CalendarRangeEdit;
 | 
			
		||||
 | 
			
		||||
      return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
 | 
			
		||||
        .then((range) => {
 | 
			
		||||
          ctx.commit("updateRange", range);
 | 
			
		||||
          return Promise.resolve(null);
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error(error);
 | 
			
		||||
          return Promise.resolve(null);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    patchRangeLocation(
 | 
			
		||||
      ctx,
 | 
			
		||||
      {
 | 
			
		||||
        location,
 | 
			
		||||
        calendarRangeId,
 | 
			
		||||
      }: { location: Location; calendarRangeId: number },
 | 
			
		||||
    ): Promise<null> {
 | 
			
		||||
      const url = `/api/1.0/calendar/calendar-range/${calendarRangeId}.json`;
 | 
			
		||||
      const body = {
 | 
			
		||||
        location: {
 | 
			
		||||
          id: location.id,
 | 
			
		||||
          type: "location",
 | 
			
		||||
        },
 | 
			
		||||
      } as CalendarRangeEdit;
 | 
			
		||||
 | 
			
		||||
      return makeFetch<CalendarRangeEdit, CalendarRange>("PATCH", url, body)
 | 
			
		||||
        .then((range) => {
 | 
			
		||||
          ctx.commit("updateRange", range);
 | 
			
		||||
          return Promise.resolve(null);
 | 
			
		||||
        })
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          console.error(error);
 | 
			
		||||
          return Promise.resolve(null);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    copyFromDayToAnotherDay(
 | 
			
		||||
      ctx,
 | 
			
		||||
      { from, to }: { from: Date; to: Date },
 | 
			
		||||
    ): Promise<null> {
 | 
			
		||||
      const rangesToCopy: EventInputCalendarRange[] =
 | 
			
		||||
        ctx.getters["getRangesOnDate"](from);
 | 
			
		||||
      const promises = [];
 | 
			
		||||
 | 
			
		||||
      for (const r of rangesToCopy) {
 | 
			
		||||
        const start = new Date(ISOToDatetime(r.start) as Date);
 | 
			
		||||
        start.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
 | 
			
		||||
        const end = new Date(ISOToDatetime(r.end) as Date);
 | 
			
		||||
        end.setFullYear(to.getFullYear(), to.getMonth(), to.getDate());
 | 
			
		||||
        const location = ctx.rootGetters["locations/getLocationById"](
 | 
			
		||||
          r.locationId,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        promises.push(ctx.dispatch("createRange", { start, end, location }));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return Promise.all(promises).then(() => Promise.resolve(null));
 | 
			
		||||
    },
 | 
			
		||||
    copyFromWeekToAnotherWeek(
 | 
			
		||||
      ctx: Context,
 | 
			
		||||
      { fromMonday, toMonday }: { fromMonday: Date; toMonday: Date },
 | 
			
		||||
    ): Promise<null> {
 | 
			
		||||
      const rangesToCopy: EventInputCalendarRange[] =
 | 
			
		||||
        ctx.getters["getRangesOnWeek"](fromMonday);
 | 
			
		||||
      const promises = [];
 | 
			
		||||
      const diffTime = toMonday.getTime() - fromMonday.getTime();
 | 
			
		||||
      for (const r of rangesToCopy) {
 | 
			
		||||
        const start = new Date(ISOToDatetime(r.start) as Date);
 | 
			
		||||
        const end = new Date(ISOToDatetime(r.end) as Date);
 | 
			
		||||
        start.setTime(start.getTime() + diffTime);
 | 
			
		||||
        end.setTime(end.getTime() + diffTime);
 | 
			
		||||
        const location = ctx.rootGetters["locations/getLocationById"](
 | 
			
		||||
          r.locationId,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        promises.push(ctx.dispatch("createRange", { start, end, location }));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return Promise.all(promises).then(() => Promise.resolve(null));
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
} as Module<CalendarRangesState, State>;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,109 +8,102 @@ import { TransportExceptionInterface } from "../../../../../../../ChillMainBundl
 | 
			
		||||
import { COLORS } from "../../../Calendar/const";
 | 
			
		||||
 | 
			
		||||
export interface CalendarRemotesState {
 | 
			
		||||
    remotes: EventInput[];
 | 
			
		||||
    remotesLoaded: { start: number; end: number }[];
 | 
			
		||||
    remotesIndex: Set<string>;
 | 
			
		||||
    key: number;
 | 
			
		||||
  remotes: EventInput[];
 | 
			
		||||
  remotesLoaded: { start: number; end: number }[];
 | 
			
		||||
  remotesIndex: Set<string>;
 | 
			
		||||
  key: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Context = ActionContext<CalendarRemotesState, State>;
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    namespaced: true,
 | 
			
		||||
    state: (): CalendarRemotesState => ({
 | 
			
		||||
        remotes: [],
 | 
			
		||||
        remotesLoaded: [],
 | 
			
		||||
        remotesIndex: new Set<string>(),
 | 
			
		||||
        key: 0,
 | 
			
		||||
    }),
 | 
			
		||||
    getters: {
 | 
			
		||||
        isRemotesLoaded:
 | 
			
		||||
            (state: CalendarRemotesState) =>
 | 
			
		||||
            ({ start, end }: { start: Date; end: Date }): boolean => {
 | 
			
		||||
                for (const range of state.remotesLoaded) {
 | 
			
		||||
                    if (
 | 
			
		||||
                        start.getTime() === range.start &&
 | 
			
		||||
                        end.getTime() === range.end
 | 
			
		||||
                    ) {
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
  namespaced: true,
 | 
			
		||||
  state: (): CalendarRemotesState => ({
 | 
			
		||||
    remotes: [],
 | 
			
		||||
    remotesLoaded: [],
 | 
			
		||||
    remotesIndex: new Set<string>(),
 | 
			
		||||
    key: 0,
 | 
			
		||||
  }),
 | 
			
		||||
  getters: {
 | 
			
		||||
    isRemotesLoaded:
 | 
			
		||||
      (state: CalendarRemotesState) =>
 | 
			
		||||
      ({ start, end }: { start: Date; end: Date }): boolean => {
 | 
			
		||||
        for (const range of state.remotesLoaded) {
 | 
			
		||||
          if (start.getTime() === range.start && end.getTime() === range.end) {
 | 
			
		||||
            return true;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                return false;
 | 
			
		||||
            },
 | 
			
		||||
        return false;
 | 
			
		||||
      },
 | 
			
		||||
  },
 | 
			
		||||
  mutations: {
 | 
			
		||||
    addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
 | 
			
		||||
      console.log("addRemotes", ranges);
 | 
			
		||||
 | 
			
		||||
      const toAdd = ranges
 | 
			
		||||
        .map((cr) => remoteToFullCalendarEvent(cr))
 | 
			
		||||
        .filter((r) => !state.remotesIndex.has(r.id));
 | 
			
		||||
 | 
			
		||||
      toAdd.forEach((r) => {
 | 
			
		||||
        state.remotesIndex.add(r.id);
 | 
			
		||||
        state.remotes.push(r);
 | 
			
		||||
      });
 | 
			
		||||
      state.key = state.key + toAdd.length;
 | 
			
		||||
    },
 | 
			
		||||
    mutations: {
 | 
			
		||||
        addRemotes(state: CalendarRemotesState, ranges: CalendarRemote[]) {
 | 
			
		||||
            console.log("addRemotes", ranges);
 | 
			
		||||
 | 
			
		||||
            const toAdd = ranges
 | 
			
		||||
                .map((cr) => remoteToFullCalendarEvent(cr))
 | 
			
		||||
                .filter((r) => !state.remotesIndex.has(r.id));
 | 
			
		||||
 | 
			
		||||
            toAdd.forEach((r) => {
 | 
			
		||||
                state.remotesIndex.add(r.id);
 | 
			
		||||
                state.remotes.push(r);
 | 
			
		||||
            });
 | 
			
		||||
            state.key = state.key + toAdd.length;
 | 
			
		||||
        },
 | 
			
		||||
        addLoaded(
 | 
			
		||||
            state: CalendarRemotesState,
 | 
			
		||||
            payload: { start: Date; end: Date },
 | 
			
		||||
        ) {
 | 
			
		||||
            state.remotesLoaded.push({
 | 
			
		||||
                start: payload.start.getTime(),
 | 
			
		||||
                end: payload.end.getTime(),
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
    addLoaded(
 | 
			
		||||
      state: CalendarRemotesState,
 | 
			
		||||
      payload: { start: Date; end: Date },
 | 
			
		||||
    ) {
 | 
			
		||||
      state.remotesLoaded.push({
 | 
			
		||||
        start: payload.start.getTime(),
 | 
			
		||||
        end: payload.end.getTime(),
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    actions: {
 | 
			
		||||
        fetchRemotes(
 | 
			
		||||
            ctx: Context,
 | 
			
		||||
            payload: { start: Date; end: Date },
 | 
			
		||||
        ): Promise<null> {
 | 
			
		||||
            const start = payload.start;
 | 
			
		||||
            const end = payload.end;
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    fetchRemotes(
 | 
			
		||||
      ctx: Context,
 | 
			
		||||
      payload: { start: Date; end: Date },
 | 
			
		||||
    ): Promise<null> {
 | 
			
		||||
      const start = payload.start;
 | 
			
		||||
      const end = payload.end;
 | 
			
		||||
 | 
			
		||||
            if (ctx.rootGetters["me/getMe"] === null) {
 | 
			
		||||
                return Promise.resolve(null);
 | 
			
		||||
            }
 | 
			
		||||
      if (ctx.rootGetters["me/getMe"] === null) {
 | 
			
		||||
        return Promise.resolve(null);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
            if (ctx.getters.isRemotesLoaded({ start, end })) {
 | 
			
		||||
                return Promise.resolve(ctx.getters.getRangeSource);
 | 
			
		||||
            }
 | 
			
		||||
      if (ctx.getters.isRemotesLoaded({ start, end })) {
 | 
			
		||||
        return Promise.resolve(ctx.getters.getRangeSource);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
            ctx.commit("addLoaded", {
 | 
			
		||||
                start: start,
 | 
			
		||||
                end: end,
 | 
			
		||||
            });
 | 
			
		||||
      ctx.commit("addLoaded", {
 | 
			
		||||
        start: start,
 | 
			
		||||
        end: end,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
            return fetchCalendarRemoteForUser(
 | 
			
		||||
                ctx.rootGetters["me/getMe"],
 | 
			
		||||
                start,
 | 
			
		||||
                end,
 | 
			
		||||
            )
 | 
			
		||||
                .then((remotes: CalendarRemote[]) => {
 | 
			
		||||
                    // to be add when reactivity problem will be solve ?
 | 
			
		||||
                    //ctx.commit('addRemotes', remotes);
 | 
			
		||||
                    const inputs = remotes
 | 
			
		||||
                        .map((cr) => remoteToFullCalendarEvent(cr))
 | 
			
		||||
                        .map((cr) => ({
 | 
			
		||||
                            ...cr,
 | 
			
		||||
                            backgroundColor: COLORS[0],
 | 
			
		||||
                            textColor: "black",
 | 
			
		||||
                            editable: false,
 | 
			
		||||
                        }));
 | 
			
		||||
                    ctx.commit("calendarRanges/addExternals", inputs, {
 | 
			
		||||
                        root: true,
 | 
			
		||||
                    });
 | 
			
		||||
                    return Promise.resolve(null);
 | 
			
		||||
                })
 | 
			
		||||
                .catch((e: TransportExceptionInterface) => {
 | 
			
		||||
                    console.error(e);
 | 
			
		||||
      return fetchCalendarRemoteForUser(ctx.rootGetters["me/getMe"], start, end)
 | 
			
		||||
        .then((remotes: CalendarRemote[]) => {
 | 
			
		||||
          // to be add when reactivity problem will be solve ?
 | 
			
		||||
          //ctx.commit('addRemotes', remotes);
 | 
			
		||||
          const inputs = remotes
 | 
			
		||||
            .map((cr) => remoteToFullCalendarEvent(cr))
 | 
			
		||||
            .map((cr) => ({
 | 
			
		||||
              ...cr,
 | 
			
		||||
              backgroundColor: COLORS[0],
 | 
			
		||||
              textColor: "black",
 | 
			
		||||
              editable: false,
 | 
			
		||||
            }));
 | 
			
		||||
          ctx.commit("calendarRanges/addExternals", inputs, {
 | 
			
		||||
            root: true,
 | 
			
		||||
          });
 | 
			
		||||
          return Promise.resolve(null);
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e: TransportExceptionInterface) => {
 | 
			
		||||
          console.error(e);
 | 
			
		||||
 | 
			
		||||
                    return Promise.resolve(null);
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
          return Promise.resolve(null);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
} as Module<CalendarRemotesState, State>;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,77 +2,77 @@ import { State } from "./../index";
 | 
			
		||||
import { ActionContext } from "vuex";
 | 
			
		||||
 | 
			
		||||
export interface FullCalendarState {
 | 
			
		||||
    currentView: {
 | 
			
		||||
        start: Date | null;
 | 
			
		||||
        end: Date | null;
 | 
			
		||||
    };
 | 
			
		||||
    key: number;
 | 
			
		||||
  currentView: {
 | 
			
		||||
    start: Date | null;
 | 
			
		||||
    end: Date | null;
 | 
			
		||||
  };
 | 
			
		||||
  key: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Context = ActionContext<FullCalendarState, State>;
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    namespaced: true,
 | 
			
		||||
    state: (): FullCalendarState => ({
 | 
			
		||||
        currentView: {
 | 
			
		||||
            start: null,
 | 
			
		||||
            end: null,
 | 
			
		||||
        },
 | 
			
		||||
        key: 0,
 | 
			
		||||
    }),
 | 
			
		||||
    mutations: {
 | 
			
		||||
        setCurrentDatesView: function (
 | 
			
		||||
            state: FullCalendarState,
 | 
			
		||||
            payload: { start: Date; end: Date },
 | 
			
		||||
        ): void {
 | 
			
		||||
            state.currentView.start = payload.start;
 | 
			
		||||
            state.currentView.end = payload.end;
 | 
			
		||||
        },
 | 
			
		||||
        increaseKey: function (state: FullCalendarState): void {
 | 
			
		||||
            state.key = state.key + 1;
 | 
			
		||||
        },
 | 
			
		||||
  namespaced: true,
 | 
			
		||||
  state: (): FullCalendarState => ({
 | 
			
		||||
    currentView: {
 | 
			
		||||
      start: null,
 | 
			
		||||
      end: null,
 | 
			
		||||
    },
 | 
			
		||||
    actions: {
 | 
			
		||||
        setCurrentDatesView(
 | 
			
		||||
            ctx: Context,
 | 
			
		||||
            { start, end }: { start: Date | null; end: Date | null },
 | 
			
		||||
        ): Promise<null> {
 | 
			
		||||
            console.log("dispatch setCurrentDatesView", { start, end });
 | 
			
		||||
 | 
			
		||||
            if (
 | 
			
		||||
                ctx.state.currentView.start !== start ||
 | 
			
		||||
                ctx.state.currentView.end !== end
 | 
			
		||||
            ) {
 | 
			
		||||
                ctx.commit("setCurrentDatesView", { start, end });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (start !== null && end !== null) {
 | 
			
		||||
                return Promise.all([
 | 
			
		||||
                    ctx
 | 
			
		||||
                        .dispatch(
 | 
			
		||||
                            "calendarRanges/fetchRanges",
 | 
			
		||||
                            { start, end },
 | 
			
		||||
                            { root: true },
 | 
			
		||||
                        )
 | 
			
		||||
                        .then((_) => Promise.resolve(null)),
 | 
			
		||||
                    ctx
 | 
			
		||||
                        .dispatch(
 | 
			
		||||
                            "calendarRemotes/fetchRemotes",
 | 
			
		||||
                            { start, end },
 | 
			
		||||
                            { root: true },
 | 
			
		||||
                        )
 | 
			
		||||
                        .then((_) => Promise.resolve(null)),
 | 
			
		||||
                    ctx
 | 
			
		||||
                        .dispatch(
 | 
			
		||||
                            "calendarLocals/fetchLocals",
 | 
			
		||||
                            { start, end },
 | 
			
		||||
                            { root: true },
 | 
			
		||||
                        )
 | 
			
		||||
                        .then((_) => Promise.resolve(null)),
 | 
			
		||||
                ]).then((_) => Promise.resolve(null));
 | 
			
		||||
            } else {
 | 
			
		||||
                return Promise.resolve(null);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    key: 0,
 | 
			
		||||
  }),
 | 
			
		||||
  mutations: {
 | 
			
		||||
    setCurrentDatesView: function (
 | 
			
		||||
      state: FullCalendarState,
 | 
			
		||||
      payload: { start: Date; end: Date },
 | 
			
		||||
    ): void {
 | 
			
		||||
      state.currentView.start = payload.start;
 | 
			
		||||
      state.currentView.end = payload.end;
 | 
			
		||||
    },
 | 
			
		||||
    increaseKey: function (state: FullCalendarState): void {
 | 
			
		||||
      state.key = state.key + 1;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    setCurrentDatesView(
 | 
			
		||||
      ctx: Context,
 | 
			
		||||
      { start, end }: { start: Date | null; end: Date | null },
 | 
			
		||||
    ): Promise<null> {
 | 
			
		||||
      console.log("dispatch setCurrentDatesView", { start, end });
 | 
			
		||||
 | 
			
		||||
      if (
 | 
			
		||||
        ctx.state.currentView.start !== start ||
 | 
			
		||||
        ctx.state.currentView.end !== end
 | 
			
		||||
      ) {
 | 
			
		||||
        ctx.commit("setCurrentDatesView", { start, end });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (start !== null && end !== null) {
 | 
			
		||||
        return Promise.all([
 | 
			
		||||
          ctx
 | 
			
		||||
            .dispatch(
 | 
			
		||||
              "calendarRanges/fetchRanges",
 | 
			
		||||
              { start, end },
 | 
			
		||||
              { root: true },
 | 
			
		||||
            )
 | 
			
		||||
            .then((_) => Promise.resolve(null)),
 | 
			
		||||
          ctx
 | 
			
		||||
            .dispatch(
 | 
			
		||||
              "calendarRemotes/fetchRemotes",
 | 
			
		||||
              { start, end },
 | 
			
		||||
              { root: true },
 | 
			
		||||
            )
 | 
			
		||||
            .then((_) => Promise.resolve(null)),
 | 
			
		||||
          ctx
 | 
			
		||||
            .dispatch(
 | 
			
		||||
              "calendarLocals/fetchLocals",
 | 
			
		||||
              { start, end },
 | 
			
		||||
              { root: true },
 | 
			
		||||
            )
 | 
			
		||||
            .then((_) => Promise.resolve(null)),
 | 
			
		||||
        ]).then((_) => Promise.resolve(null));
 | 
			
		||||
      } else {
 | 
			
		||||
        return Promise.resolve(null);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -5,61 +5,61 @@ import { getLocations } from "../../../../../../../ChillMainBundle/Resources/pub
 | 
			
		||||
import { whereami } from "../../../../../../../ChillMainBundle/Resources/public/lib/api/user";
 | 
			
		||||
 | 
			
		||||
export interface LocationState {
 | 
			
		||||
    locations: Location[];
 | 
			
		||||
    locationPicked: Location | null;
 | 
			
		||||
    currentLocation: Location | null;
 | 
			
		||||
  locations: Location[];
 | 
			
		||||
  locationPicked: Location | null;
 | 
			
		||||
  currentLocation: Location | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    namespaced: true,
 | 
			
		||||
    state: (): LocationState => {
 | 
			
		||||
        return {
 | 
			
		||||
            locations: [],
 | 
			
		||||
            locationPicked: null,
 | 
			
		||||
            currentLocation: null,
 | 
			
		||||
        };
 | 
			
		||||
  namespaced: true,
 | 
			
		||||
  state: (): LocationState => {
 | 
			
		||||
    return {
 | 
			
		||||
      locations: [],
 | 
			
		||||
      locationPicked: null,
 | 
			
		||||
      currentLocation: null,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  getters: {
 | 
			
		||||
    getLocationById:
 | 
			
		||||
      (state) =>
 | 
			
		||||
      (id: number): Location | undefined => {
 | 
			
		||||
        return state.locations.find((l) => l.id === id);
 | 
			
		||||
      },
 | 
			
		||||
  },
 | 
			
		||||
  mutations: {
 | 
			
		||||
    setLocations(state, locations): void {
 | 
			
		||||
      state.locations = locations;
 | 
			
		||||
    },
 | 
			
		||||
    getters: {
 | 
			
		||||
        getLocationById:
 | 
			
		||||
            (state) =>
 | 
			
		||||
            (id: number): Location | undefined => {
 | 
			
		||||
                return state.locations.find((l) => l.id === id);
 | 
			
		||||
            },
 | 
			
		||||
    },
 | 
			
		||||
    mutations: {
 | 
			
		||||
        setLocations(state, locations): void {
 | 
			
		||||
            state.locations = locations;
 | 
			
		||||
        },
 | 
			
		||||
        setLocationPicked(state, location: Location | null): void {
 | 
			
		||||
            if (null === location) {
 | 
			
		||||
                state.locationPicked = null;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
    setLocationPicked(state, location: Location | null): void {
 | 
			
		||||
      if (null === location) {
 | 
			
		||||
        state.locationPicked = null;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
            state.locationPicked =
 | 
			
		||||
                state.locations.find((l) => l.id === location.id) || null;
 | 
			
		||||
        },
 | 
			
		||||
        setCurrentLocation(state, location: Location | null): void {
 | 
			
		||||
            if (null === location) {
 | 
			
		||||
                state.currentLocation = null;
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
      state.locationPicked =
 | 
			
		||||
        state.locations.find((l) => l.id === location.id) || null;
 | 
			
		||||
    },
 | 
			
		||||
    setCurrentLocation(state, location: Location | null): void {
 | 
			
		||||
      if (null === location) {
 | 
			
		||||
        state.currentLocation = null;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
            state.currentLocation =
 | 
			
		||||
                state.locations.find((l) => l.id === location.id) || null;
 | 
			
		||||
        },
 | 
			
		||||
      state.currentLocation =
 | 
			
		||||
        state.locations.find((l) => l.id === location.id) || null;
 | 
			
		||||
    },
 | 
			
		||||
    actions: {
 | 
			
		||||
        getLocations(ctx): Promise<void> {
 | 
			
		||||
            return getLocations().then((locations) => {
 | 
			
		||||
                ctx.commit("setLocations", locations);
 | 
			
		||||
                return Promise.resolve();
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
        getCurrentLocation(ctx): Promise<void> {
 | 
			
		||||
            return whereami().then((location) => {
 | 
			
		||||
                ctx.commit("setCurrentLocation", location);
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    getLocations(ctx): Promise<void> {
 | 
			
		||||
      return getLocations().then((locations) => {
 | 
			
		||||
        ctx.commit("setLocations", locations);
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    getCurrentLocation(ctx): Promise<void> {
 | 
			
		||||
      return whereami().then((location) => {
 | 
			
		||||
        ctx.commit("setCurrentLocation", location);
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
} as Module<LocationState, State>;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,24 +3,24 @@ import { User } from "../../../../../../../ChillMainBundle/Resources/public/type
 | 
			
		||||
import { ActionContext } from "vuex";
 | 
			
		||||
 | 
			
		||||
export interface MeState {
 | 
			
		||||
    me: User | null;
 | 
			
		||||
  me: User | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Context = ActionContext<MeState, State>;
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    namespaced: true,
 | 
			
		||||
    state: (): MeState => ({
 | 
			
		||||
        me: null,
 | 
			
		||||
    }),
 | 
			
		||||
    getters: {
 | 
			
		||||
        getMe: function (state: MeState): User | null {
 | 
			
		||||
            return state.me;
 | 
			
		||||
        },
 | 
			
		||||
  namespaced: true,
 | 
			
		||||
  state: (): MeState => ({
 | 
			
		||||
    me: null,
 | 
			
		||||
  }),
 | 
			
		||||
  getters: {
 | 
			
		||||
    getMe: function (state: MeState): User | null {
 | 
			
		||||
      return state.me;
 | 
			
		||||
    },
 | 
			
		||||
    mutations: {
 | 
			
		||||
        setWhoAmi(state: MeState, me: User) {
 | 
			
		||||
            state.me = me;
 | 
			
		||||
        },
 | 
			
		||||
  },
 | 
			
		||||
  mutations: {
 | 
			
		||||
    setWhoAmi(state: MeState, me: User) {
 | 
			
		||||
      state.me = me;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,51 +1,51 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <h2 class="chill-red">
 | 
			
		||||
            {{ $t("choose_your_calendar_user") }}
 | 
			
		||||
        </h2>
 | 
			
		||||
        <VueMultiselect
 | 
			
		||||
            name="field"
 | 
			
		||||
            id="calendarUserSelector"
 | 
			
		||||
            v-model="value"
 | 
			
		||||
            track-by="id"
 | 
			
		||||
            label="value"
 | 
			
		||||
            :custom-label="transName"
 | 
			
		||||
            :placeholder="$t('select_user')"
 | 
			
		||||
            :multiple="true"
 | 
			
		||||
            :close-on-select="false"
 | 
			
		||||
            :allow-empty="true"
 | 
			
		||||
            :model-value="value"
 | 
			
		||||
            :select-label="$t('multiselect.select_label')"
 | 
			
		||||
            :deselect-label="$t('multiselect.deselect_label')"
 | 
			
		||||
            :selected-label="$t('multiselect.selected_label')"
 | 
			
		||||
            @select="selectUsers"
 | 
			
		||||
            @remove="unSelectUsers"
 | 
			
		||||
            @close="coloriseSelectedValues"
 | 
			
		||||
            :options="options"
 | 
			
		||||
        />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-check">
 | 
			
		||||
        <input
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            id="myCalendar"
 | 
			
		||||
            class="form-check-input"
 | 
			
		||||
            v-model="showMyCalendarWidget"
 | 
			
		||||
        />
 | 
			
		||||
        <label class="form-check-label" for="myCalendar">{{
 | 
			
		||||
            $t("show_my_calendar")
 | 
			
		||||
        }}</label>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="form-check">
 | 
			
		||||
        <input
 | 
			
		||||
            type="checkbox"
 | 
			
		||||
            id="weekends"
 | 
			
		||||
            class="form-check-input"
 | 
			
		||||
            @click="toggleWeekends"
 | 
			
		||||
        />
 | 
			
		||||
        <label class="form-check-label" for="weekends">{{
 | 
			
		||||
            $t("show_weekends")
 | 
			
		||||
        }}</label>
 | 
			
		||||
    </div>
 | 
			
		||||
  <div>
 | 
			
		||||
    <h2 class="chill-red">
 | 
			
		||||
      {{ $t("choose_your_calendar_user") }}
 | 
			
		||||
    </h2>
 | 
			
		||||
    <VueMultiselect
 | 
			
		||||
      name="field"
 | 
			
		||||
      id="calendarUserSelector"
 | 
			
		||||
      v-model="value"
 | 
			
		||||
      track-by="id"
 | 
			
		||||
      label="value"
 | 
			
		||||
      :custom-label="transName"
 | 
			
		||||
      :placeholder="$t('select_user')"
 | 
			
		||||
      :multiple="true"
 | 
			
		||||
      :close-on-select="false"
 | 
			
		||||
      :allow-empty="true"
 | 
			
		||||
      :model-value="value"
 | 
			
		||||
      :select-label="$t('multiselect.select_label')"
 | 
			
		||||
      :deselect-label="$t('multiselect.deselect_label')"
 | 
			
		||||
      :selected-label="$t('multiselect.selected_label')"
 | 
			
		||||
      @select="selectUsers"
 | 
			
		||||
      @remove="unSelectUsers"
 | 
			
		||||
      @close="coloriseSelectedValues"
 | 
			
		||||
      :options="options"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="form-check">
 | 
			
		||||
    <input
 | 
			
		||||
      type="checkbox"
 | 
			
		||||
      id="myCalendar"
 | 
			
		||||
      class="form-check-input"
 | 
			
		||||
      v-model="showMyCalendarWidget"
 | 
			
		||||
    />
 | 
			
		||||
    <label class="form-check-label" for="myCalendar">{{
 | 
			
		||||
      $t("show_my_calendar")
 | 
			
		||||
    }}</label>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="form-check">
 | 
			
		||||
    <input
 | 
			
		||||
      type="checkbox"
 | 
			
		||||
      id="weekends"
 | 
			
		||||
      class="form-check-input"
 | 
			
		||||
      @click="toggleWeekends"
 | 
			
		||||
    />
 | 
			
		||||
    <label class="form-check-label" for="weekends">{{
 | 
			
		||||
      $t("show_weekends")
 | 
			
		||||
    }}</label>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script>
 | 
			
		||||
import { fetchCalendarRanges, fetchCalendar } from "../../_api/api";
 | 
			
		||||
@@ -53,206 +53,183 @@ import VueMultiselect from "vue-multiselect";
 | 
			
		||||
import { whoami } from "ChillPersonAssets/vuejs/AccompanyingCourse/api";
 | 
			
		||||
 | 
			
		||||
const COLORS = [
 | 
			
		||||
    /* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
 | 
			
		||||
    "#8dd3c7",
 | 
			
		||||
    "#ffffb3",
 | 
			
		||||
    "#bebada",
 | 
			
		||||
    "#fb8072",
 | 
			
		||||
    "#80b1d3",
 | 
			
		||||
    "#fdb462",
 | 
			
		||||
    "#b3de69",
 | 
			
		||||
    "#fccde5",
 | 
			
		||||
    "#d9d9d9",
 | 
			
		||||
    "#bc80bd",
 | 
			
		||||
    "#ccebc5",
 | 
			
		||||
    "#ffed6f",
 | 
			
		||||
  /* from https://colorbrewer2.org/#type=qualitative&scheme=Set3&n=12 */
 | 
			
		||||
  "#8dd3c7",
 | 
			
		||||
  "#ffffb3",
 | 
			
		||||
  "#bebada",
 | 
			
		||||
  "#fb8072",
 | 
			
		||||
  "#80b1d3",
 | 
			
		||||
  "#fdb462",
 | 
			
		||||
  "#b3de69",
 | 
			
		||||
  "#fccde5",
 | 
			
		||||
  "#d9d9d9",
 | 
			
		||||
  "#bc80bd",
 | 
			
		||||
  "#ccebc5",
 | 
			
		||||
  "#ffed6f",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "CalendarUserSelector",
 | 
			
		||||
    components: { VueMultiselect },
 | 
			
		||||
    props: [
 | 
			
		||||
        "users",
 | 
			
		||||
        "updateEventsSource",
 | 
			
		||||
        "calendarEvents",
 | 
			
		||||
        "showMyCalendar",
 | 
			
		||||
        "toggleMyCalendar",
 | 
			
		||||
        "toggleWeekends",
 | 
			
		||||
    ],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            errorMsg: [],
 | 
			
		||||
            value: [],
 | 
			
		||||
            options: [],
 | 
			
		||||
        };
 | 
			
		||||
  name: "CalendarUserSelector",
 | 
			
		||||
  components: { VueMultiselect },
 | 
			
		||||
  props: [
 | 
			
		||||
    "users",
 | 
			
		||||
    "updateEventsSource",
 | 
			
		||||
    "calendarEvents",
 | 
			
		||||
    "showMyCalendar",
 | 
			
		||||
    "toggleMyCalendar",
 | 
			
		||||
    "toggleWeekends",
 | 
			
		||||
  ],
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      errorMsg: [],
 | 
			
		||||
      value: [],
 | 
			
		||||
      options: [],
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    showMyCalendarWidget: {
 | 
			
		||||
      set(value) {
 | 
			
		||||
        this.toggleMyCalendar(value);
 | 
			
		||||
        this.updateEventsSource();
 | 
			
		||||
      },
 | 
			
		||||
      get() {
 | 
			
		||||
        return this.showMyCalendar;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        showMyCalendarWidget: {
 | 
			
		||||
            set(value) {
 | 
			
		||||
                this.toggleMyCalendar(value);
 | 
			
		||||
                this.updateEventsSource();
 | 
			
		||||
            },
 | 
			
		||||
            get() {
 | 
			
		||||
                return this.showMyCalendar;
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    init() {
 | 
			
		||||
      this.fetchData();
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        init() {
 | 
			
		||||
            this.fetchData();
 | 
			
		||||
        },
 | 
			
		||||
        fetchData() {
 | 
			
		||||
            fetchCalendarRanges()
 | 
			
		||||
                .then(
 | 
			
		||||
                    (calendarRanges) =>
 | 
			
		||||
                        new Promise((resolve, reject) => {
 | 
			
		||||
                            let results = calendarRanges.results;
 | 
			
		||||
    fetchData() {
 | 
			
		||||
      fetchCalendarRanges()
 | 
			
		||||
        .then(
 | 
			
		||||
          (calendarRanges) =>
 | 
			
		||||
            new Promise((resolve, reject) => {
 | 
			
		||||
              let results = calendarRanges.results;
 | 
			
		||||
 | 
			
		||||
                            let users = [];
 | 
			
		||||
              let users = [];
 | 
			
		||||
 | 
			
		||||
                            results.forEach((i) => {
 | 
			
		||||
                                if (!users.some((j) => i.user.id === j.id)) {
 | 
			
		||||
                                    let ratio = Math.floor(
 | 
			
		||||
                                        users.length / COLORS.length,
 | 
			
		||||
                                    );
 | 
			
		||||
                                    let colorIndex =
 | 
			
		||||
                                        users.length - ratio * COLORS.length;
 | 
			
		||||
                                    users.push({
 | 
			
		||||
                                        id: i.user.id,
 | 
			
		||||
                                        username: i.user.username,
 | 
			
		||||
                                        color: COLORS[colorIndex],
 | 
			
		||||
                                    });
 | 
			
		||||
                                }
 | 
			
		||||
                            });
 | 
			
		||||
 | 
			
		||||
                            let calendarEvents = [];
 | 
			
		||||
                            users.forEach((u) => {
 | 
			
		||||
                                let arr = results
 | 
			
		||||
                                    .filter((i) => i.user.id === u.id)
 | 
			
		||||
                                    .map((i) => ({
 | 
			
		||||
                                        start: i.startDate.datetime,
 | 
			
		||||
                                        end: i.endDate.datetime,
 | 
			
		||||
                                        calendarRangeId: i.id,
 | 
			
		||||
                                        sourceColor: u.color,
 | 
			
		||||
                                        //display: 'background' // can be an option for the disponibility
 | 
			
		||||
                                    }));
 | 
			
		||||
                                calendarEvents.push({
 | 
			
		||||
                                    events: arr,
 | 
			
		||||
                                    color: u.color,
 | 
			
		||||
                                    textColor: "#444444",
 | 
			
		||||
                                    editable: false,
 | 
			
		||||
                                    id: u.id,
 | 
			
		||||
                                });
 | 
			
		||||
                            });
 | 
			
		||||
 | 
			
		||||
                            this.users.loaded = users;
 | 
			
		||||
                            this.options = users;
 | 
			
		||||
 | 
			
		||||
                            this.calendarEvents.loaded = calendarEvents;
 | 
			
		||||
                            whoami().then(
 | 
			
		||||
                                (me) =>
 | 
			
		||||
                                    new Promise((resolve, reject) => {
 | 
			
		||||
                                        this.users.logged = me;
 | 
			
		||||
                                        let currentUser = users.find(
 | 
			
		||||
                                            (u) => u.id === me.id,
 | 
			
		||||
                                        );
 | 
			
		||||
                                        this.value = currentUser;
 | 
			
		||||
 | 
			
		||||
                                        fetchCalendar(currentUser.id).then(
 | 
			
		||||
                                            (calendar) =>
 | 
			
		||||
                                                new Promise(
 | 
			
		||||
                                                    (resolve, reject) => {
 | 
			
		||||
                                                        let results =
 | 
			
		||||
                                                            calendar.results;
 | 
			
		||||
                                                        let events =
 | 
			
		||||
                                                            results.map(
 | 
			
		||||
                                                                (i) => ({
 | 
			
		||||
                                                                    start: i
 | 
			
		||||
                                                                        .startDate
 | 
			
		||||
                                                                        .datetime,
 | 
			
		||||
                                                                    end: i
 | 
			
		||||
                                                                        .endDate
 | 
			
		||||
                                                                        .datetime,
 | 
			
		||||
                                                                }),
 | 
			
		||||
                                                            );
 | 
			
		||||
                                                        let calendarEventsCurrentUser =
 | 
			
		||||
                                                            {
 | 
			
		||||
                                                                events: events,
 | 
			
		||||
                                                                color: "darkblue",
 | 
			
		||||
                                                                id: 1000,
 | 
			
		||||
                                                                editable: false,
 | 
			
		||||
                                                            };
 | 
			
		||||
                                                        this.calendarEvents.user =
 | 
			
		||||
                                                            calendarEventsCurrentUser;
 | 
			
		||||
 | 
			
		||||
                                                        this.selectUsers(
 | 
			
		||||
                                                            currentUser,
 | 
			
		||||
                                                        );
 | 
			
		||||
 | 
			
		||||
                                                        resolve();
 | 
			
		||||
                                                    },
 | 
			
		||||
                                                ),
 | 
			
		||||
                                        );
 | 
			
		||||
 | 
			
		||||
                                        resolve();
 | 
			
		||||
                                    }),
 | 
			
		||||
                            );
 | 
			
		||||
 | 
			
		||||
                            resolve();
 | 
			
		||||
                        }),
 | 
			
		||||
                )
 | 
			
		||||
                .catch((error) => {
 | 
			
		||||
                    this.errorMsg.push(error.message);
 | 
			
		||||
                });
 | 
			
		||||
        },
 | 
			
		||||
        transName(value) {
 | 
			
		||||
            return `${value.username}`;
 | 
			
		||||
        },
 | 
			
		||||
        coloriseSelectedValues() {
 | 
			
		||||
            let tags = document.querySelectorAll(
 | 
			
		||||
                "div.multiselect__tags-wrap",
 | 
			
		||||
            )[0];
 | 
			
		||||
 | 
			
		||||
            if (tags.hasChildNodes()) {
 | 
			
		||||
                let children = tags.childNodes;
 | 
			
		||||
                for (let i = 0; i < children.length; i++) {
 | 
			
		||||
                    let child = children[i];
 | 
			
		||||
                    if (child.nodeType === Node.ELEMENT_NODE) {
 | 
			
		||||
                        this.users.selected.forEach((u) => {
 | 
			
		||||
                            if (child.hasChildNodes()) {
 | 
			
		||||
                                if (child.firstChild.innerText == u.username) {
 | 
			
		||||
                                    child.style.background = u.color;
 | 
			
		||||
                                    child.firstChild.style.color = "#444444";
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
              results.forEach((i) => {
 | 
			
		||||
                if (!users.some((j) => i.user.id === j.id)) {
 | 
			
		||||
                  let ratio = Math.floor(users.length / COLORS.length);
 | 
			
		||||
                  let colorIndex = users.length - ratio * COLORS.length;
 | 
			
		||||
                  users.push({
 | 
			
		||||
                    id: i.user.id,
 | 
			
		||||
                    username: i.user.username,
 | 
			
		||||
                    color: COLORS[colorIndex],
 | 
			
		||||
                  });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        selectEvents() {
 | 
			
		||||
            let selectedUsersId = this.users.selected.map((a) => a.id);
 | 
			
		||||
            this.calendarEvents.selected = this.calendarEvents.loaded.filter(
 | 
			
		||||
                (a) => selectedUsersId.includes(a.id),
 | 
			
		||||
            );
 | 
			
		||||
        },
 | 
			
		||||
        selectUsers(value) {
 | 
			
		||||
            this.users.selected.push(value);
 | 
			
		||||
            this.coloriseSelectedValues();
 | 
			
		||||
            this.selectEvents();
 | 
			
		||||
            this.updateEventsSource();
 | 
			
		||||
        },
 | 
			
		||||
        unSelectUsers(value) {
 | 
			
		||||
            this.users.selected = this.users.selected.filter(
 | 
			
		||||
                (a) => a.id != value.id,
 | 
			
		||||
            );
 | 
			
		||||
            this.selectEvents();
 | 
			
		||||
            this.updateEventsSource();
 | 
			
		||||
        },
 | 
			
		||||
              });
 | 
			
		||||
 | 
			
		||||
              let calendarEvents = [];
 | 
			
		||||
              users.forEach((u) => {
 | 
			
		||||
                let arr = results
 | 
			
		||||
                  .filter((i) => i.user.id === u.id)
 | 
			
		||||
                  .map((i) => ({
 | 
			
		||||
                    start: i.startDate.datetime,
 | 
			
		||||
                    end: i.endDate.datetime,
 | 
			
		||||
                    calendarRangeId: i.id,
 | 
			
		||||
                    sourceColor: u.color,
 | 
			
		||||
                    //display: 'background' // can be an option for the disponibility
 | 
			
		||||
                  }));
 | 
			
		||||
                calendarEvents.push({
 | 
			
		||||
                  events: arr,
 | 
			
		||||
                  color: u.color,
 | 
			
		||||
                  textColor: "#444444",
 | 
			
		||||
                  editable: false,
 | 
			
		||||
                  id: u.id,
 | 
			
		||||
                });
 | 
			
		||||
              });
 | 
			
		||||
 | 
			
		||||
              this.users.loaded = users;
 | 
			
		||||
              this.options = users;
 | 
			
		||||
 | 
			
		||||
              this.calendarEvents.loaded = calendarEvents;
 | 
			
		||||
              whoami().then(
 | 
			
		||||
                (me) =>
 | 
			
		||||
                  new Promise((resolve, reject) => {
 | 
			
		||||
                    this.users.logged = me;
 | 
			
		||||
                    let currentUser = users.find((u) => u.id === me.id);
 | 
			
		||||
                    this.value = currentUser;
 | 
			
		||||
 | 
			
		||||
                    fetchCalendar(currentUser.id).then(
 | 
			
		||||
                      (calendar) =>
 | 
			
		||||
                        new Promise((resolve, reject) => {
 | 
			
		||||
                          let results = calendar.results;
 | 
			
		||||
                          let events = results.map((i) => ({
 | 
			
		||||
                            start: i.startDate.datetime,
 | 
			
		||||
                            end: i.endDate.datetime,
 | 
			
		||||
                          }));
 | 
			
		||||
                          let calendarEventsCurrentUser = {
 | 
			
		||||
                            events: events,
 | 
			
		||||
                            color: "darkblue",
 | 
			
		||||
                            id: 1000,
 | 
			
		||||
                            editable: false,
 | 
			
		||||
                          };
 | 
			
		||||
                          this.calendarEvents.user = calendarEventsCurrentUser;
 | 
			
		||||
 | 
			
		||||
                          this.selectUsers(currentUser);
 | 
			
		||||
 | 
			
		||||
                          resolve();
 | 
			
		||||
                        }),
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    resolve();
 | 
			
		||||
                  }),
 | 
			
		||||
              );
 | 
			
		||||
 | 
			
		||||
              resolve();
 | 
			
		||||
            }),
 | 
			
		||||
        )
 | 
			
		||||
        .catch((error) => {
 | 
			
		||||
          this.errorMsg.push(error.message);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.init();
 | 
			
		||||
    transName(value) {
 | 
			
		||||
      return `${value.username}`;
 | 
			
		||||
    },
 | 
			
		||||
    coloriseSelectedValues() {
 | 
			
		||||
      let tags = document.querySelectorAll("div.multiselect__tags-wrap")[0];
 | 
			
		||||
 | 
			
		||||
      if (tags.hasChildNodes()) {
 | 
			
		||||
        let children = tags.childNodes;
 | 
			
		||||
        for (let i = 0; i < children.length; i++) {
 | 
			
		||||
          let child = children[i];
 | 
			
		||||
          if (child.nodeType === Node.ELEMENT_NODE) {
 | 
			
		||||
            this.users.selected.forEach((u) => {
 | 
			
		||||
              if (child.hasChildNodes()) {
 | 
			
		||||
                if (child.firstChild.innerText == u.username) {
 | 
			
		||||
                  child.style.background = u.color;
 | 
			
		||||
                  child.firstChild.style.color = "#444444";
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    selectEvents() {
 | 
			
		||||
      let selectedUsersId = this.users.selected.map((a) => a.id);
 | 
			
		||||
      this.calendarEvents.selected = this.calendarEvents.loaded.filter((a) =>
 | 
			
		||||
        selectedUsersId.includes(a.id),
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
    selectUsers(value) {
 | 
			
		||||
      this.users.selected.push(value);
 | 
			
		||||
      this.coloriseSelectedValues();
 | 
			
		||||
      this.selectEvents();
 | 
			
		||||
      this.updateEventsSource();
 | 
			
		||||
    },
 | 
			
		||||
    unSelectUsers(value) {
 | 
			
		||||
      this.users.selected = this.users.selected.filter((a) => a.id != value.id);
 | 
			
		||||
      this.selectEvents();
 | 
			
		||||
      this.updateEventsSource();
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.init();
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,59 +1,54 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <template v-if="templates.length > 0">
 | 
			
		||||
            <slot name="title">
 | 
			
		||||
                <h2>{{ $t("generate_document") }}</h2>
 | 
			
		||||
            </slot>
 | 
			
		||||
  <div>
 | 
			
		||||
    <template v-if="templates.length > 0">
 | 
			
		||||
      <slot name="title">
 | 
			
		||||
        <h2>{{ $t("generate_document") }}</h2>
 | 
			
		||||
      </slot>
 | 
			
		||||
 | 
			
		||||
            <div class="container">
 | 
			
		||||
                <div class="row">
 | 
			
		||||
                    <div class="col-md-4">
 | 
			
		||||
                        <slot name="label">
 | 
			
		||||
                            <label>{{ $t("select_a_template") }}</label>
 | 
			
		||||
                        </slot>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="col-md-8">
 | 
			
		||||
                        <div class="input-group mb-3">
 | 
			
		||||
                            <select class="form-select" v-model="template">
 | 
			
		||||
                                <option disabled selected value="">
 | 
			
		||||
                                    {{ $t("choose_a_template") }}
 | 
			
		||||
                                </option>
 | 
			
		||||
                                <template v-for="t in templates" :key="t.id">
 | 
			
		||||
                                    <option :value="t.id">
 | 
			
		||||
                                        {{
 | 
			
		||||
                                            localizeString(t.name) ||
 | 
			
		||||
                                            "Aucun nom défini"
 | 
			
		||||
                                        }}
 | 
			
		||||
                                    </option>
 | 
			
		||||
                                </template>
 | 
			
		||||
                            </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"
 | 
			
		||||
                            /></a>
 | 
			
		||||
                            <a
 | 
			
		||||
                                v-else
 | 
			
		||||
                                class="btn btn-update btn-sm change-icon"
 | 
			
		||||
                                href="#"
 | 
			
		||||
                                disabled
 | 
			
		||||
                                ><i class="fa fa-fw fa-cog"
 | 
			
		||||
                            /></a>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="row" v-if="hasDescription">
 | 
			
		||||
                    <div class="col-md-8 align-self-end">
 | 
			
		||||
                        <p>{{ getDescription }}</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
      <div class="container">
 | 
			
		||||
        <div class="row">
 | 
			
		||||
          <div class="col-md-4">
 | 
			
		||||
            <slot name="label">
 | 
			
		||||
              <label>{{ $t("select_a_template") }}</label>
 | 
			
		||||
            </slot>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="col-md-8">
 | 
			
		||||
            <div class="input-group mb-3">
 | 
			
		||||
              <select class="form-select" v-model="template">
 | 
			
		||||
                <option disabled selected value="">
 | 
			
		||||
                  {{ $t("choose_a_template") }}
 | 
			
		||||
                </option>
 | 
			
		||||
                <template v-for="t in templates" :key="t.id">
 | 
			
		||||
                  <option :value="t.id">
 | 
			
		||||
                    {{ localizeString(t.name) || "Aucun nom défini" }}
 | 
			
		||||
                  </option>
 | 
			
		||||
                </template>
 | 
			
		||||
              </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"
 | 
			
		||||
              /></a>
 | 
			
		||||
              <a
 | 
			
		||||
                v-else
 | 
			
		||||
                class="btn btn-update btn-sm change-icon"
 | 
			
		||||
                href="#"
 | 
			
		||||
                disabled
 | 
			
		||||
                ><i class="fa fa-fw fa-cog"
 | 
			
		||||
              /></a>
 | 
			
		||||
            </div>
 | 
			
		||||
        </template>
 | 
			
		||||
    </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="row" v-if="hasDescription">
 | 
			
		||||
          <div class="col-md-8 align-self-end">
 | 
			
		||||
            <p>{{ getDescription }}</p>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -61,83 +56,83 @@ import { buildLink } from "ChillDocGeneratorAssets/lib/document-generator";
 | 
			
		||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "PickTemplate",
 | 
			
		||||
    props: {
 | 
			
		||||
        entityId: [String, Number],
 | 
			
		||||
        entityClass: {
 | 
			
		||||
            type: String,
 | 
			
		||||
            required: false,
 | 
			
		||||
        },
 | 
			
		||||
        templates: {
 | 
			
		||||
            type: Array,
 | 
			
		||||
            required: true,
 | 
			
		||||
        },
 | 
			
		||||
        preventDefaultMoveToGenerate: {
 | 
			
		||||
            type: Boolean,
 | 
			
		||||
            required: false,
 | 
			
		||||
            default: false,
 | 
			
		||||
        },
 | 
			
		||||
  name: "PickTemplate",
 | 
			
		||||
  props: {
 | 
			
		||||
    entityId: [String, Number],
 | 
			
		||||
    entityClass: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      required: false,
 | 
			
		||||
    },
 | 
			
		||||
    emits: ["goToGenerateDocument"],
 | 
			
		||||
    data() {
 | 
			
		||||
        return {
 | 
			
		||||
            template: null,
 | 
			
		||||
        };
 | 
			
		||||
    templates: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      required: true,
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
        canGenerate() {
 | 
			
		||||
            return this.template != null;
 | 
			
		||||
        },
 | 
			
		||||
        hasDescription() {
 | 
			
		||||
            if (this.template == null) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
    preventDefaultMoveToGenerate: {
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      required: false,
 | 
			
		||||
      default: false,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  emits: ["goToGenerateDocument"],
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      template: null,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    canGenerate() {
 | 
			
		||||
      return this.template != null;
 | 
			
		||||
    },
 | 
			
		||||
    hasDescription() {
 | 
			
		||||
      if (this.template == null) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        },
 | 
			
		||||
        getDescription() {
 | 
			
		||||
            if (null === this.template) {
 | 
			
		||||
                return "";
 | 
			
		||||
            }
 | 
			
		||||
            let desc = this.templates.find((t) => t.id === this.template);
 | 
			
		||||
            if (null === desc) {
 | 
			
		||||
                return "";
 | 
			
		||||
            }
 | 
			
		||||
            return desc.description || "";
 | 
			
		||||
        },
 | 
			
		||||
        buildUrlGenerate() {
 | 
			
		||||
            if (null === this.template) {
 | 
			
		||||
                return "#";
 | 
			
		||||
            }
 | 
			
		||||
      return true;
 | 
			
		||||
    },
 | 
			
		||||
    getDescription() {
 | 
			
		||||
      if (null === this.template) {
 | 
			
		||||
        return "";
 | 
			
		||||
      }
 | 
			
		||||
      let desc = this.templates.find((t) => t.id === this.template);
 | 
			
		||||
      if (null === desc) {
 | 
			
		||||
        return "";
 | 
			
		||||
      }
 | 
			
		||||
      return desc.description || "";
 | 
			
		||||
    },
 | 
			
		||||
    buildUrlGenerate() {
 | 
			
		||||
      if (null === this.template) {
 | 
			
		||||
        return "#";
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
            return buildLink(this.template, this.entityId, this.entityClass);
 | 
			
		||||
        },
 | 
			
		||||
      return buildLink(this.template, this.entityId, this.entityClass);
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        localizeString(str) {
 | 
			
		||||
            return localizeString(str);
 | 
			
		||||
        },
 | 
			
		||||
        clickGenerate(event, link) {
 | 
			
		||||
            if (!this.preventDefaultMoveToGenerate) {
 | 
			
		||||
                window.location.assign(link);
 | 
			
		||||
            }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    localizeString(str) {
 | 
			
		||||
      return localizeString(str);
 | 
			
		||||
    },
 | 
			
		||||
    clickGenerate(event, link) {
 | 
			
		||||
      if (!this.preventDefaultMoveToGenerate) {
 | 
			
		||||
        window.location.assign(link);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
            this.$emit("goToGenerateDocument", {
 | 
			
		||||
                event,
 | 
			
		||||
                link,
 | 
			
		||||
                template: this.template,
 | 
			
		||||
            });
 | 
			
		||||
        },
 | 
			
		||||
      this.$emit("goToGenerateDocument", {
 | 
			
		||||
        event,
 | 
			
		||||
        link,
 | 
			
		||||
        template: this.template,
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    i18n: {
 | 
			
		||||
        messages: {
 | 
			
		||||
            fr: {
 | 
			
		||||
                generate_document: "Générer un document",
 | 
			
		||||
                select_a_template: "Choisir un modèle",
 | 
			
		||||
                choose_a_template: "Choisir",
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
  },
 | 
			
		||||
  i18n: {
 | 
			
		||||
    messages: {
 | 
			
		||||
      fr: {
 | 
			
		||||
        generate_document: "Générer un document",
 | 
			
		||||
        select_a_template: "Choisir un modèle",
 | 
			
		||||
        choose_a_template: "Choisir",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ final readonly class StoredObjectVersionApiController
 | 
			
		||||
 | 
			
		||||
        return new JsonResponse(
 | 
			
		||||
            $this->serializer->serialize(
 | 
			
		||||
                new Collection(array_values($items->toArray()), $paginator),
 | 
			
		||||
                new Collection($items, $paginator),
 | 
			
		||||
                'json',
 | 
			
		||||
                [AbstractNormalizer::GROUPS => ['read', StoredObjectVersionNormalizer::WITH_POINT_IN_TIMES_CONTEXT, StoredObjectVersionNormalizer::WITH_RESTORED_CONTEXT]]
 | 
			
		||||
            ),
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ class StoredObject implements Document, TrackCreationInterface
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Collection<int, StoredObjectVersion>&Selectable<int, StoredObjectVersion>
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true)]
 | 
			
		||||
    #[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true, fetch: 'EAGER')]
 | 
			
		||||
    private Collection&Selectable $versions;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -23,14 +23,10 @@ use Random\RandomException;
 | 
			
		||||
 * Store each version of StoredObject's.
 | 
			
		||||
 *
 | 
			
		||||
 * A version should not be created manually: use the method @see{StoredObject::registerVersion} instead.
 | 
			
		||||
 *
 | 
			
		||||
 * Each filename must be unique within the same StoredObject. We add a condition on id to apply this condition only for
 | 
			
		||||
 * newly created versions when this new index is applied.
 | 
			
		||||
 */
 | 
			
		||||
#[ORM\Entity]
 | 
			
		||||
#[ORM\Table('chill_doc.stored_object_version')]
 | 
			
		||||
#[ORM\UniqueConstraint(name: 'chill_doc_stored_object_version_unique_by_object', columns: ['stored_object_id', 'version'])]
 | 
			
		||||
#[ORM\UniqueConstraint(name: 'chill_doc_stored_object_version_unique_by_filename', columns: ['filename'], options: ['where' => '(id > 0)'])]
 | 
			
		||||
class StoredObjectVersion implements TrackCreationInterface
 | 
			
		||||
{
 | 
			
		||||
    use TrackCreationTrait;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * 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.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\DocStoreBundle\Exception;
 | 
			
		||||
 | 
			
		||||
class ConversionWithSameMimeTypeException extends \RuntimeException
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(string $mimeType, ?\Throwable $previous = null)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct("Conversion to same MIME type '{$mimeType}' is not allowed: already at the same MIME type", 0, $previous);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,20 +6,20 @@ const algo = "AES-CBC";
 | 
			
		||||
const URL_POST = "/asyncupload/temp_url/generate/post";
 | 
			
		||||
 | 
			
		||||
const keyDefinition = {
 | 
			
		||||
    name: algo,
 | 
			
		||||
    length: 256,
 | 
			
		||||
  name: algo,
 | 
			
		||||
  length: 256,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const createFilename = (): string => {
 | 
			
		||||
    let text = "";
 | 
			
		||||
    const possible =
 | 
			
		||||
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
 | 
			
		||||
  let text = "";
 | 
			
		||||
  const possible =
 | 
			
		||||
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < 7; i++) {
 | 
			
		||||
        text += possible.charAt(Math.floor(Math.random() * possible.length));
 | 
			
		||||
    }
 | 
			
		||||
  for (let i = 0; i < 7; i++) {
 | 
			
		||||
    text += possible.charAt(Math.floor(Math.random() * possible.length));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return text;
 | 
			
		||||
  return text;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -30,59 +30,59 @@ const createFilename = (): string => {
 | 
			
		||||
 * @returns {Promise<StoredObject>} A Promise that resolves to the newly created StoredObject.
 | 
			
		||||
 */
 | 
			
		||||
export const fetchNewStoredObject = async (): Promise<StoredObject> => {
 | 
			
		||||
    return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null);
 | 
			
		||||
  return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const uploadVersion = async (
 | 
			
		||||
    uploadFile: ArrayBuffer,
 | 
			
		||||
    storedObject: StoredObject,
 | 
			
		||||
  uploadFile: ArrayBuffer,
 | 
			
		||||
  storedObject: StoredObject,
 | 
			
		||||
): Promise<string> => {
 | 
			
		||||
    const params = new URLSearchParams();
 | 
			
		||||
    params.append("expires_delay", "180");
 | 
			
		||||
    params.append("submit_delay", "180");
 | 
			
		||||
    const asyncData: PostStoreObjectSignature = await makeFetch(
 | 
			
		||||
        "GET",
 | 
			
		||||
        `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
 | 
			
		||||
            "?" +
 | 
			
		||||
            params.toString(),
 | 
			
		||||
    );
 | 
			
		||||
    const suffix = createFilename();
 | 
			
		||||
    const filename = asyncData.prefix + suffix;
 | 
			
		||||
    const formData = new FormData();
 | 
			
		||||
    formData.append("redirect", asyncData.redirect);
 | 
			
		||||
    formData.append("max_file_size", asyncData.max_file_size.toString());
 | 
			
		||||
    formData.append("max_file_count", asyncData.max_file_count.toString());
 | 
			
		||||
    formData.append("expires", asyncData.expires.toString());
 | 
			
		||||
    formData.append("signature", asyncData.signature);
 | 
			
		||||
    formData.append(filename, new Blob([uploadFile]), suffix);
 | 
			
		||||
  const params = new URLSearchParams();
 | 
			
		||||
  params.append("expires_delay", "180");
 | 
			
		||||
  params.append("submit_delay", "180");
 | 
			
		||||
  const asyncData: PostStoreObjectSignature = await makeFetch(
 | 
			
		||||
    "GET",
 | 
			
		||||
    `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` +
 | 
			
		||||
      "?" +
 | 
			
		||||
      params.toString(),
 | 
			
		||||
  );
 | 
			
		||||
  const suffix = createFilename();
 | 
			
		||||
  const filename = asyncData.prefix + suffix;
 | 
			
		||||
  const formData = new FormData();
 | 
			
		||||
  formData.append("redirect", asyncData.redirect);
 | 
			
		||||
  formData.append("max_file_size", asyncData.max_file_size.toString());
 | 
			
		||||
  formData.append("max_file_count", asyncData.max_file_count.toString());
 | 
			
		||||
  formData.append("expires", asyncData.expires.toString());
 | 
			
		||||
  formData.append("signature", asyncData.signature);
 | 
			
		||||
  formData.append(filename, new Blob([uploadFile]), suffix);
 | 
			
		||||
 | 
			
		||||
    const response = await window.fetch(asyncData.url, {
 | 
			
		||||
        method: "POST",
 | 
			
		||||
        body: formData,
 | 
			
		||||
    });
 | 
			
		||||
  const response = await window.fetch(asyncData.url, {
 | 
			
		||||
    method: "POST",
 | 
			
		||||
    body: formData,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
    if (!response.ok) {
 | 
			
		||||
        console.error("Error while sending file to store", response);
 | 
			
		||||
        throw new Error(response.statusText);
 | 
			
		||||
    }
 | 
			
		||||
  if (!response.ok) {
 | 
			
		||||
    console.error("Error while sending file to store", response);
 | 
			
		||||
    throw new Error(response.statusText);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return Promise.resolve(filename);
 | 
			
		||||
  return Promise.resolve(filename);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const encryptFile = async (
 | 
			
		||||
    originalFile: ArrayBuffer,
 | 
			
		||||
  originalFile: ArrayBuffer,
 | 
			
		||||
): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => {
 | 
			
		||||
    const iv = crypto.getRandomValues(new Uint8Array(16));
 | 
			
		||||
    const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
 | 
			
		||||
        "encrypt",
 | 
			
		||||
        "decrypt",
 | 
			
		||||
    ]);
 | 
			
		||||
    const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
 | 
			
		||||
    const encrypted = await window.crypto.subtle.encrypt(
 | 
			
		||||
        { name: algo, iv: iv },
 | 
			
		||||
        key,
 | 
			
		||||
        originalFile,
 | 
			
		||||
    );
 | 
			
		||||
  const iv = crypto.getRandomValues(new Uint8Array(16));
 | 
			
		||||
  const key = await window.crypto.subtle.generateKey(keyDefinition, true, [
 | 
			
		||||
    "encrypt",
 | 
			
		||||
    "decrypt",
 | 
			
		||||
  ]);
 | 
			
		||||
  const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
 | 
			
		||||
  const encrypted = await window.crypto.subtle.encrypt(
 | 
			
		||||
    { name: algo, iv: iv },
 | 
			
		||||
    key,
 | 
			
		||||
    originalFile,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    return Promise.resolve([encrypted, iv, exportedKey]);
 | 
			
		||||
  return Promise.resolve([encrypted, iv, exportedKey]);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,9 @@ import { fetchResults } from "ChillMainAssets/lib/api/apiMethods";
 | 
			
		||||
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
 | 
			
		||||
 | 
			
		||||
export function fetch_generic_docs_by_accompanying_period(
 | 
			
		||||
    periodId: number,
 | 
			
		||||
  periodId: number,
 | 
			
		||||
): Promise<GenericDocForAccompanyingPeriod[]> {
 | 
			
		||||
    return fetchResults(
 | 
			
		||||
        `/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
 | 
			
		||||
    );
 | 
			
		||||
  return fetchResults(
 | 
			
		||||
    `/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`,
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,117 +6,116 @@ import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vue
 | 
			
		||||
const i18n = _createI18n({});
 | 
			
		||||
 | 
			
		||||
const startApp = (
 | 
			
		||||
    divElement: HTMLDivElement,
 | 
			
		||||
    collectionEntry: null | HTMLLIElement,
 | 
			
		||||
  divElement: HTMLDivElement,
 | 
			
		||||
  collectionEntry: null | HTMLLIElement,
 | 
			
		||||
): void => {
 | 
			
		||||
    console.log("app started", divElement);
 | 
			
		||||
  console.log("app started", divElement);
 | 
			
		||||
 | 
			
		||||
    const inputTitle = collectionEntry?.querySelector("input[type='text']");
 | 
			
		||||
  const inputTitle = collectionEntry?.querySelector("input[type='text']");
 | 
			
		||||
 | 
			
		||||
    const input_stored_object: HTMLInputElement | null =
 | 
			
		||||
        divElement.querySelector("input[data-stored-object]");
 | 
			
		||||
    if (null === input_stored_object) {
 | 
			
		||||
        throw new Error("input to stored object not found");
 | 
			
		||||
    }
 | 
			
		||||
  const input_stored_object: HTMLInputElement | null = divElement.querySelector(
 | 
			
		||||
    "input[data-stored-object]",
 | 
			
		||||
  );
 | 
			
		||||
  if (null === input_stored_object) {
 | 
			
		||||
    throw new Error("input to stored object not found");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    let existingDoc: StoredObject | null = null;
 | 
			
		||||
    if (input_stored_object.value !== "") {
 | 
			
		||||
        existingDoc = JSON.parse(input_stored_object.value);
 | 
			
		||||
    }
 | 
			
		||||
    const app_container = document.createElement("div");
 | 
			
		||||
    divElement.appendChild(app_container);
 | 
			
		||||
  let existingDoc: StoredObject | null = null;
 | 
			
		||||
  if (input_stored_object.value !== "") {
 | 
			
		||||
    existingDoc = JSON.parse(input_stored_object.value);
 | 
			
		||||
  }
 | 
			
		||||
  const app_container = document.createElement("div");
 | 
			
		||||
  divElement.appendChild(app_container);
 | 
			
		||||
 | 
			
		||||
    const app = createApp({
 | 
			
		||||
        template:
 | 
			
		||||
            '<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
 | 
			
		||||
        data() {
 | 
			
		||||
            return {
 | 
			
		||||
                existingDoc: existingDoc,
 | 
			
		||||
                inputTitle: inputTitle,
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        components: {
 | 
			
		||||
            DropFileWidget,
 | 
			
		||||
        },
 | 
			
		||||
        methods: {
 | 
			
		||||
            addDocument: function ({
 | 
			
		||||
                stored_object,
 | 
			
		||||
                stored_object_version,
 | 
			
		||||
                file_name,
 | 
			
		||||
            }: {
 | 
			
		||||
                stored_object: StoredObject;
 | 
			
		||||
                stored_object_version: StoredObjectVersion;
 | 
			
		||||
                file_name: string;
 | 
			
		||||
            }): void {
 | 
			
		||||
                stored_object.title = file_name;
 | 
			
		||||
                console.log("object added", stored_object);
 | 
			
		||||
                console.log("version added", stored_object_version);
 | 
			
		||||
                this.$data.existingDoc = stored_object;
 | 
			
		||||
                this.$data.existingDoc.currentVersion = stored_object_version;
 | 
			
		||||
                input_stored_object.value = JSON.stringify(
 | 
			
		||||
                    this.$data.existingDoc,
 | 
			
		||||
                );
 | 
			
		||||
                if (this.$data.inputTitle) {
 | 
			
		||||
                    if (!this.$data.inputTitle?.value) {
 | 
			
		||||
                        this.$data.inputTitle.value = file_name;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            removeDocument: function (object: StoredObject): void {
 | 
			
		||||
                console.log("catch remove document", object);
 | 
			
		||||
                input_stored_object.value = "";
 | 
			
		||||
                this.$data.existingDoc = undefined;
 | 
			
		||||
                console.log("collectionEntry", collectionEntry);
 | 
			
		||||
  const app = createApp({
 | 
			
		||||
    template:
 | 
			
		||||
      '<drop-file-widget :existingDoc="this.$data.existingDoc" :allowRemove="true" @addDocument="this.addDocument" @removeDocument="removeDocument"></drop-file-widget>',
 | 
			
		||||
    data() {
 | 
			
		||||
      return {
 | 
			
		||||
        existingDoc: existingDoc,
 | 
			
		||||
        inputTitle: inputTitle,
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    components: {
 | 
			
		||||
      DropFileWidget,
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      addDocument: function ({
 | 
			
		||||
        stored_object,
 | 
			
		||||
        stored_object_version,
 | 
			
		||||
        file_name,
 | 
			
		||||
      }: {
 | 
			
		||||
        stored_object: StoredObject;
 | 
			
		||||
        stored_object_version: StoredObjectVersion;
 | 
			
		||||
        file_name: string;
 | 
			
		||||
      }): void {
 | 
			
		||||
        stored_object.title = file_name;
 | 
			
		||||
        console.log("object added", stored_object);
 | 
			
		||||
        console.log("version added", stored_object_version);
 | 
			
		||||
        this.$data.existingDoc = stored_object;
 | 
			
		||||
        this.$data.existingDoc.currentVersion = stored_object_version;
 | 
			
		||||
        input_stored_object.value = JSON.stringify(this.$data.existingDoc);
 | 
			
		||||
        if (this.$data.inputTitle) {
 | 
			
		||||
          if (!this.$data.inputTitle?.value) {
 | 
			
		||||
            this.$data.inputTitle.value = file_name;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      removeDocument: function (object: StoredObject): void {
 | 
			
		||||
        console.log("catch remove document", object);
 | 
			
		||||
        input_stored_object.value = "";
 | 
			
		||||
        this.$data.existingDoc = undefined;
 | 
			
		||||
        console.log("collectionEntry", collectionEntry);
 | 
			
		||||
 | 
			
		||||
                if (null !== collectionEntry) {
 | 
			
		||||
                    console.log("will remove collection");
 | 
			
		||||
                    collectionEntry.remove();
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
        if (null !== collectionEntry) {
 | 
			
		||||
          console.log("will remove collection");
 | 
			
		||||
          collectionEntry.remove();
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
    app.use(i18n).mount(app_container);
 | 
			
		||||
  app.use(i18n).mount(app_container);
 | 
			
		||||
};
 | 
			
		||||
window.addEventListener("collection-add-entry", ((
 | 
			
		||||
    e: CustomEvent<CollectionEventPayload>,
 | 
			
		||||
  e: CustomEvent<CollectionEventPayload>,
 | 
			
		||||
) => {
 | 
			
		||||
    const detail = e.detail;
 | 
			
		||||
    const divElement: null | HTMLDivElement = detail.entry.querySelector(
 | 
			
		||||
        "div[data-stored-object]",
 | 
			
		||||
    );
 | 
			
		||||
  const detail = e.detail;
 | 
			
		||||
  const divElement: null | HTMLDivElement = detail.entry.querySelector(
 | 
			
		||||
    "div[data-stored-object]",
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    if (null === divElement) {
 | 
			
		||||
        throw new Error("div[data-stored-object] not found");
 | 
			
		||||
    }
 | 
			
		||||
  if (null === divElement) {
 | 
			
		||||
    throw new Error("div[data-stored-object] not found");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    startApp(divElement, detail.entry);
 | 
			
		||||
  startApp(divElement, detail.entry);
 | 
			
		||||
}) as EventListener);
 | 
			
		||||
 | 
			
		||||
window.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
    const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
 | 
			
		||||
        "div[data-stored-object]",
 | 
			
		||||
    );
 | 
			
		||||
  const upload_inputs: NodeListOf<HTMLDivElement> = document.querySelectorAll(
 | 
			
		||||
    "div[data-stored-object]",
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    upload_inputs.forEach((input: HTMLDivElement): void => {
 | 
			
		||||
        // test for a parent to check if this is a collection entry
 | 
			
		||||
        let collectionEntry: null | HTMLLIElement = null;
 | 
			
		||||
        const parent = input.parentElement;
 | 
			
		||||
        console.log("parent", parent);
 | 
			
		||||
        if (null !== parent) {
 | 
			
		||||
            const grandParent = parent.parentElement;
 | 
			
		||||
            console.log("grandParent", grandParent);
 | 
			
		||||
            if (null !== grandParent) {
 | 
			
		||||
                if (
 | 
			
		||||
                    grandParent.tagName.toLowerCase() === "li" &&
 | 
			
		||||
                    grandParent.classList.contains("entry")
 | 
			
		||||
                ) {
 | 
			
		||||
                    collectionEntry = grandParent as HTMLLIElement;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
  upload_inputs.forEach((input: HTMLDivElement): void => {
 | 
			
		||||
    // test for a parent to check if this is a collection entry
 | 
			
		||||
    let collectionEntry: null | HTMLLIElement = null;
 | 
			
		||||
    const parent = input.parentElement;
 | 
			
		||||
    console.log("parent", parent);
 | 
			
		||||
    if (null !== parent) {
 | 
			
		||||
      const grandParent = parent.parentElement;
 | 
			
		||||
      console.log("grandParent", grandParent);
 | 
			
		||||
      if (null !== grandParent) {
 | 
			
		||||
        if (
 | 
			
		||||
          grandParent.tagName.toLowerCase() === "li" &&
 | 
			
		||||
          grandParent.classList.contains("entry")
 | 
			
		||||
        ) {
 | 
			
		||||
          collectionEntry = grandParent as HTMLLIElement;
 | 
			
		||||
        }
 | 
			
		||||
        startApp(input, collectionEntry);
 | 
			
		||||
    });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    startApp(input, collectionEntry);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export {};
 | 
			
		||||
 
 | 
			
		||||
@@ -9,26 +9,26 @@ import ToastPlugin from "vue-toast-notification";
 | 
			
		||||
const i18n = _createI18n({});
 | 
			
		||||
 | 
			
		||||
window.addEventListener("DOMContentLoaded", function (e) {
 | 
			
		||||
    document
 | 
			
		||||
        .querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
 | 
			
		||||
        .forEach((el) => {
 | 
			
		||||
            const storedObject = JSON.parse(
 | 
			
		||||
                el.dataset.storedObject as string,
 | 
			
		||||
            ) as StoredObject;
 | 
			
		||||
            const title = el.dataset.title as string;
 | 
			
		||||
            const app = createApp({
 | 
			
		||||
                components: { DownloadButton },
 | 
			
		||||
                data() {
 | 
			
		||||
                    return {
 | 
			
		||||
                        storedObject,
 | 
			
		||||
                        title,
 | 
			
		||||
                        classes: { btn: true, "btn-outline-primary": true },
 | 
			
		||||
                    };
 | 
			
		||||
                },
 | 
			
		||||
                template:
 | 
			
		||||
                    '<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
 | 
			
		||||
            });
 | 
			
		||||
  document
 | 
			
		||||
    .querySelectorAll<HTMLDivElement>("div[data-download-button-single]")
 | 
			
		||||
    .forEach((el) => {
 | 
			
		||||
      const storedObject = JSON.parse(
 | 
			
		||||
        el.dataset.storedObject as string,
 | 
			
		||||
      ) as StoredObject;
 | 
			
		||||
      const title = el.dataset.title as string;
 | 
			
		||||
      const app = createApp({
 | 
			
		||||
        components: { DownloadButton },
 | 
			
		||||
        data() {
 | 
			
		||||
          return {
 | 
			
		||||
            storedObject,
 | 
			
		||||
            title,
 | 
			
		||||
            classes: { btn: true, "btn-outline-primary": true },
 | 
			
		||||
          };
 | 
			
		||||
        },
 | 
			
		||||
        template:
 | 
			
		||||
          '<download-button :stored-object="storedObject" :at-version="storedObject.currentVersion" :classes="classes" :filename="title" :direct-download="true"></download-button>',
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
            app.use(i18n).use(ToastPlugin).mount(el);
 | 
			
		||||
        });
 | 
			
		||||
      app.use(i18n).use(ToastPlugin).mount(el);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -8,66 +8,66 @@ import ToastPlugin from "vue-toast-notification";
 | 
			
		||||
const i18n = _createI18n({});
 | 
			
		||||
 | 
			
		||||
window.addEventListener("DOMContentLoaded", function (e) {
 | 
			
		||||
    document
 | 
			
		||||
        .querySelectorAll<HTMLDivElement>("div[data-download-buttons]")
 | 
			
		||||
        .forEach((el) => {
 | 
			
		||||
            const app = createApp({
 | 
			
		||||
                components: { DocumentActionButtonsGroup },
 | 
			
		||||
                data() {
 | 
			
		||||
                    const datasets = el.dataset as {
 | 
			
		||||
                        filename: string;
 | 
			
		||||
                        canEdit: string;
 | 
			
		||||
                        storedObject: string;
 | 
			
		||||
                        buttonSmall: string;
 | 
			
		||||
                        davLink: string;
 | 
			
		||||
                        davLinkExpiration: string;
 | 
			
		||||
                    };
 | 
			
		||||
  document
 | 
			
		||||
    .querySelectorAll<HTMLDivElement>("div[data-download-buttons]")
 | 
			
		||||
    .forEach((el) => {
 | 
			
		||||
      const app = createApp({
 | 
			
		||||
        components: { DocumentActionButtonsGroup },
 | 
			
		||||
        data() {
 | 
			
		||||
          const datasets = el.dataset as {
 | 
			
		||||
            filename: string;
 | 
			
		||||
            canEdit: string;
 | 
			
		||||
            storedObject: string;
 | 
			
		||||
            buttonSmall: string;
 | 
			
		||||
            davLink: string;
 | 
			
		||||
            davLinkExpiration: string;
 | 
			
		||||
          };
 | 
			
		||||
 | 
			
		||||
                    const storedObject = JSON.parse(
 | 
			
		||||
                            datasets.storedObject,
 | 
			
		||||
                        ) as StoredObject,
 | 
			
		||||
                        filename = datasets.filename,
 | 
			
		||||
                        canEdit = datasets.canEdit === "1",
 | 
			
		||||
                        small = datasets.buttonSmall === "1",
 | 
			
		||||
                        davLink =
 | 
			
		||||
                            "davLink" in datasets && datasets.davLink !== ""
 | 
			
		||||
                                ? datasets.davLink
 | 
			
		||||
                                : null,
 | 
			
		||||
                        davLinkExpiration =
 | 
			
		||||
                            "davLinkExpiration" in datasets
 | 
			
		||||
                                ? Number.parseInt(datasets.davLinkExpiration)
 | 
			
		||||
                                : null;
 | 
			
		||||
                    return {
 | 
			
		||||
                        storedObject,
 | 
			
		||||
                        filename,
 | 
			
		||||
                        canEdit,
 | 
			
		||||
                        small,
 | 
			
		||||
                        davLink,
 | 
			
		||||
                        davLinkExpiration,
 | 
			
		||||
                    };
 | 
			
		||||
                },
 | 
			
		||||
                template:
 | 
			
		||||
                    '<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small" :dav-link="davLink" :dav-link-expiration="davLinkExpiration" @on-stored-object-status-change="onStoredObjectStatusChange"></document-action-buttons-group>',
 | 
			
		||||
                methods: {
 | 
			
		||||
                    onStoredObjectStatusChange: function (
 | 
			
		||||
                        newStatus: StoredObjectStatusChange,
 | 
			
		||||
                    ): void {
 | 
			
		||||
                        this.$data.storedObject.status = newStatus.status;
 | 
			
		||||
                        this.$data.storedObject.filename = newStatus.filename;
 | 
			
		||||
                        this.$data.storedObject.type = newStatus.type;
 | 
			
		||||
          const storedObject = JSON.parse(
 | 
			
		||||
              datasets.storedObject,
 | 
			
		||||
            ) as StoredObject,
 | 
			
		||||
            filename = datasets.filename,
 | 
			
		||||
            canEdit = datasets.canEdit === "1",
 | 
			
		||||
            small = datasets.buttonSmall === "1",
 | 
			
		||||
            davLink =
 | 
			
		||||
              "davLink" in datasets && datasets.davLink !== ""
 | 
			
		||||
                ? datasets.davLink
 | 
			
		||||
                : null,
 | 
			
		||||
            davLinkExpiration =
 | 
			
		||||
              "davLinkExpiration" in datasets
 | 
			
		||||
                ? Number.parseInt(datasets.davLinkExpiration)
 | 
			
		||||
                : null;
 | 
			
		||||
          return {
 | 
			
		||||
            storedObject,
 | 
			
		||||
            filename,
 | 
			
		||||
            canEdit,
 | 
			
		||||
            small,
 | 
			
		||||
            davLink,
 | 
			
		||||
            davLinkExpiration,
 | 
			
		||||
          };
 | 
			
		||||
        },
 | 
			
		||||
        template:
 | 
			
		||||
          '<document-action-buttons-group :can-edit="canEdit" :filename="filename" :stored-object="storedObject" :small="small" :dav-link="davLink" :dav-link-expiration="davLinkExpiration" @on-stored-object-status-change="onStoredObjectStatusChange"></document-action-buttons-group>',
 | 
			
		||||
        methods: {
 | 
			
		||||
          onStoredObjectStatusChange: function (
 | 
			
		||||
            newStatus: StoredObjectStatusChange,
 | 
			
		||||
          ): void {
 | 
			
		||||
            this.$data.storedObject.status = newStatus.status;
 | 
			
		||||
            this.$data.storedObject.filename = newStatus.filename;
 | 
			
		||||
            this.$data.storedObject.type = newStatus.type;
 | 
			
		||||
 | 
			
		||||
                        // remove eventual div which inform pending status
 | 
			
		||||
                        document
 | 
			
		||||
                            .querySelectorAll(
 | 
			
		||||
                                `[data-docgen-is-pending="${this.$data.storedObject.id}"]`,
 | 
			
		||||
                            )
 | 
			
		||||
                            .forEach(function (el) {
 | 
			
		||||
                                el.remove();
 | 
			
		||||
                            });
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
            });
 | 
			
		||||
            // remove eventual div which inform pending status
 | 
			
		||||
            document
 | 
			
		||||
              .querySelectorAll(
 | 
			
		||||
                `[data-docgen-is-pending="${this.$data.storedObject.id}"]`,
 | 
			
		||||
              )
 | 
			
		||||
              .forEach(function (el) {
 | 
			
		||||
                el.remove();
 | 
			
		||||
              });
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
            app.use(i18n).use(ToastPlugin).mount(el);
 | 
			
		||||
        });
 | 
			
		||||
      app.use(i18n).use(ToastPlugin).mount(el);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import { DateTime } from "ChillMainAssets/types";
 | 
			
		||||
import { StoredObject } from "ChillDocStoreAssets/types/index";
 | 
			
		||||
 | 
			
		||||
export interface GenericDocMetadata {
 | 
			
		||||
    isPresent: boolean;
 | 
			
		||||
  isPresent: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -15,74 +15,57 @@ export interface EmptyMetadata extends GenericDocMetadata {}
 | 
			
		||||
 * Minimal Metadata for a GenericDoc with a normalizer
 | 
			
		||||
 */
 | 
			
		||||
export interface BaseMetadata extends GenericDocMetadata {
 | 
			
		||||
    title: string;
 | 
			
		||||
  title: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A generic doc is a document attached to a Person or an AccompanyingPeriod.
 | 
			
		||||
 */
 | 
			
		||||
export interface GenericDoc {
 | 
			
		||||
    type: "doc_store_generic_doc";
 | 
			
		||||
    uniqueKey: string;
 | 
			
		||||
    key: string;
 | 
			
		||||
    identifiers: { id: number };
 | 
			
		||||
    context: "person" | "accompanying-period";
 | 
			
		||||
    doc_date: DateTime;
 | 
			
		||||
    metadata: GenericDocMetadata;
 | 
			
		||||
    storedObject: StoredObject | null;
 | 
			
		||||
  type: "doc_store_generic_doc";
 | 
			
		||||
  uniqueKey: string;
 | 
			
		||||
  key: string;
 | 
			
		||||
  identifiers: object;
 | 
			
		||||
  context: "person" | "accompanying-period";
 | 
			
		||||
  doc_date: DateTime;
 | 
			
		||||
  metadata: GenericDocMetadata;
 | 
			
		||||
  storedObject: StoredObject | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenericDocForAccompanyingPeriod extends GenericDoc {
 | 
			
		||||
    context: "accompanying-period";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isGenericDocForAccompanyingPeriod(
 | 
			
		||||
    doc: GenericDoc,
 | 
			
		||||
): doc is GenericDocForAccompanyingPeriod {
 | 
			
		||||
    return doc.context === "accompanying-period";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isGenericDocWithStoredObject(
 | 
			
		||||
    doc: GenericDoc,
 | 
			
		||||
): doc is GenericDoc & { storedObject: StoredObject } {
 | 
			
		||||
    return doc.storedObject !== null;
 | 
			
		||||
  context: "accompanying-period";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface BaseMetadataWithHtml extends BaseMetadata {
 | 
			
		||||
    html: string;
 | 
			
		||||
  html: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenericDocForAccompanyingCourseDocument
 | 
			
		||||
    extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
    key: "accompanying_course_document";
 | 
			
		||||
    metadata: BaseMetadataWithHtml;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
  extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
  key: "accompanying_course_document";
 | 
			
		||||
  metadata: BaseMetadataWithHtml;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenericDocForAccompanyingCourseActivityDocument
 | 
			
		||||
    extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
    key: "accompanying_course_activity_document";
 | 
			
		||||
    metadata: BaseMetadataWithHtml;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
  extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
  key: "accompanying_course_activity_document";
 | 
			
		||||
  metadata: BaseMetadataWithHtml;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenericDocForAccompanyingCourseCalendarDocument
 | 
			
		||||
    extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
    key: "accompanying_course_calendar_document";
 | 
			
		||||
    metadata: BaseMetadataWithHtml;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
  extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
  key: "accompanying_course_calendar_document";
 | 
			
		||||
  metadata: BaseMetadataWithHtml;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenericDocForAccompanyingCoursePersonDocument
 | 
			
		||||
    extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
    key: "person_document";
 | 
			
		||||
    metadata: BaseMetadataWithHtml;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
  extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
  key: "person_document";
 | 
			
		||||
  metadata: BaseMetadataWithHtml;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument
 | 
			
		||||
    extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
    key: "accompanying_period_work_evaluation_document";
 | 
			
		||||
    metadata: BaseMetadataWithHtml;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
  extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
  key: "accompanying_period_work_evaluation_document";
 | 
			
		||||
  metadata: BaseMetadataWithHtml;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,73 +4,73 @@ import { SignedUrlGet } from "ChillDocStoreAssets/vuejs/StoredObjectButton/helpe
 | 
			
		||||
export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending";
 | 
			
		||||
 | 
			
		||||
export interface StoredObject {
 | 
			
		||||
    id: number;
 | 
			
		||||
    title: string | null;
 | 
			
		||||
    uuid: string;
 | 
			
		||||
    prefix: string;
 | 
			
		||||
    status: StoredObjectStatus;
 | 
			
		||||
    currentVersion:
 | 
			
		||||
        | null
 | 
			
		||||
        | StoredObjectVersionCreated
 | 
			
		||||
        | StoredObjectVersionPersisted;
 | 
			
		||||
    totalVersions: number;
 | 
			
		||||
    datas: object;
 | 
			
		||||
    /** @deprecated */
 | 
			
		||||
    creationDate: DateTime;
 | 
			
		||||
    createdAt: DateTime | null;
 | 
			
		||||
    createdBy: User | null;
 | 
			
		||||
    _permissions: {
 | 
			
		||||
        canEdit: boolean;
 | 
			
		||||
        canSee: boolean;
 | 
			
		||||
    };
 | 
			
		||||
    _links?: {
 | 
			
		||||
        dav_link?: {
 | 
			
		||||
            href: string;
 | 
			
		||||
            expiration: number;
 | 
			
		||||
        };
 | 
			
		||||
        downloadLink?: SignedUrlGet;
 | 
			
		||||
  id: number;
 | 
			
		||||
  title: string | null;
 | 
			
		||||
  uuid: string;
 | 
			
		||||
  prefix: string;
 | 
			
		||||
  status: StoredObjectStatus;
 | 
			
		||||
  currentVersion:
 | 
			
		||||
    | null
 | 
			
		||||
    | StoredObjectVersionCreated
 | 
			
		||||
    | StoredObjectVersionPersisted;
 | 
			
		||||
  totalVersions: number;
 | 
			
		||||
  datas: object;
 | 
			
		||||
  /** @deprecated */
 | 
			
		||||
  creationDate: DateTime;
 | 
			
		||||
  createdAt: DateTime | null;
 | 
			
		||||
  createdBy: User | null;
 | 
			
		||||
  _permissions: {
 | 
			
		||||
    canEdit: boolean;
 | 
			
		||||
    canSee: boolean;
 | 
			
		||||
  };
 | 
			
		||||
  _links?: {
 | 
			
		||||
    dav_link?: {
 | 
			
		||||
      href: string;
 | 
			
		||||
      expiration: number;
 | 
			
		||||
    };
 | 
			
		||||
    downloadLink?: SignedUrlGet;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface StoredObjectVersion {
 | 
			
		||||
    /**
 | 
			
		||||
     * filename of the object in the object storage
 | 
			
		||||
     */
 | 
			
		||||
    filename: string;
 | 
			
		||||
    iv: number[];
 | 
			
		||||
    keyInfos: JsonWebKey;
 | 
			
		||||
    type: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * filename of the object in the object storage
 | 
			
		||||
   */
 | 
			
		||||
  filename: string;
 | 
			
		||||
  iv: number[];
 | 
			
		||||
  keyInfos: JsonWebKey;
 | 
			
		||||
  type: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface StoredObjectVersionCreated extends StoredObjectVersion {
 | 
			
		||||
    persisted: false;
 | 
			
		||||
  persisted: false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface StoredObjectVersionPersisted
 | 
			
		||||
    extends StoredObjectVersionCreated {
 | 
			
		||||
    version: number;
 | 
			
		||||
    id: number;
 | 
			
		||||
    createdAt: DateTime | null;
 | 
			
		||||
    createdBy: User | null;
 | 
			
		||||
  extends StoredObjectVersionCreated {
 | 
			
		||||
  version: number;
 | 
			
		||||
  id: number;
 | 
			
		||||
  createdAt: DateTime | null;
 | 
			
		||||
  createdBy: User | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface StoredObjectStatusChange {
 | 
			
		||||
    id: number;
 | 
			
		||||
    filename: string;
 | 
			
		||||
    status: StoredObjectStatus;
 | 
			
		||||
    type: string;
 | 
			
		||||
  id: number;
 | 
			
		||||
  filename: string;
 | 
			
		||||
  status: StoredObjectStatus;
 | 
			
		||||
  type: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface StoredObjectVersionWithPointInTime
 | 
			
		||||
    extends StoredObjectVersionPersisted {
 | 
			
		||||
    "point-in-times": StoredObjectPointInTime[];
 | 
			
		||||
    "from-restored": StoredObjectVersionPersisted | null;
 | 
			
		||||
  extends StoredObjectVersionPersisted {
 | 
			
		||||
  "point-in-times": StoredObjectPointInTime[];
 | 
			
		||||
  "from-restored": StoredObjectVersionPersisted | null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface StoredObjectPointInTime {
 | 
			
		||||
    id: number;
 | 
			
		||||
    byUser: User | null;
 | 
			
		||||
    reason: "keep-before-conversion" | "keep-by-user";
 | 
			
		||||
  id: number;
 | 
			
		||||
  byUser: User | null;
 | 
			
		||||
  reason: "keep-before-conversion" | "keep-by-user";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -82,63 +82,63 @@ export type WopiEditButtonExecutableBeforeLeaveFunction = () => Promise<void>;
 | 
			
		||||
 * Object containing information for performering a POST request to a swift object store
 | 
			
		||||
 */
 | 
			
		||||
export interface PostStoreObjectSignature {
 | 
			
		||||
    method: "POST";
 | 
			
		||||
    max_file_size: number;
 | 
			
		||||
    max_file_count: 1;
 | 
			
		||||
    expires: number;
 | 
			
		||||
    submit_delay: 180;
 | 
			
		||||
    redirect: string;
 | 
			
		||||
    prefix: string;
 | 
			
		||||
    url: string;
 | 
			
		||||
    signature: string;
 | 
			
		||||
  method: "POST";
 | 
			
		||||
  max_file_size: number;
 | 
			
		||||
  max_file_count: 1;
 | 
			
		||||
  expires: number;
 | 
			
		||||
  submit_delay: 180;
 | 
			
		||||
  redirect: string;
 | 
			
		||||
  prefix: string;
 | 
			
		||||
  url: string;
 | 
			
		||||
  signature: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PDFPage {
 | 
			
		||||
    index: number;
 | 
			
		||||
    width: number;
 | 
			
		||||
    height: number;
 | 
			
		||||
  index: number;
 | 
			
		||||
  width: number;
 | 
			
		||||
  height: number;
 | 
			
		||||
}
 | 
			
		||||
export interface SignatureZone {
 | 
			
		||||
    index: number | null;
 | 
			
		||||
    x: number;
 | 
			
		||||
    y: number;
 | 
			
		||||
    width: number;
 | 
			
		||||
    height: number;
 | 
			
		||||
    PDFPage: PDFPage;
 | 
			
		||||
  index: number | null;
 | 
			
		||||
  x: number;
 | 
			
		||||
  y: number;
 | 
			
		||||
  width: number;
 | 
			
		||||
  height: number;
 | 
			
		||||
  PDFPage: PDFPage;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Signature {
 | 
			
		||||
    id: number;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
    zones: SignatureZone[];
 | 
			
		||||
  id: number;
 | 
			
		||||
  storedObject: StoredObject;
 | 
			
		||||
  zones: SignatureZone[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type SignedState =
 | 
			
		||||
    | "pending"
 | 
			
		||||
    | "signed"
 | 
			
		||||
    | "rejected"
 | 
			
		||||
    | "canceled"
 | 
			
		||||
    | "error";
 | 
			
		||||
  | "pending"
 | 
			
		||||
  | "signed"
 | 
			
		||||
  | "rejected"
 | 
			
		||||
  | "canceled"
 | 
			
		||||
  | "error";
 | 
			
		||||
 | 
			
		||||
export interface CheckSignature {
 | 
			
		||||
    state: SignedState;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
  state: SignedState;
 | 
			
		||||
  storedObject: StoredObject;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type CanvasEvent = "select" | "add";
 | 
			
		||||
 | 
			
		||||
export interface ZoomLevel {
 | 
			
		||||
    id: number;
 | 
			
		||||
    zoom: number;
 | 
			
		||||
    label: {
 | 
			
		||||
        fr?: string;
 | 
			
		||||
        nl?: string;
 | 
			
		||||
    };
 | 
			
		||||
  id: number;
 | 
			
		||||
  zoom: number;
 | 
			
		||||
  label: {
 | 
			
		||||
    fr?: string;
 | 
			
		||||
    nl?: string;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenericDoc {
 | 
			
		||||
    type: "doc_store_generic_doc";
 | 
			
		||||
    key: string;
 | 
			
		||||
    context: "person" | "accompanying-period";
 | 
			
		||||
    doc_date: DateTime;
 | 
			
		||||
  type: "doc_store_generic_doc";
 | 
			
		||||
  key: string;
 | 
			
		||||
  context: "person" | "accompanying-period";
 | 
			
		||||
  doc_date: DateTime;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,67 +1,65 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <div v-if="isButtonGroupDisplayable" class="btn-group">
 | 
			
		||||
        <button
 | 
			
		||||
            :class="
 | 
			
		||||
                Object.assign({
 | 
			
		||||
                    btn: true,
 | 
			
		||||
                    'btn-outline-primary': true,
 | 
			
		||||
                    'dropdown-toggle': true,
 | 
			
		||||
                    'btn-sm': props.small,
 | 
			
		||||
                })
 | 
			
		||||
            "
 | 
			
		||||
            type="button"
 | 
			
		||||
            data-bs-toggle="dropdown"
 | 
			
		||||
            aria-expanded="false"
 | 
			
		||||
        >
 | 
			
		||||
            Actions
 | 
			
		||||
        </button>
 | 
			
		||||
        <ul class="dropdown-menu">
 | 
			
		||||
            <li v-if="isEditableOnline">
 | 
			
		||||
                <wopi-edit-button
 | 
			
		||||
                    :stored-object="props.storedObject"
 | 
			
		||||
                    :classes="{ 'dropdown-item': true }"
 | 
			
		||||
                    :execute-before-leave="props.executeBeforeLeave"
 | 
			
		||||
                ></wopi-edit-button>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li v-if="isEditableOnDesktop">
 | 
			
		||||
                <desktop-edit-button
 | 
			
		||||
                    :classes="{ 'dropdown-item': true }"
 | 
			
		||||
                    :edit-link="props.davLink"
 | 
			
		||||
                    :expiration-link="props.davLinkExpiration"
 | 
			
		||||
                ></desktop-edit-button>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li v-if="isConvertibleToPdf">
 | 
			
		||||
                <convert-button
 | 
			
		||||
                    :stored-object="props.storedObject"
 | 
			
		||||
                    :filename="filename"
 | 
			
		||||
                    :classes="{ 'dropdown-item': true }"
 | 
			
		||||
                ></convert-button>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li v-if="isDownloadable">
 | 
			
		||||
                <download-button
 | 
			
		||||
                    :stored-object="props.storedObject"
 | 
			
		||||
                    :at-version="props.storedObject.currentVersion"
 | 
			
		||||
                    :filename="filename"
 | 
			
		||||
                    :classes="{ 'dropdown-item': true }"
 | 
			
		||||
                    :display-action-string-in-button="true"
 | 
			
		||||
                ></download-button>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li v-if="isHistoryViewable">
 | 
			
		||||
                <history-button
 | 
			
		||||
                    :stored-object="props.storedObject"
 | 
			
		||||
                    :can-edit="
 | 
			
		||||
                        canEdit && props.storedObject._permissions.canEdit
 | 
			
		||||
                    "
 | 
			
		||||
                ></history-button>
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-else-if="'pending' === props.storedObject.status">
 | 
			
		||||
        <div class="btn btn-outline-info">Génération en cours</div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-else-if="'failure' === props.storedObject.status">
 | 
			
		||||
        <div class="btn btn-outline-danger">La génération a échoué</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  <div v-if="isButtonGroupDisplayable" class="btn-group">
 | 
			
		||||
    <button
 | 
			
		||||
      :class="
 | 
			
		||||
        Object.assign({
 | 
			
		||||
          btn: true,
 | 
			
		||||
          'btn-outline-primary': true,
 | 
			
		||||
          'dropdown-toggle': true,
 | 
			
		||||
          'btn-sm': props.small,
 | 
			
		||||
        })
 | 
			
		||||
      "
 | 
			
		||||
      type="button"
 | 
			
		||||
      data-bs-toggle="dropdown"
 | 
			
		||||
      aria-expanded="false"
 | 
			
		||||
    >
 | 
			
		||||
      Actions
 | 
			
		||||
    </button>
 | 
			
		||||
    <ul class="dropdown-menu">
 | 
			
		||||
      <li v-if="isEditableOnline">
 | 
			
		||||
        <wopi-edit-button
 | 
			
		||||
          :stored-object="props.storedObject"
 | 
			
		||||
          :classes="{ 'dropdown-item': true }"
 | 
			
		||||
          :execute-before-leave="props.executeBeforeLeave"
 | 
			
		||||
        ></wopi-edit-button>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li v-if="isEditableOnDesktop">
 | 
			
		||||
        <desktop-edit-button
 | 
			
		||||
          :classes="{ 'dropdown-item': true }"
 | 
			
		||||
          :edit-link="props.davLink"
 | 
			
		||||
          :expiration-link="props.davLinkExpiration"
 | 
			
		||||
        ></desktop-edit-button>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li v-if="isConvertibleToPdf">
 | 
			
		||||
        <convert-button
 | 
			
		||||
          :stored-object="props.storedObject"
 | 
			
		||||
          :filename="filename"
 | 
			
		||||
          :classes="{ 'dropdown-item': true }"
 | 
			
		||||
        ></convert-button>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li v-if="isDownloadable">
 | 
			
		||||
        <download-button
 | 
			
		||||
          :stored-object="props.storedObject"
 | 
			
		||||
          :at-version="props.storedObject.currentVersion"
 | 
			
		||||
          :filename="filename"
 | 
			
		||||
          :classes="{ 'dropdown-item': true }"
 | 
			
		||||
          :display-action-string-in-button="true"
 | 
			
		||||
        ></download-button>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li v-if="isHistoryViewable">
 | 
			
		||||
        <history-button
 | 
			
		||||
          :stored-object="props.storedObject"
 | 
			
		||||
          :can-edit="canEdit && props.storedObject._permissions.canEdit"
 | 
			
		||||
        ></history-button>
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div v-else-if="'pending' === props.storedObject.status">
 | 
			
		||||
    <div class="btn btn-outline-info">Génération en cours</div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div v-else-if="'failure' === props.storedObject.status">
 | 
			
		||||
    <div class="btn btn-outline-danger">La génération a échoué</div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
@@ -70,68 +68,66 @@ import ConvertButton from "./StoredObjectButton/ConvertButton.vue";
 | 
			
		||||
import DownloadButton from "./StoredObjectButton/DownloadButton.vue";
 | 
			
		||||
import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue";
 | 
			
		||||
import {
 | 
			
		||||
    is_extension_editable,
 | 
			
		||||
    is_extension_viewable,
 | 
			
		||||
    is_object_ready,
 | 
			
		||||
  is_extension_editable,
 | 
			
		||||
  is_extension_viewable,
 | 
			
		||||
  is_object_ready,
 | 
			
		||||
} from "./StoredObjectButton/helpers";
 | 
			
		||||
import {
 | 
			
		||||
    StoredObject,
 | 
			
		||||
    StoredObjectStatusChange,
 | 
			
		||||
    StoredObjectVersion,
 | 
			
		||||
    WopiEditButtonExecutableBeforeLeaveFunction,
 | 
			
		||||
  StoredObject,
 | 
			
		||||
  StoredObjectStatusChange,
 | 
			
		||||
  StoredObjectVersion,
 | 
			
		||||
  WopiEditButtonExecutableBeforeLeaveFunction,
 | 
			
		||||
} from "../types";
 | 
			
		||||
import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue";
 | 
			
		||||
import HistoryButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton.vue";
 | 
			
		||||
 | 
			
		||||
interface DocumentActionButtonsGroupConfig {
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
    small?: boolean;
 | 
			
		||||
    canEdit?: boolean;
 | 
			
		||||
    canDownload?: boolean;
 | 
			
		||||
    canConvertPdf?: boolean;
 | 
			
		||||
    returnPath?: string;
 | 
			
		||||
  storedObject: StoredObject;
 | 
			
		||||
  small?: boolean;
 | 
			
		||||
  canEdit?: boolean;
 | 
			
		||||
  canDownload?: boolean;
 | 
			
		||||
  canConvertPdf?: boolean;
 | 
			
		||||
  returnPath?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Will be the filename displayed to the user when he·she download the document
 | 
			
		||||
     * (the document will be saved on his disk with this name)
 | 
			
		||||
     *
 | 
			
		||||
     * If not set, 'document' will be used.
 | 
			
		||||
     */
 | 
			
		||||
    filename?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * Will be the filename displayed to the user when he·she download the document
 | 
			
		||||
   * (the document will be saved on his disk with this name)
 | 
			
		||||
   *
 | 
			
		||||
   * If not set, 'document' will be used.
 | 
			
		||||
   */
 | 
			
		||||
  filename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If set, will execute this function before leaving to the editor
 | 
			
		||||
     */
 | 
			
		||||
    executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
 | 
			
		||||
  /**
 | 
			
		||||
   * If set, will execute this function before leaving to the editor
 | 
			
		||||
   */
 | 
			
		||||
  executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * a link to download and edit file using webdav
 | 
			
		||||
     */
 | 
			
		||||
    davLink?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * a link to download and edit file using webdav
 | 
			
		||||
   */
 | 
			
		||||
  davLink?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the expiration date of the download, as a unix timestamp
 | 
			
		||||
     */
 | 
			
		||||
    davLinkExpiration?: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * the expiration date of the download, as a unix timestamp
 | 
			
		||||
   */
 | 
			
		||||
  davLinkExpiration?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit =
 | 
			
		||||
    defineEmits<
 | 
			
		||||
        (
 | 
			
		||||
            e: "onStoredObjectStatusChange",
 | 
			
		||||
            newStatus: StoredObjectStatusChange,
 | 
			
		||||
        ) => void
 | 
			
		||||
    >();
 | 
			
		||||
  defineEmits<
 | 
			
		||||
    (
 | 
			
		||||
      e: "onStoredObjectStatusChange",
 | 
			
		||||
      newStatus: StoredObjectStatusChange,
 | 
			
		||||
    ) => void
 | 
			
		||||
  >();
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<DocumentActionButtonsGroupConfig>(), {
 | 
			
		||||
    small: false,
 | 
			
		||||
    canEdit: true,
 | 
			
		||||
    canDownload: true,
 | 
			
		||||
    canConvertPdf: true,
 | 
			
		||||
    returnPath:
 | 
			
		||||
        window.location.pathname +
 | 
			
		||||
        window.location.search +
 | 
			
		||||
        window.location.hash,
 | 
			
		||||
  small: false,
 | 
			
		||||
  canEdit: true,
 | 
			
		||||
  canDownload: true,
 | 
			
		||||
  canConvertPdf: true,
 | 
			
		||||
  returnPath:
 | 
			
		||||
    window.location.pathname + window.location.search + window.location.hash,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -145,93 +141,93 @@ let tryiesForReady = 0;
 | 
			
		||||
const maxTryiesForReady = 120;
 | 
			
		||||
 | 
			
		||||
const isButtonGroupDisplayable = computed<boolean>(() => {
 | 
			
		||||
    return (
 | 
			
		||||
        isDownloadable.value ||
 | 
			
		||||
        isEditableOnline.value ||
 | 
			
		||||
        isEditableOnDesktop.value ||
 | 
			
		||||
        isConvertibleToPdf.value
 | 
			
		||||
    );
 | 
			
		||||
  return (
 | 
			
		||||
    isDownloadable.value ||
 | 
			
		||||
    isEditableOnline.value ||
 | 
			
		||||
    isEditableOnDesktop.value ||
 | 
			
		||||
    isConvertibleToPdf.value
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const isDownloadable = computed<boolean>(() => {
 | 
			
		||||
    return (
 | 
			
		||||
        props.storedObject.status === "ready" ||
 | 
			
		||||
        // happens when the stored object version is just added, but not persisted
 | 
			
		||||
        (props.storedObject.currentVersion !== null &&
 | 
			
		||||
            props.storedObject.status === "empty")
 | 
			
		||||
    );
 | 
			
		||||
  return (
 | 
			
		||||
    props.storedObject.status === "ready" ||
 | 
			
		||||
    // happens when the stored object version is just added, but not persisted
 | 
			
		||||
    (props.storedObject.currentVersion !== null &&
 | 
			
		||||
      props.storedObject.status === "empty")
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const isEditableOnline = computed<boolean>(() => {
 | 
			
		||||
    return (
 | 
			
		||||
        props.storedObject.status === "ready" &&
 | 
			
		||||
        props.storedObject._permissions.canEdit &&
 | 
			
		||||
        props.canEdit &&
 | 
			
		||||
        props.storedObject.currentVersion !== null &&
 | 
			
		||||
        is_extension_editable(props.storedObject.currentVersion.type) &&
 | 
			
		||||
        props.storedObject.currentVersion.persisted !== false
 | 
			
		||||
    );
 | 
			
		||||
  return (
 | 
			
		||||
    props.storedObject.status === "ready" &&
 | 
			
		||||
    props.storedObject._permissions.canEdit &&
 | 
			
		||||
    props.canEdit &&
 | 
			
		||||
    props.storedObject.currentVersion !== null &&
 | 
			
		||||
    is_extension_editable(props.storedObject.currentVersion.type) &&
 | 
			
		||||
    props.storedObject.currentVersion.persisted !== false
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const isEditableOnDesktop = computed<boolean>(() => {
 | 
			
		||||
    return isEditableOnline.value;
 | 
			
		||||
  return isEditableOnline.value;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const isConvertibleToPdf = computed<boolean>(() => {
 | 
			
		||||
    return (
 | 
			
		||||
        props.storedObject.status === "ready" &&
 | 
			
		||||
        props.storedObject._permissions.canSee &&
 | 
			
		||||
        props.canConvertPdf &&
 | 
			
		||||
        props.storedObject.currentVersion !== null &&
 | 
			
		||||
        is_extension_viewable(props.storedObject.currentVersion.type) &&
 | 
			
		||||
        props.storedObject.currentVersion.type !== "application/pdf" &&
 | 
			
		||||
        props.storedObject.currentVersion.persisted !== false
 | 
			
		||||
    );
 | 
			
		||||
  return (
 | 
			
		||||
    props.storedObject.status === "ready" &&
 | 
			
		||||
    props.storedObject._permissions.canSee &&
 | 
			
		||||
    props.canConvertPdf &&
 | 
			
		||||
    props.storedObject.currentVersion !== null &&
 | 
			
		||||
    is_extension_viewable(props.storedObject.currentVersion.type) &&
 | 
			
		||||
    props.storedObject.currentVersion.type !== "application/pdf" &&
 | 
			
		||||
    props.storedObject.currentVersion.persisted !== false
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const isHistoryViewable = computed<boolean>(() => {
 | 
			
		||||
    return props.storedObject.status === "ready";
 | 
			
		||||
  return props.storedObject.status === "ready";
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const checkForReady = function (): void {
 | 
			
		||||
    if (
 | 
			
		||||
        "ready" === props.storedObject.status ||
 | 
			
		||||
        "empty" === props.storedObject.status ||
 | 
			
		||||
        "failure" === props.storedObject.status ||
 | 
			
		||||
        // stop reloading if the page stays opened for a long time
 | 
			
		||||
        tryiesForReady > maxTryiesForReady
 | 
			
		||||
    ) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
  if (
 | 
			
		||||
    "ready" === props.storedObject.status ||
 | 
			
		||||
    "empty" === props.storedObject.status ||
 | 
			
		||||
    "failure" === props.storedObject.status ||
 | 
			
		||||
    // stop reloading if the page stays opened for a long time
 | 
			
		||||
    tryiesForReady > maxTryiesForReady
 | 
			
		||||
  ) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    tryiesForReady = tryiesForReady + 1;
 | 
			
		||||
  tryiesForReady = tryiesForReady + 1;
 | 
			
		||||
 | 
			
		||||
    setTimeout(onObjectNewStatusCallback, 5000);
 | 
			
		||||
  setTimeout(onObjectNewStatusCallback, 5000);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onObjectNewStatusCallback = async function (): Promise<void> {
 | 
			
		||||
    if (props.storedObject.status === "stored_object_created") {
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const new_status = await is_object_ready(props.storedObject);
 | 
			
		||||
    if (props.storedObject.status !== new_status.status) {
 | 
			
		||||
        emit("onStoredObjectStatusChange", new_status);
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
    } else if ("failure" === new_status.status) {
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ("ready" !== new_status.status) {
 | 
			
		||||
        // we check for new status, unless it is ready
 | 
			
		||||
        checkForReady();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  if (props.storedObject.status === "stored_object_created") {
 | 
			
		||||
    return Promise.resolve();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const new_status = await is_object_ready(props.storedObject);
 | 
			
		||||
  if (props.storedObject.status !== new_status.status) {
 | 
			
		||||
    emit("onStoredObjectStatusChange", new_status);
 | 
			
		||||
    return Promise.resolve();
 | 
			
		||||
  } else if ("failure" === new_status.status) {
 | 
			
		||||
    return Promise.resolve();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ("ready" !== new_status.status) {
 | 
			
		||||
    // we check for new status, unless it is ready
 | 
			
		||||
    checkForReady();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return Promise.resolve();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    checkForReady();
 | 
			
		||||
  checkForReady();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -4,36 +4,36 @@ import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
 | 
			
		||||
import App from "./App.vue";
 | 
			
		||||
 | 
			
		||||
const appMessages = {
 | 
			
		||||
    fr: {
 | 
			
		||||
        yes: "Oui",
 | 
			
		||||
        are_you_sure: "Êtes-vous sûr·e?",
 | 
			
		||||
        you_are_going_to_sign: "Vous allez signer le document",
 | 
			
		||||
        signature_confirmation: "Confirmation de la signature",
 | 
			
		||||
        sign: "Signer",
 | 
			
		||||
        choose_another_signature: "Choisir une autre zone",
 | 
			
		||||
        cancel: "Annuler",
 | 
			
		||||
        last_sign_zone: "Zone de signature précédente",
 | 
			
		||||
        next_sign_zone: "Zone de signature suivante",
 | 
			
		||||
        add_sign_zone: "Ajouter une zone de signature",
 | 
			
		||||
        click_on_document: "Cliquer sur le document",
 | 
			
		||||
        last_zone: "Zone précédente",
 | 
			
		||||
        next_zone: "Zone suivante",
 | 
			
		||||
        add_zone: "Ajouter une zone",
 | 
			
		||||
        another_zone: "Autre zone",
 | 
			
		||||
        electronic_signature_in_progress: "Signature électronique en cours...",
 | 
			
		||||
        loading: "Chargement...",
 | 
			
		||||
        remove_sign_zone: "Enlever la zone",
 | 
			
		||||
        return: "Retour",
 | 
			
		||||
        see_all_pages: "Voir toutes les pages",
 | 
			
		||||
        all_pages: "Toutes les pages",
 | 
			
		||||
    },
 | 
			
		||||
  fr: {
 | 
			
		||||
    yes: "Oui",
 | 
			
		||||
    are_you_sure: "Êtes-vous sûr·e?",
 | 
			
		||||
    you_are_going_to_sign: "Vous allez signer le document",
 | 
			
		||||
    signature_confirmation: "Confirmation de la signature",
 | 
			
		||||
    sign: "Signer",
 | 
			
		||||
    choose_another_signature: "Choisir une autre zone",
 | 
			
		||||
    cancel: "Annuler",
 | 
			
		||||
    last_sign_zone: "Zone de signature précédente",
 | 
			
		||||
    next_sign_zone: "Zone de signature suivante",
 | 
			
		||||
    add_sign_zone: "Ajouter une zone de signature",
 | 
			
		||||
    click_on_document: "Cliquer sur le document",
 | 
			
		||||
    last_zone: "Zone précédente",
 | 
			
		||||
    next_zone: "Zone suivante",
 | 
			
		||||
    add_zone: "Ajouter une zone",
 | 
			
		||||
    another_zone: "Autre zone",
 | 
			
		||||
    electronic_signature_in_progress: "Signature électronique en cours...",
 | 
			
		||||
    loading: "Chargement...",
 | 
			
		||||
    remove_sign_zone: "Enlever la zone",
 | 
			
		||||
    return: "Retour",
 | 
			
		||||
    see_all_pages: "Voir toutes les pages",
 | 
			
		||||
    all_pages: "Toutes les pages",
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const i18n = _createI18n(appMessages);
 | 
			
		||||
 | 
			
		||||
const app = createApp({
 | 
			
		||||
    template: `<app></app>`,
 | 
			
		||||
  template: `<app></app>`,
 | 
			
		||||
})
 | 
			
		||||
    .use(i18n)
 | 
			
		||||
    .component("app", App)
 | 
			
		||||
    .mount("#document-signature");
 | 
			
		||||
  .use(i18n)
 | 
			
		||||
  .component("app", App)
 | 
			
		||||
  .mount("#document-signature");
 | 
			
		||||
 
 | 
			
		||||
@@ -1,208 +1,206 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { StoredObject, StoredObjectVersionCreated } from "../../types";
 | 
			
		||||
import {
 | 
			
		||||
    encryptFile,
 | 
			
		||||
    fetchNewStoredObject,
 | 
			
		||||
    uploadVersion,
 | 
			
		||||
  encryptFile,
 | 
			
		||||
  fetchNewStoredObject,
 | 
			
		||||
  uploadVersion,
 | 
			
		||||
} from "../../js/async-upload/uploader";
 | 
			
		||||
import { computed, ref, Ref } from "vue";
 | 
			
		||||
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
 | 
			
		||||
 | 
			
		||||
interface DropFileConfig {
 | 
			
		||||
    existingDoc?: StoredObject;
 | 
			
		||||
  existingDoc?: StoredObject;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<DropFileConfig>(), {
 | 
			
		||||
    existingDoc: null,
 | 
			
		||||
  existingDoc: null,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit =
 | 
			
		||||
    defineEmits<
 | 
			
		||||
        (
 | 
			
		||||
            e: "addDocument",
 | 
			
		||||
            {
 | 
			
		||||
                stored_object_version: StoredObjectVersionCreated,
 | 
			
		||||
                stored_object: StoredObject,
 | 
			
		||||
                file_name: string,
 | 
			
		||||
            },
 | 
			
		||||
        ) => void
 | 
			
		||||
    >();
 | 
			
		||||
  defineEmits<
 | 
			
		||||
    (
 | 
			
		||||
      e: "addDocument",
 | 
			
		||||
      {
 | 
			
		||||
        stored_object_version: StoredObjectVersionCreated,
 | 
			
		||||
        stored_object: StoredObject,
 | 
			
		||||
        file_name: string,
 | 
			
		||||
      },
 | 
			
		||||
    ) => void
 | 
			
		||||
  >();
 | 
			
		||||
 | 
			
		||||
const is_dragging: Ref<boolean> = ref(false);
 | 
			
		||||
const uploading: Ref<boolean> = ref(false);
 | 
			
		||||
const display_filename: Ref<string | null> = ref(null);
 | 
			
		||||
 | 
			
		||||
const has_existing_doc = computed<boolean>(() => {
 | 
			
		||||
    return props.existingDoc !== undefined && props.existingDoc !== null;
 | 
			
		||||
  return props.existingDoc !== undefined && props.existingDoc !== null;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const onDragOver = (e: Event) => {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
  e.preventDefault();
 | 
			
		||||
 | 
			
		||||
    is_dragging.value = true;
 | 
			
		||||
  is_dragging.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onDragLeave = (e: Event) => {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
  e.preventDefault();
 | 
			
		||||
 | 
			
		||||
    is_dragging.value = false;
 | 
			
		||||
  is_dragging.value = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onDrop = (e: DragEvent) => {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
  e.preventDefault();
 | 
			
		||||
 | 
			
		||||
    const files = e.dataTransfer?.files;
 | 
			
		||||
  const files = e.dataTransfer?.files;
 | 
			
		||||
 | 
			
		||||
    if (null === files || undefined === files) {
 | 
			
		||||
        console.error("no files transferred", e.dataTransfer);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (files.length === 0) {
 | 
			
		||||
        console.error("no files given");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
  if (null === files || undefined === files) {
 | 
			
		||||
    console.error("no files transferred", e.dataTransfer);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  if (files.length === 0) {
 | 
			
		||||
    console.error("no files given");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    handleFile(files[0]);
 | 
			
		||||
  handleFile(files[0]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onZoneClick = (e: Event) => {
 | 
			
		||||
    e.stopPropagation();
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
  e.stopPropagation();
 | 
			
		||||
  e.preventDefault();
 | 
			
		||||
 | 
			
		||||
    const input = document.createElement("input");
 | 
			
		||||
    input.type = "file";
 | 
			
		||||
    input.addEventListener("change", onFileChange);
 | 
			
		||||
  const input = document.createElement("input");
 | 
			
		||||
  input.type = "file";
 | 
			
		||||
  input.addEventListener("change", onFileChange);
 | 
			
		||||
 | 
			
		||||
    input.click();
 | 
			
		||||
  input.click();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onFileChange = async (event: Event): Promise<void> => {
 | 
			
		||||
    const input = event.target as HTMLInputElement;
 | 
			
		||||
  const input = event.target as HTMLInputElement;
 | 
			
		||||
 | 
			
		||||
    if (input.files && input.files[0]) {
 | 
			
		||||
        console.log("file added", input.files[0]);
 | 
			
		||||
        const file = input.files[0];
 | 
			
		||||
        await handleFile(file);
 | 
			
		||||
  if (input.files && input.files[0]) {
 | 
			
		||||
    console.log("file added", input.files[0]);
 | 
			
		||||
    const file = input.files[0];
 | 
			
		||||
    await handleFile(file);
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
    }
 | 
			
		||||
    return Promise.resolve();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    throw "No file given";
 | 
			
		||||
  throw "No file given";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleFile = async (file: File): Promise<void> => {
 | 
			
		||||
    uploading.value = true;
 | 
			
		||||
    display_filename.value = file.name;
 | 
			
		||||
    const type = file.type;
 | 
			
		||||
  uploading.value = true;
 | 
			
		||||
  display_filename.value = file.name;
 | 
			
		||||
  const type = file.type;
 | 
			
		||||
 | 
			
		||||
    // create a stored_object if not exists
 | 
			
		||||
    let stored_object;
 | 
			
		||||
    if (null === props.existingDoc) {
 | 
			
		||||
        stored_object = await fetchNewStoredObject();
 | 
			
		||||
    } else {
 | 
			
		||||
        stored_object = props.existingDoc;
 | 
			
		||||
    }
 | 
			
		||||
  // create a stored_object if not exists
 | 
			
		||||
  let stored_object;
 | 
			
		||||
  if (null === props.existingDoc) {
 | 
			
		||||
    stored_object = await fetchNewStoredObject();
 | 
			
		||||
  } else {
 | 
			
		||||
    stored_object = props.existingDoc;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const buffer = await file.arrayBuffer();
 | 
			
		||||
    const [encrypted, iv, jsonWebKey] = await encryptFile(buffer);
 | 
			
		||||
    const filename = await uploadVersion(encrypted, stored_object);
 | 
			
		||||
  const buffer = await file.arrayBuffer();
 | 
			
		||||
  const [encrypted, iv, jsonWebKey] = await encryptFile(buffer);
 | 
			
		||||
  const filename = await uploadVersion(encrypted, stored_object);
 | 
			
		||||
 | 
			
		||||
    const stored_object_version: StoredObjectVersionCreated = {
 | 
			
		||||
        filename: filename,
 | 
			
		||||
        iv: Array.from(iv),
 | 
			
		||||
        keyInfos: jsonWebKey,
 | 
			
		||||
        type: type,
 | 
			
		||||
        persisted: false,
 | 
			
		||||
    };
 | 
			
		||||
  const stored_object_version: StoredObjectVersionCreated = {
 | 
			
		||||
    filename: filename,
 | 
			
		||||
    iv: Array.from(iv),
 | 
			
		||||
    keyInfos: jsonWebKey,
 | 
			
		||||
    type: type,
 | 
			
		||||
    persisted: false,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
    const fileName = file.name;
 | 
			
		||||
    let file_name = "Nouveau document";
 | 
			
		||||
    const file_name_split = fileName.split(".");
 | 
			
		||||
    if (file_name_split.length > 1) {
 | 
			
		||||
        const extension = file_name_split
 | 
			
		||||
            ? file_name_split[file_name_split.length - 1]
 | 
			
		||||
            : "";
 | 
			
		||||
        file_name = fileName.replace(extension, "").slice(0, -1);
 | 
			
		||||
    }
 | 
			
		||||
  const fileName = file.name;
 | 
			
		||||
  let file_name = "Nouveau document";
 | 
			
		||||
  const file_name_split = fileName.split(".");
 | 
			
		||||
  if (file_name_split.length > 1) {
 | 
			
		||||
    const extension = file_name_split
 | 
			
		||||
      ? file_name_split[file_name_split.length - 1]
 | 
			
		||||
      : "";
 | 
			
		||||
    file_name = fileName.replace(extension, "").slice(0, -1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    emit("addDocument", {
 | 
			
		||||
        stored_object,
 | 
			
		||||
        stored_object_version,
 | 
			
		||||
        file_name: file_name,
 | 
			
		||||
    });
 | 
			
		||||
    uploading.value = false;
 | 
			
		||||
  emit("addDocument", {
 | 
			
		||||
    stored_object,
 | 
			
		||||
    stored_object_version,
 | 
			
		||||
    file_name: file_name,
 | 
			
		||||
  });
 | 
			
		||||
  uploading.value = false;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
    <div class="drop-file">
 | 
			
		||||
        <div
 | 
			
		||||
            v-if="!uploading"
 | 
			
		||||
            :class="{ area: true, dragging: is_dragging }"
 | 
			
		||||
            @click="onZoneClick"
 | 
			
		||||
            @dragover="onDragOver"
 | 
			
		||||
            @dragleave="onDragLeave"
 | 
			
		||||
            @drop="onDrop"
 | 
			
		||||
        >
 | 
			
		||||
            <p v-if="has_existing_doc" class="file-icon">
 | 
			
		||||
                <file-icon :type="props.existingDoc?.type"></file-icon>
 | 
			
		||||
            </p>
 | 
			
		||||
  <div class="drop-file">
 | 
			
		||||
    <div
 | 
			
		||||
      v-if="!uploading"
 | 
			
		||||
      :class="{ area: true, dragging: is_dragging }"
 | 
			
		||||
      @click="onZoneClick"
 | 
			
		||||
      @dragover="onDragOver"
 | 
			
		||||
      @dragleave="onDragLeave"
 | 
			
		||||
      @drop="onDrop"
 | 
			
		||||
    >
 | 
			
		||||
      <p v-if="has_existing_doc" class="file-icon">
 | 
			
		||||
        <file-icon :type="props.existingDoc?.type"></file-icon>
 | 
			
		||||
      </p>
 | 
			
		||||
 | 
			
		||||
            <p v-if="display_filename !== null" class="display-filename">
 | 
			
		||||
                {{ display_filename }}
 | 
			
		||||
            </p>
 | 
			
		||||
            <!-- todo i18n -->
 | 
			
		||||
            <p v-if="has_existing_doc">
 | 
			
		||||
                Déposez un document ou cliquez ici pour remplacer le document
 | 
			
		||||
                existant
 | 
			
		||||
            </p>
 | 
			
		||||
            <p v-else>
 | 
			
		||||
                Déposez un document ou cliquez ici pour ouvrir le navigateur de
 | 
			
		||||
                fichier
 | 
			
		||||
            </p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-else class="waiting">
 | 
			
		||||
            <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
 | 
			
		||||
            <span class="sr-only">Loading...</span>
 | 
			
		||||
        </div>
 | 
			
		||||
      <p v-if="display_filename !== null" class="display-filename">
 | 
			
		||||
        {{ display_filename }}
 | 
			
		||||
      </p>
 | 
			
		||||
      <!-- todo i18n -->
 | 
			
		||||
      <p v-if="has_existing_doc">
 | 
			
		||||
        Déposez un document ou cliquez ici pour remplacer le document existant
 | 
			
		||||
      </p>
 | 
			
		||||
      <p v-else>
 | 
			
		||||
        Déposez un document ou cliquez ici pour ouvrir le navigateur de fichier
 | 
			
		||||
      </p>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-else class="waiting">
 | 
			
		||||
      <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
 | 
			
		||||
      <span class="sr-only">Loading...</span>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.drop-file {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
 | 
			
		||||
  .file-icon {
 | 
			
		||||
    font-size: xx-large;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .display-filename {
 | 
			
		||||
    font-variant: small-caps;
 | 
			
		||||
    font-weight: 200;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & > .area,
 | 
			
		||||
  & > .waiting {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 10rem;
 | 
			
		||||
 | 
			
		||||
    .file-icon {
 | 
			
		||||
        font-size: xx-large;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
 | 
			
		||||
    p {
 | 
			
		||||
      // require for display in DropFileModal
 | 
			
		||||
      text-align: center;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    .display-filename {
 | 
			
		||||
        font-variant: small-caps;
 | 
			
		||||
        font-weight: 200;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    & > .area,
 | 
			
		||||
    & > .waiting {
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        height: 10rem;
 | 
			
		||||
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
 | 
			
		||||
        p {
 | 
			
		||||
            // require for display in DropFileModal
 | 
			
		||||
            text-align: center;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    & > .area {
 | 
			
		||||
        border: 4px dashed #ccc;
 | 
			
		||||
 | 
			
		||||
        &.dragging {
 | 
			
		||||
            border: 4px dashed blue;
 | 
			
		||||
        }
 | 
			
		||||
  & > .area {
 | 
			
		||||
    border: 4px dashed #ccc;
 | 
			
		||||
 | 
			
		||||
    &.dragging {
 | 
			
		||||
      border: 4px dashed blue;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,27 +4,26 @@ import { StoredObject, StoredObjectVersion } from "../../types";
 | 
			
		||||
import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue";
 | 
			
		||||
import { computed, reactive } from "vue";
 | 
			
		||||
import { useToast } from "vue-toast-notification";
 | 
			
		||||
import { DOCUMENT_ADD, trans } from "translator";
 | 
			
		||||
 | 
			
		||||
interface DropFileConfig {
 | 
			
		||||
    allowRemove: boolean;
 | 
			
		||||
    existingDoc?: StoredObject;
 | 
			
		||||
  allowRemove: boolean;
 | 
			
		||||
  existingDoc?: StoredObject;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<DropFileConfig>(), {
 | 
			
		||||
    allowRemove: false,
 | 
			
		||||
  allowRemove: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
    (
 | 
			
		||||
        e: "addDocument",
 | 
			
		||||
        {
 | 
			
		||||
            stored_object: StoredObject,
 | 
			
		||||
            stored_object_version: StoredObjectVersion,
 | 
			
		||||
            file_name: string,
 | 
			
		||||
        },
 | 
			
		||||
    ): void;
 | 
			
		||||
    (e: "removeDocument"): void;
 | 
			
		||||
  (
 | 
			
		||||
    e: "addDocument",
 | 
			
		||||
    {
 | 
			
		||||
      stored_object: StoredObject,
 | 
			
		||||
      stored_object_version: StoredObjectVersion,
 | 
			
		||||
      file_name: string,
 | 
			
		||||
    },
 | 
			
		||||
  ): void;
 | 
			
		||||
  (e: "removeDocument"): void;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const $toast = useToast();
 | 
			
		||||
@@ -34,65 +33,67 @@ const state = reactive({ showModal: false });
 | 
			
		||||
const modalClasses = { "modal-dialog-centered": true, "modal-md": true };
 | 
			
		||||
 | 
			
		||||
const buttonState = computed<"add" | "replace">(() => {
 | 
			
		||||
    if (props.existingDoc === undefined || props.existingDoc === null) {
 | 
			
		||||
        return "add";
 | 
			
		||||
    }
 | 
			
		||||
  if (props.existingDoc === undefined || props.existingDoc === null) {
 | 
			
		||||
    return "add";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return "replace";
 | 
			
		||||
  return "replace";
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function onAddDocument({
 | 
			
		||||
    stored_object,
 | 
			
		||||
    stored_object_version,
 | 
			
		||||
    file_name,
 | 
			
		||||
  stored_object,
 | 
			
		||||
  stored_object_version,
 | 
			
		||||
  file_name,
 | 
			
		||||
}: {
 | 
			
		||||
    stored_object: StoredObject;
 | 
			
		||||
    stored_object_version: StoredObjectVersion;
 | 
			
		||||
    file_name: string;
 | 
			
		||||
  stored_object: StoredObject;
 | 
			
		||||
  stored_object_version: StoredObjectVersion;
 | 
			
		||||
  file_name: string;
 | 
			
		||||
}): void {
 | 
			
		||||
    const message =
 | 
			
		||||
        buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
 | 
			
		||||
    $toast.success(message);
 | 
			
		||||
    emit("addDocument", { stored_object_version, stored_object, file_name });
 | 
			
		||||
    state.showModal = false;
 | 
			
		||||
  const message =
 | 
			
		||||
    buttonState.value === "add" ? "Document ajouté" : "Document remplacé";
 | 
			
		||||
  $toast.success(message);
 | 
			
		||||
  emit("addDocument", { stored_object_version, stored_object, file_name });
 | 
			
		||||
  state.showModal = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onRemoveDocument(): void {
 | 
			
		||||
    emit("removeDocument");
 | 
			
		||||
  emit("removeDocument");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function openModal(): void {
 | 
			
		||||
    state.showModal = true;
 | 
			
		||||
  state.showModal = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function closeModal(): void {
 | 
			
		||||
    state.showModal = false;
 | 
			
		||||
  state.showModal = false;
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
    <button
 | 
			
		||||
        v-if="buttonState === 'add'"
 | 
			
		||||
        @click="openModal"
 | 
			
		||||
        class="btn btn-create"
 | 
			
		||||
    >
 | 
			
		||||
        {{ trans(DOCUMENT_ADD) }}
 | 
			
		||||
    </button>
 | 
			
		||||
    <button v-else @click="openModal" class="btn btn-edit"></button>
 | 
			
		||||
    <modal
 | 
			
		||||
        v-if="state.showModal"
 | 
			
		||||
        :modal-dialog-class="modalClasses"
 | 
			
		||||
        @close="closeModal"
 | 
			
		||||
    >
 | 
			
		||||
        <template v-slot:body>
 | 
			
		||||
            <drop-file-widget
 | 
			
		||||
                :existing-doc="existingDoc"
 | 
			
		||||
                :allow-remove="allowRemove"
 | 
			
		||||
                @add-document="onAddDocument"
 | 
			
		||||
                @remove-document="onRemoveDocument"
 | 
			
		||||
            ></drop-file-widget>
 | 
			
		||||
        </template>
 | 
			
		||||
    </modal>
 | 
			
		||||
  <button
 | 
			
		||||
    v-if="buttonState === 'add'"
 | 
			
		||||
    @click="openModal"
 | 
			
		||||
    class="btn btn-create"
 | 
			
		||||
  >
 | 
			
		||||
    Ajouter un document
 | 
			
		||||
  </button>
 | 
			
		||||
  <button v-else @click="openModal" class="btn btn-edit">
 | 
			
		||||
    Remplacer le document
 | 
			
		||||
  </button>
 | 
			
		||||
  <modal
 | 
			
		||||
    v-if="state.showModal"
 | 
			
		||||
    :modal-dialog-class="modalClasses"
 | 
			
		||||
    @close="closeModal"
 | 
			
		||||
  >
 | 
			
		||||
    <template v-slot:body>
 | 
			
		||||
      <drop-file-widget
 | 
			
		||||
        :existing-doc="existingDoc"
 | 
			
		||||
        :allow-remove="allowRemove"
 | 
			
		||||
        @add-document="onAddDocument"
 | 
			
		||||
        @remove-document="onRemoveDocument"
 | 
			
		||||
      ></drop-file-widget>
 | 
			
		||||
    </template>
 | 
			
		||||
  </modal>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,97 +5,97 @@ import DropFile from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFile.vue";
 | 
			
		||||
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
 | 
			
		||||
 | 
			
		||||
interface DropFileConfig {
 | 
			
		||||
    allowRemove: boolean;
 | 
			
		||||
    existingDoc?: StoredObject;
 | 
			
		||||
  allowRemove: boolean;
 | 
			
		||||
  existingDoc?: StoredObject;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<DropFileConfig>(), {
 | 
			
		||||
    allowRemove: false,
 | 
			
		||||
  allowRemove: false,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
    (
 | 
			
		||||
        e: "addDocument",
 | 
			
		||||
        {
 | 
			
		||||
            stored_object: StoredObject,
 | 
			
		||||
            stored_object_version: StoredObjectVersion,
 | 
			
		||||
            file_name: string,
 | 
			
		||||
        },
 | 
			
		||||
    ): void;
 | 
			
		||||
    (e: "removeDocument"): void;
 | 
			
		||||
  (
 | 
			
		||||
    e: "addDocument",
 | 
			
		||||
    {
 | 
			
		||||
      stored_object: StoredObject,
 | 
			
		||||
      stored_object_version: StoredObjectVersion,
 | 
			
		||||
      file_name: string,
 | 
			
		||||
    },
 | 
			
		||||
  ): void;
 | 
			
		||||
  (e: "removeDocument"): void;
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const has_existing_doc = computed<boolean>(() => {
 | 
			
		||||
    return props.existingDoc !== undefined && props.existingDoc !== null;
 | 
			
		||||
  return props.existingDoc !== undefined && props.existingDoc !== null;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const dav_link_expiration = computed<number | undefined>(() => {
 | 
			
		||||
    if (props.existingDoc === undefined || props.existingDoc === null) {
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
    if (props.existingDoc.status !== "ready") {
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
  if (props.existingDoc === undefined || props.existingDoc === null) {
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
  if (props.existingDoc.status !== "ready") {
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return props.existingDoc._links?.dav_link?.expiration;
 | 
			
		||||
  return props.existingDoc._links?.dav_link?.expiration;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const dav_link_href = computed<string | undefined>(() => {
 | 
			
		||||
    if (props.existingDoc === undefined || props.existingDoc === null) {
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
    if (props.existingDoc.status !== "ready") {
 | 
			
		||||
        return undefined;
 | 
			
		||||
    }
 | 
			
		||||
  if (props.existingDoc === undefined || props.existingDoc === null) {
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
  if (props.existingDoc.status !== "ready") {
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return props.existingDoc._links?.dav_link?.href;
 | 
			
		||||
  return props.existingDoc._links?.dav_link?.href;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const onAddDocument = ({
 | 
			
		||||
    stored_object,
 | 
			
		||||
    stored_object_version,
 | 
			
		||||
    file_name,
 | 
			
		||||
  stored_object,
 | 
			
		||||
  stored_object_version,
 | 
			
		||||
  file_name,
 | 
			
		||||
}: {
 | 
			
		||||
    stored_object: StoredObject;
 | 
			
		||||
    stored_object_version: StoredObjectVersion;
 | 
			
		||||
    file_name: string;
 | 
			
		||||
  stored_object: StoredObject;
 | 
			
		||||
  stored_object_version: StoredObjectVersion;
 | 
			
		||||
  file_name: string;
 | 
			
		||||
}): void => {
 | 
			
		||||
    emit("addDocument", { stored_object, stored_object_version, file_name });
 | 
			
		||||
  emit("addDocument", { stored_object, stored_object_version, file_name });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onRemoveDocument = (e: Event): void => {
 | 
			
		||||
    e.stopPropagation();
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    emit("removeDocument");
 | 
			
		||||
  e.stopPropagation();
 | 
			
		||||
  e.preventDefault();
 | 
			
		||||
  emit("removeDocument");
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
    <div>
 | 
			
		||||
        <drop-file
 | 
			
		||||
            :existingDoc="props.existingDoc"
 | 
			
		||||
            @addDocument="onAddDocument"
 | 
			
		||||
        ></drop-file>
 | 
			
		||||
  <div>
 | 
			
		||||
    <drop-file
 | 
			
		||||
      :existingDoc="props.existingDoc"
 | 
			
		||||
      @addDocument="onAddDocument"
 | 
			
		||||
    ></drop-file>
 | 
			
		||||
 | 
			
		||||
        <ul class="record_actions">
 | 
			
		||||
            <li v-if="has_existing_doc">
 | 
			
		||||
                <document-action-buttons-group
 | 
			
		||||
                    :stored-object="props.existingDoc"
 | 
			
		||||
                    :can-edit="props.existingDoc?.status === 'ready'"
 | 
			
		||||
                    :can-download="true"
 | 
			
		||||
                    :dav-link="dav_link_href"
 | 
			
		||||
                    :dav-link-expiration="dav_link_expiration"
 | 
			
		||||
                />
 | 
			
		||||
            </li>
 | 
			
		||||
            <li>
 | 
			
		||||
                <button
 | 
			
		||||
                    v-if="allowRemove"
 | 
			
		||||
                    class="btn btn-delete"
 | 
			
		||||
                    @click="onRemoveDocument($event)"
 | 
			
		||||
                ></button>
 | 
			
		||||
            </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
    <ul class="record_actions">
 | 
			
		||||
      <li v-if="has_existing_doc">
 | 
			
		||||
        <document-action-buttons-group
 | 
			
		||||
          :stored-object="props.existingDoc"
 | 
			
		||||
          :can-edit="props.existingDoc?.status === 'ready'"
 | 
			
		||||
          :can-download="true"
 | 
			
		||||
          :dav-link="dav_link_href"
 | 
			
		||||
          :dav-link-expiration="dav_link_expiration"
 | 
			
		||||
        />
 | 
			
		||||
      </li>
 | 
			
		||||
      <li>
 | 
			
		||||
        <button
 | 
			
		||||
          v-if="allowRemove"
 | 
			
		||||
          class="btn btn-delete"
 | 
			
		||||
          @click="onRemoveDocument($event)"
 | 
			
		||||
        ></button>
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,46 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
interface FileIconConfig {
 | 
			
		||||
    type: string;
 | 
			
		||||
  type: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<FileIconConfig>();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
    <i class="fa fa-file-pdf-o" v-if="props.type === 'application/pdf'"></i>
 | 
			
		||||
    <i
 | 
			
		||||
        class="fa fa-file-word-o"
 | 
			
		||||
        v-else-if="props.type === 'application/vnd.oasis.opendocument.text'"
 | 
			
		||||
    ></i>
 | 
			
		||||
    <i
 | 
			
		||||
        class="fa fa-file-word-o"
 | 
			
		||||
        v-else-if="
 | 
			
		||||
            props.type ===
 | 
			
		||||
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
 | 
			
		||||
        "
 | 
			
		||||
    ></i>
 | 
			
		||||
    <i
 | 
			
		||||
        class="fa fa-file-word-o"
 | 
			
		||||
        v-else-if="props.type === 'application/msword'"
 | 
			
		||||
    ></i>
 | 
			
		||||
    <i
 | 
			
		||||
        class="fa fa-file-excel-o"
 | 
			
		||||
        v-else-if="
 | 
			
		||||
            props.type ===
 | 
			
		||||
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
 | 
			
		||||
        "
 | 
			
		||||
    ></i>
 | 
			
		||||
    <i
 | 
			
		||||
        class="fa fa-file-excel-o"
 | 
			
		||||
        v-else-if="props.type === 'application/vnd.ms-excel'"
 | 
			
		||||
    ></i>
 | 
			
		||||
    <i class="fa fa-file-image-o" v-else-if="props.type === 'image/jpeg'"></i>
 | 
			
		||||
    <i class="fa fa-file-image-o" v-else-if="props.type === 'image/png'"></i>
 | 
			
		||||
    <i
 | 
			
		||||
        class="fa fa-file-archive-o"
 | 
			
		||||
        v-else-if="props.type === 'application/x-zip-compressed'"
 | 
			
		||||
    ></i>
 | 
			
		||||
    <i class="fa fa-file-code-o" v-else></i>
 | 
			
		||||
  <i class="fa fa-file-pdf-o" v-if="props.type === 'application/pdf'"></i>
 | 
			
		||||
  <i
 | 
			
		||||
    class="fa fa-file-word-o"
 | 
			
		||||
    v-else-if="props.type === 'application/vnd.oasis.opendocument.text'"
 | 
			
		||||
  ></i>
 | 
			
		||||
  <i
 | 
			
		||||
    class="fa fa-file-word-o"
 | 
			
		||||
    v-else-if="
 | 
			
		||||
      props.type ===
 | 
			
		||||
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
 | 
			
		||||
    "
 | 
			
		||||
  ></i>
 | 
			
		||||
  <i
 | 
			
		||||
    class="fa fa-file-word-o"
 | 
			
		||||
    v-else-if="props.type === 'application/msword'"
 | 
			
		||||
  ></i>
 | 
			
		||||
  <i
 | 
			
		||||
    class="fa fa-file-excel-o"
 | 
			
		||||
    v-else-if="
 | 
			
		||||
      props.type ===
 | 
			
		||||
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
 | 
			
		||||
    "
 | 
			
		||||
  ></i>
 | 
			
		||||
  <i
 | 
			
		||||
    class="fa fa-file-excel-o"
 | 
			
		||||
    v-else-if="props.type === 'application/vnd.ms-excel'"
 | 
			
		||||
  ></i>
 | 
			
		||||
  <i class="fa fa-file-image-o" v-else-if="props.type === 'image/jpeg'"></i>
 | 
			
		||||
  <i class="fa fa-file-image-o" v-else-if="props.type === 'image/png'"></i>
 | 
			
		||||
  <i
 | 
			
		||||
    class="fa fa-file-archive-o"
 | 
			
		||||
    v-else-if="props.type === 'application/x-zip-compressed'"
 | 
			
		||||
  ></i>
 | 
			
		||||
  <i class="fa fa-file-code-o" v-else></i>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,28 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <a :class="props.classes" @click="download_and_open($event)" ref="btn">
 | 
			
		||||
        <i class="fa fa-file-pdf-o"></i>
 | 
			
		||||
        Télécharger en pdf
 | 
			
		||||
    </a>
 | 
			
		||||
  <a :class="props.classes" @click="download_and_open($event)" ref="btn">
 | 
			
		||||
    <i class="fa fa-file-pdf-o"></i>
 | 
			
		||||
    Télécharger en pdf
 | 
			
		||||
  </a>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import {
 | 
			
		||||
    build_convert_link,
 | 
			
		||||
    download_and_decrypt_doc,
 | 
			
		||||
    download_doc,
 | 
			
		||||
  build_convert_link,
 | 
			
		||||
  download_and_decrypt_doc,
 | 
			
		||||
  download_doc,
 | 
			
		||||
} from "./helpers";
 | 
			
		||||
import mime from "mime";
 | 
			
		||||
import { reactive, ref } from "vue";
 | 
			
		||||
import { StoredObject } from "../../types";
 | 
			
		||||
 | 
			
		||||
interface ConvertButtonConfig {
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
    classes: Record<string, boolean>;
 | 
			
		||||
    filename?: string;
 | 
			
		||||
  storedObject: StoredObject;
 | 
			
		||||
  classes: Record<string, boolean>;
 | 
			
		||||
  filename?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface DownloadButtonState {
 | 
			
		||||
    content: null | string;
 | 
			
		||||
  content: null | string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<ConvertButtonConfig>();
 | 
			
		||||
@@ -30,36 +30,34 @@ const state: DownloadButtonState = reactive({ content: null });
 | 
			
		||||
const btn = ref<HTMLAnchorElement | null>(null);
 | 
			
		||||
 | 
			
		||||
async function download_and_open(event: Event): Promise<void> {
 | 
			
		||||
    const button = event.target as HTMLAnchorElement;
 | 
			
		||||
  const button = event.target as HTMLAnchorElement;
 | 
			
		||||
 | 
			
		||||
    if (null === state.content) {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
  if (null === state.content) {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
 | 
			
		||||
        const raw = await download_doc(
 | 
			
		||||
            build_convert_link(props.storedObject.uuid),
 | 
			
		||||
        );
 | 
			
		||||
        state.content = window.URL.createObjectURL(raw);
 | 
			
		||||
    const raw = await download_doc(build_convert_link(props.storedObject.uuid));
 | 
			
		||||
    state.content = window.URL.createObjectURL(raw);
 | 
			
		||||
 | 
			
		||||
        button.href = window.URL.createObjectURL(raw);
 | 
			
		||||
        button.type = "application/pdf";
 | 
			
		||||
    button.href = window.URL.createObjectURL(raw);
 | 
			
		||||
    button.type = "application/pdf";
 | 
			
		||||
 | 
			
		||||
        button.download = props.filename + ".pdf" || "document.pdf";
 | 
			
		||||
    }
 | 
			
		||||
    button.download = props.filename + ".pdf" || "document.pdf";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    button.click();
 | 
			
		||||
    const reset_pending = setTimeout(reset_state, 45000);
 | 
			
		||||
  button.click();
 | 
			
		||||
  const reset_pending = setTimeout(reset_state, 45000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function reset_state(): void {
 | 
			
		||||
    state.content = null;
 | 
			
		||||
    btn.value?.removeAttribute("download");
 | 
			
		||||
    btn.value?.removeAttribute("href");
 | 
			
		||||
    btn.value?.removeAttribute("type");
 | 
			
		||||
  state.content = null;
 | 
			
		||||
  btn.value?.removeAttribute("download");
 | 
			
		||||
  btn.value?.removeAttribute("href");
 | 
			
		||||
  btn.value?.removeAttribute("type");
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
i.fa::before {
 | 
			
		||||
    color: var(--bs-dropdown-link-hover-color);
 | 
			
		||||
  color: var(--bs-dropdown-link-hover-color);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,13 @@ import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
 | 
			
		||||
import { computed, reactive } from "vue";
 | 
			
		||||
 | 
			
		||||
export interface DesktopEditButtonConfig {
 | 
			
		||||
    editLink: null;
 | 
			
		||||
    classes: Record<string, boolean>;
 | 
			
		||||
    expirationLink: number | Date;
 | 
			
		||||
  editLink: null;
 | 
			
		||||
  classes: Record<string, boolean>;
 | 
			
		||||
  expirationLink: number | Date;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface DesktopEditButtonState {
 | 
			
		||||
    modalOpened: boolean;
 | 
			
		||||
  modalOpened: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const state: DesktopEditButtonState = reactive({ modalOpened: false });
 | 
			
		||||
@@ -17,80 +17,76 @@ const state: DesktopEditButtonState = reactive({ modalOpened: false });
 | 
			
		||||
const props = defineProps<DesktopEditButtonConfig>();
 | 
			
		||||
 | 
			
		||||
const buildCommand = computed<string>(
 | 
			
		||||
    () => "vnd.libreoffice.command:ofe|u|" + props.editLink,
 | 
			
		||||
  () => "vnd.libreoffice.command:ofe|u|" + props.editLink,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const editionUntilFormatted = computed<string>(() => {
 | 
			
		||||
    let d;
 | 
			
		||||
  let d;
 | 
			
		||||
 | 
			
		||||
    if (props.expirationLink instanceof Date) {
 | 
			
		||||
        d = props.expirationLink;
 | 
			
		||||
    } else {
 | 
			
		||||
        d = new Date(props.expirationLink * 1000);
 | 
			
		||||
    }
 | 
			
		||||
    console.log(props.expirationLink);
 | 
			
		||||
  if (props.expirationLink instanceof Date) {
 | 
			
		||||
    d = props.expirationLink;
 | 
			
		||||
  } else {
 | 
			
		||||
    d = new Date(props.expirationLink * 1000);
 | 
			
		||||
  }
 | 
			
		||||
  console.log(props.expirationLink);
 | 
			
		||||
 | 
			
		||||
    return new Intl.DateTimeFormat(undefined, {
 | 
			
		||||
        dateStyle: "long",
 | 
			
		||||
        timeStyle: "medium",
 | 
			
		||||
    }).format(d);
 | 
			
		||||
  return new Intl.DateTimeFormat(undefined, {
 | 
			
		||||
    dateStyle: "long",
 | 
			
		||||
    timeStyle: "medium",
 | 
			
		||||
  }).format(d);
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
    <teleport to="body">
 | 
			
		||||
        <modal v-if="state.modalOpened" @close="state.modalOpened = false">
 | 
			
		||||
            <template v-slot:body>
 | 
			
		||||
                <div class="desktop-edit">
 | 
			
		||||
                    <p class="center">
 | 
			
		||||
                        Veuillez enregistrer vos modifications avant le
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <strong>{{ editionUntilFormatted }}</strong>
 | 
			
		||||
                    </p>
 | 
			
		||||
  <teleport to="body">
 | 
			
		||||
    <modal v-if="state.modalOpened" @close="state.modalOpened = false">
 | 
			
		||||
      <template v-slot:body>
 | 
			
		||||
        <div class="desktop-edit">
 | 
			
		||||
          <p class="center">Veuillez enregistrer vos modifications avant le</p>
 | 
			
		||||
          <p>
 | 
			
		||||
            <strong>{{ editionUntilFormatted }}</strong>
 | 
			
		||||
          </p>
 | 
			
		||||
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <a class="btn btn-primary" :href="buildCommand"
 | 
			
		||||
                            >Ouvrir le document pour édition</a
 | 
			
		||||
                        >
 | 
			
		||||
                    </p>
 | 
			
		||||
          <p>
 | 
			
		||||
            <a class="btn btn-primary" :href="buildCommand"
 | 
			
		||||
              >Ouvrir le document pour édition</a
 | 
			
		||||
            >
 | 
			
		||||
          </p>
 | 
			
		||||
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <small
 | 
			
		||||
                            >Le document peut être édité uniquement en utilisant
 | 
			
		||||
                            Libre Office.</small
 | 
			
		||||
                        >
 | 
			
		||||
                    </p>
 | 
			
		||||
          <p>
 | 
			
		||||
            <small
 | 
			
		||||
              >Le document peut être édité uniquement en utilisant Libre
 | 
			
		||||
              Office.</small
 | 
			
		||||
            >
 | 
			
		||||
          </p>
 | 
			
		||||
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <small
 | 
			
		||||
                            >En cas d'échec lors de l'enregistrement, sauver le
 | 
			
		||||
                            document sur le poste de travail avant de le déposer
 | 
			
		||||
                            à nouveau ici.</small
 | 
			
		||||
                        >
 | 
			
		||||
                    </p>
 | 
			
		||||
          <p>
 | 
			
		||||
            <small
 | 
			
		||||
              >En cas d'échec lors de l'enregistrement, sauver le document sur
 | 
			
		||||
              le poste de travail avant de le déposer à nouveau ici.</small
 | 
			
		||||
            >
 | 
			
		||||
          </p>
 | 
			
		||||
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <small
 | 
			
		||||
                            >Vous pouvez naviguez sur d'autres pages pendant
 | 
			
		||||
                            l'édition.</small
 | 
			
		||||
                        >
 | 
			
		||||
                    </p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </template>
 | 
			
		||||
        </modal>
 | 
			
		||||
    </teleport>
 | 
			
		||||
    <a :class="props.classes" @click="state.modalOpened = true">
 | 
			
		||||
        <i class="fa fa-desktop"></i>
 | 
			
		||||
        Éditer sur le bureau
 | 
			
		||||
    </a>
 | 
			
		||||
          <p>
 | 
			
		||||
            <small
 | 
			
		||||
              >Vous pouvez naviguez sur d'autres pages pendant l'édition.</small
 | 
			
		||||
            >
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
    </modal>
 | 
			
		||||
  </teleport>
 | 
			
		||||
  <a :class="props.classes" @click="state.modalOpened = true">
 | 
			
		||||
    <i class="fa fa-desktop"></i>
 | 
			
		||||
    Éditer sur le bureau
 | 
			
		||||
  </a>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.desktop-edit {
 | 
			
		||||
    text-align: center;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
i.fa::before {
 | 
			
		||||
    color: var(--bs-dropdown-link-hover-color);
 | 
			
		||||
  color: var(--bs-dropdown-link-hover-color);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,26 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <a
 | 
			
		||||
        v-if="!state.is_ready"
 | 
			
		||||
        :class="props.classes"
 | 
			
		||||
        @click="download_and_open()"
 | 
			
		||||
        title="Télécharger"
 | 
			
		||||
    >
 | 
			
		||||
        <i class="fa fa-download"></i>
 | 
			
		||||
        <template v-if="displayActionStringInButton">Télécharger</template>
 | 
			
		||||
    </a>
 | 
			
		||||
    <a
 | 
			
		||||
        v-else
 | 
			
		||||
        :class="props.classes"
 | 
			
		||||
        target="_blank"
 | 
			
		||||
        :type="props.atVersion.type"
 | 
			
		||||
        :download="buildDocumentName()"
 | 
			
		||||
        :href="state.href_url"
 | 
			
		||||
        ref="open_button"
 | 
			
		||||
        title="Ouvrir"
 | 
			
		||||
    >
 | 
			
		||||
        <i class="fa fa-external-link"></i>
 | 
			
		||||
        <template v-if="displayActionStringInButton">Ouvrir</template>
 | 
			
		||||
    </a>
 | 
			
		||||
  <a
 | 
			
		||||
    v-if="!state.is_ready"
 | 
			
		||||
    :class="props.classes"
 | 
			
		||||
    @click="download_and_open()"
 | 
			
		||||
    title="Télécharger"
 | 
			
		||||
  >
 | 
			
		||||
    <i class="fa fa-download"></i>
 | 
			
		||||
    <template v-if="displayActionStringInButton">Télécharger</template>
 | 
			
		||||
  </a>
 | 
			
		||||
  <a
 | 
			
		||||
    v-else
 | 
			
		||||
    :class="props.classes"
 | 
			
		||||
    target="_blank"
 | 
			
		||||
    :type="props.atVersion.type"
 | 
			
		||||
    :download="buildDocumentName()"
 | 
			
		||||
    :href="state.href_url"
 | 
			
		||||
    ref="open_button"
 | 
			
		||||
    title="Ouvrir"
 | 
			
		||||
  >
 | 
			
		||||
    <i class="fa fa-external-link"></i>
 | 
			
		||||
    <template v-if="displayActionStringInButton">Ouvrir</template>
 | 
			
		||||
  </a>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
@@ -30,112 +30,109 @@ import mime from "mime";
 | 
			
		||||
import { StoredObject, StoredObjectVersion } from "../../types";
 | 
			
		||||
 | 
			
		||||
interface DownloadButtonConfig {
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
    atVersion: StoredObjectVersion;
 | 
			
		||||
    classes: Record<string, boolean>;
 | 
			
		||||
    filename?: string;
 | 
			
		||||
    /**
 | 
			
		||||
     * if true, display the action string into the button. If false, displays only
 | 
			
		||||
     * the icon
 | 
			
		||||
     */
 | 
			
		||||
    displayActionStringInButton?: boolean;
 | 
			
		||||
    /**
 | 
			
		||||
     * if true, will download directly the file on load
 | 
			
		||||
     */
 | 
			
		||||
    directDownload?: boolean;
 | 
			
		||||
  storedObject: StoredObject;
 | 
			
		||||
  atVersion: StoredObjectVersion;
 | 
			
		||||
  classes: Record<string, boolean>;
 | 
			
		||||
  filename?: string;
 | 
			
		||||
  /**
 | 
			
		||||
   * if true, display the action string into the button. If false, displays only
 | 
			
		||||
   * the icon
 | 
			
		||||
   */
 | 
			
		||||
  displayActionStringInButton?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * if true, will download directly the file on load
 | 
			
		||||
   */
 | 
			
		||||
  directDownload?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface DownloadButtonState {
 | 
			
		||||
    is_ready: boolean;
 | 
			
		||||
    is_running: boolean;
 | 
			
		||||
    href_url: string;
 | 
			
		||||
  is_ready: boolean;
 | 
			
		||||
  is_running: boolean;
 | 
			
		||||
  href_url: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<DownloadButtonConfig>(), {
 | 
			
		||||
    displayActionStringInButton: true,
 | 
			
		||||
    directDownload: false,
 | 
			
		||||
  displayActionStringInButton: true,
 | 
			
		||||
  directDownload: false,
 | 
			
		||||
});
 | 
			
		||||
const state: DownloadButtonState = reactive({
 | 
			
		||||
    is_ready: false,
 | 
			
		||||
    is_running: false,
 | 
			
		||||
    href_url: "#",
 | 
			
		||||
  is_ready: false,
 | 
			
		||||
  is_running: false,
 | 
			
		||||
  href_url: "#",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const open_button = ref<HTMLAnchorElement | null>(null);
 | 
			
		||||
 | 
			
		||||
function buildDocumentName(): string {
 | 
			
		||||
    let document_name = props.filename ?? props.storedObject.title;
 | 
			
		||||
  let document_name = props.filename ?? props.storedObject.title;
 | 
			
		||||
 | 
			
		||||
    if ("" === document_name || null === document_name) {
 | 
			
		||||
        document_name = "document";
 | 
			
		||||
    }
 | 
			
		||||
  if ("" === document_name || null === document_name) {
 | 
			
		||||
    document_name = "document";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const ext = mime.getExtension(props.atVersion.type);
 | 
			
		||||
  const ext = mime.getExtension(props.atVersion.type);
 | 
			
		||||
 | 
			
		||||
    if (null !== ext) {
 | 
			
		||||
        return document_name + "." + ext;
 | 
			
		||||
    }
 | 
			
		||||
  if (null !== ext) {
 | 
			
		||||
    return document_name + "." + ext;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return document_name;
 | 
			
		||||
  return document_name;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function download_and_open(): Promise<void> {
 | 
			
		||||
    if (state.is_running) {
 | 
			
		||||
        console.log("state is running, aborting");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
  if (state.is_running) {
 | 
			
		||||
    console.log("state is running, aborting");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    state.is_running = true;
 | 
			
		||||
  state.is_running = true;
 | 
			
		||||
 | 
			
		||||
    if (state.is_ready) {
 | 
			
		||||
        console.log("state is ready. This should not happens");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
  if (state.is_ready) {
 | 
			
		||||
    console.log("state is ready. This should not happens");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    let raw;
 | 
			
		||||
  let raw;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        raw = await download_and_decrypt_doc(
 | 
			
		||||
            props.storedObject,
 | 
			
		||||
            props.atVersion,
 | 
			
		||||
        );
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        console.error("error while downloading and decrypting document");
 | 
			
		||||
        console.error(e);
 | 
			
		||||
        throw e;
 | 
			
		||||
    }
 | 
			
		||||
  try {
 | 
			
		||||
    raw = await download_and_decrypt_doc(props.storedObject, props.atVersion);
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error("error while downloading and decrypting document");
 | 
			
		||||
    console.error(e);
 | 
			
		||||
    throw e;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    state.href_url = window.URL.createObjectURL(raw);
 | 
			
		||||
    state.is_running = false;
 | 
			
		||||
    state.is_ready = true;
 | 
			
		||||
  state.href_url = window.URL.createObjectURL(raw);
 | 
			
		||||
  state.is_running = false;
 | 
			
		||||
  state.is_ready = true;
 | 
			
		||||
 | 
			
		||||
    if (!props.directDownload) {
 | 
			
		||||
        await nextTick();
 | 
			
		||||
        open_button.value?.click();
 | 
			
		||||
  if (!props.directDownload) {
 | 
			
		||||
    await nextTick();
 | 
			
		||||
    open_button.value?.click();
 | 
			
		||||
 | 
			
		||||
        console.log("open button should have been clicked");
 | 
			
		||||
        setTimeout(reset_state, 45000);
 | 
			
		||||
    }
 | 
			
		||||
    console.log("open button should have been clicked");
 | 
			
		||||
    setTimeout(reset_state, 45000);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function reset_state(): void {
 | 
			
		||||
    state.href_url = "#";
 | 
			
		||||
    state.is_ready = false;
 | 
			
		||||
    state.is_running = false;
 | 
			
		||||
  state.href_url = "#";
 | 
			
		||||
  state.is_ready = false;
 | 
			
		||||
  state.is_running = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
    if (props.directDownload) {
 | 
			
		||||
        download_and_open();
 | 
			
		||||
    }
 | 
			
		||||
  if (props.directDownload) {
 | 
			
		||||
    download_and_open();
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
i.fa::before {
 | 
			
		||||
    color: var(--bs-dropdown-link-hover-color);
 | 
			
		||||
  color: var(--bs-dropdown-link-hover-color);
 | 
			
		||||
}
 | 
			
		||||
i.fa {
 | 
			
		||||
    margin-right: 0.5rem;
 | 
			
		||||
  margin-right: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,20 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import HistoryButtonModal from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonModal.vue";
 | 
			
		||||
import {
 | 
			
		||||
    StoredObject,
 | 
			
		||||
    StoredObjectVersionWithPointInTime,
 | 
			
		||||
  StoredObject,
 | 
			
		||||
  StoredObjectVersionWithPointInTime,
 | 
			
		||||
} from "./../../types";
 | 
			
		||||
import { computed, reactive, ref, useTemplateRef } from "vue";
 | 
			
		||||
import { get_versions } from "./HistoryButton/api";
 | 
			
		||||
 | 
			
		||||
interface HistoryButtonConfig {
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
    canEdit: boolean;
 | 
			
		||||
  storedObject: StoredObject;
 | 
			
		||||
  canEdit: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface HistoryButtonState {
 | 
			
		||||
    versions: StoredObjectVersionWithPointInTime[];
 | 
			
		||||
    loaded: boolean;
 | 
			
		||||
  versions: StoredObjectVersionWithPointInTime[];
 | 
			
		||||
  loaded: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<HistoryButtonConfig>();
 | 
			
		||||
@@ -22,47 +22,47 @@ const state = reactive<HistoryButtonState>({ versions: [], loaded: false });
 | 
			
		||||
const modal = useTemplateRef<typeof HistoryButtonModal>("modal");
 | 
			
		||||
 | 
			
		||||
const download_version_and_open_modal = async function (): Promise<void> {
 | 
			
		||||
    if (null !== modal.value) {
 | 
			
		||||
        modal.value.open();
 | 
			
		||||
    } else {
 | 
			
		||||
        console.log("modal is null");
 | 
			
		||||
    }
 | 
			
		||||
  if (null !== modal.value) {
 | 
			
		||||
    modal.value.open();
 | 
			
		||||
  } else {
 | 
			
		||||
    console.log("modal is null");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    if (!state.loaded) {
 | 
			
		||||
        const versions = await get_versions(props.storedObject);
 | 
			
		||||
  if (!state.loaded) {
 | 
			
		||||
    const versions = await get_versions(props.storedObject);
 | 
			
		||||
 | 
			
		||||
        for (const version of versions) {
 | 
			
		||||
            state.versions.push(version);
 | 
			
		||||
        }
 | 
			
		||||
        state.loaded = true;
 | 
			
		||||
    for (const version of versions) {
 | 
			
		||||
      state.versions.push(version);
 | 
			
		||||
    }
 | 
			
		||||
    state.loaded = true;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onRestoreVersion = ({
 | 
			
		||||
    newVersion,
 | 
			
		||||
  newVersion,
 | 
			
		||||
}: {
 | 
			
		||||
    newVersion: StoredObjectVersionWithPointInTime;
 | 
			
		||||
  newVersion: StoredObjectVersionWithPointInTime;
 | 
			
		||||
}) => {
 | 
			
		||||
    state.versions.unshift(newVersion);
 | 
			
		||||
  state.versions.unshift(newVersion);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
    <a @click="download_version_and_open_modal" class="dropdown-item">
 | 
			
		||||
        <history-button-modal
 | 
			
		||||
            ref="modal"
 | 
			
		||||
            :versions="state.versions"
 | 
			
		||||
            :stored-object="storedObject"
 | 
			
		||||
            :can-edit="canEdit"
 | 
			
		||||
            @restore-version="onRestoreVersion"
 | 
			
		||||
        ></history-button-modal>
 | 
			
		||||
        <i class="fa fa-history"></i>
 | 
			
		||||
        Historique
 | 
			
		||||
    </a>
 | 
			
		||||
  <a @click="download_version_and_open_modal" class="dropdown-item">
 | 
			
		||||
    <history-button-modal
 | 
			
		||||
      ref="modal"
 | 
			
		||||
      :versions="state.versions"
 | 
			
		||||
      :stored-object="storedObject"
 | 
			
		||||
      :can-edit="canEdit"
 | 
			
		||||
      @restore-version="onRestoreVersion"
 | 
			
		||||
    ></history-button-modal>
 | 
			
		||||
    <i class="fa fa-history"></i>
 | 
			
		||||
    Historique
 | 
			
		||||
  </a>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
i.fa::before {
 | 
			
		||||
    color: var(--bs-dropdown-link-hover-color);
 | 
			
		||||
  color: var(--bs-dropdown-link-hover-color);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,26 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import {
 | 
			
		||||
    StoredObject,
 | 
			
		||||
    StoredObjectVersionWithPointInTime,
 | 
			
		||||
  StoredObject,
 | 
			
		||||
  StoredObjectVersionWithPointInTime,
 | 
			
		||||
} from "./../../../types";
 | 
			
		||||
import HistoryButtonListItem from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonListItem.vue";
 | 
			
		||||
import { computed, reactive } from "vue";
 | 
			
		||||
 | 
			
		||||
interface HistoryButtonListConfig {
 | 
			
		||||
    versions: StoredObjectVersionWithPointInTime[];
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
    canEdit: boolean;
 | 
			
		||||
  versions: StoredObjectVersionWithPointInTime[];
 | 
			
		||||
  storedObject: StoredObject;
 | 
			
		||||
  canEdit: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
    restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
 | 
			
		||||
  restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
interface HistoryButtonListState {
 | 
			
		||||
    /**
 | 
			
		||||
     * Contains the number of the newly created version when a version is restored.
 | 
			
		||||
     */
 | 
			
		||||
    restored: number;
 | 
			
		||||
  /**
 | 
			
		||||
   * Contains the number of the newly created version when a version is restored.
 | 
			
		||||
   */
 | 
			
		||||
  restored: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<HistoryButtonListConfig>();
 | 
			
		||||
@@ -28,11 +28,11 @@ const props = defineProps<HistoryButtonListConfig>();
 | 
			
		||||
const state = reactive<HistoryButtonListState>({ restored: -1 });
 | 
			
		||||
 | 
			
		||||
const higher_version = computed<number>(() =>
 | 
			
		||||
    props.versions.reduce(
 | 
			
		||||
        (accumulator: number, version: StoredObjectVersionWithPointInTime) =>
 | 
			
		||||
            Math.max(accumulator, version.version),
 | 
			
		||||
        -1,
 | 
			
		||||
    ),
 | 
			
		||||
  props.versions.reduce(
 | 
			
		||||
    (accumulator: number, version: StoredObjectVersionWithPointInTime) =>
 | 
			
		||||
      Math.max(accumulator, version.version),
 | 
			
		||||
    -1,
 | 
			
		||||
  ),
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -41,32 +41,32 @@ const higher_version = computed<number>(() =>
 | 
			
		||||
 * internally, keep track of the newly restored version
 | 
			
		||||
 */
 | 
			
		||||
const onRestored = ({
 | 
			
		||||
    newVersion,
 | 
			
		||||
  newVersion,
 | 
			
		||||
}: {
 | 
			
		||||
    newVersion: StoredObjectVersionWithPointInTime;
 | 
			
		||||
  newVersion: StoredObjectVersionWithPointInTime;
 | 
			
		||||
}) => {
 | 
			
		||||
    state.restored = newVersion.version;
 | 
			
		||||
    emit("restoreVersion", { newVersion });
 | 
			
		||||
  state.restored = newVersion.version;
 | 
			
		||||
  emit("restoreVersion", { newVersion });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
    <template v-if="props.versions.length > 0">
 | 
			
		||||
        <div class="container">
 | 
			
		||||
            <template v-for="v in props.versions" :key="v.id">
 | 
			
		||||
                <history-button-list-item
 | 
			
		||||
                    :version="v"
 | 
			
		||||
                    :can-edit="canEdit"
 | 
			
		||||
                    :is-current="higher_version === v.version"
 | 
			
		||||
                    :stored-object="storedObject"
 | 
			
		||||
                    @restore-version="onRestored"
 | 
			
		||||
                ></history-button-list-item>
 | 
			
		||||
            </template>
 | 
			
		||||
        </div>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template v-else>
 | 
			
		||||
        <p>Chargement des versions</p>
 | 
			
		||||
    </template>
 | 
			
		||||
  <template v-if="props.versions.length > 0">
 | 
			
		||||
    <div class="container">
 | 
			
		||||
      <template v-for="v in props.versions" :key="v.id">
 | 
			
		||||
        <history-button-list-item
 | 
			
		||||
          :version="v"
 | 
			
		||||
          :can-edit="canEdit"
 | 
			
		||||
          :is-current="higher_version === v.version"
 | 
			
		||||
          :stored-object="storedObject"
 | 
			
		||||
          @restore-version="onRestored"
 | 
			
		||||
        ></history-button-list-item>
 | 
			
		||||
      </template>
 | 
			
		||||
    </div>
 | 
			
		||||
  </template>
 | 
			
		||||
  <template v-else>
 | 
			
		||||
    <p>Chargement des versions</p>
 | 
			
		||||
  </template>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,196 +1,184 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import {
 | 
			
		||||
    StoredObject,
 | 
			
		||||
    StoredObjectPointInTime,
 | 
			
		||||
    StoredObjectVersionWithPointInTime,
 | 
			
		||||
} from "ChillDocStoreAssets/types";
 | 
			
		||||
  StoredObject,
 | 
			
		||||
  StoredObjectPointInTime,
 | 
			
		||||
  StoredObjectVersionWithPointInTime,
 | 
			
		||||
} from "./../../../types";
 | 
			
		||||
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
 | 
			
		||||
import { ISOToDatetime } from "ChillMainAssets/chill/js/date";
 | 
			
		||||
import { ISOToDatetime } from "./../../../../../../ChillMainBundle/Resources/public/chill/js/date";
 | 
			
		||||
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
 | 
			
		||||
import RestoreVersionButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/RestoreVersionButton.vue";
 | 
			
		||||
import DownloadButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DownloadButton.vue";
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
 | 
			
		||||
interface HistoryButtonListItemConfig {
 | 
			
		||||
    version: StoredObjectVersionWithPointInTime;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
    canEdit: boolean;
 | 
			
		||||
    isCurrent: boolean;
 | 
			
		||||
  version: StoredObjectVersionWithPointInTime;
 | 
			
		||||
  storedObject: StoredObject;
 | 
			
		||||
  canEdit: boolean;
 | 
			
		||||
  isCurrent: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
    restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
 | 
			
		||||
  restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const props = defineProps<HistoryButtonListItemConfig>();
 | 
			
		||||
 | 
			
		||||
const onRestore = ({
 | 
			
		||||
    newVersion,
 | 
			
		||||
  newVersion,
 | 
			
		||||
}: {
 | 
			
		||||
    newVersion: StoredObjectVersionWithPointInTime;
 | 
			
		||||
  newVersion: StoredObjectVersionWithPointInTime;
 | 
			
		||||
}) => {
 | 
			
		||||
    emit("restoreVersion", { newVersion });
 | 
			
		||||
  emit("restoreVersion", { newVersion });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isKeptBeforeConversion = computed<boolean>(() => {
 | 
			
		||||
    if ("point-in-times" in props.version) {
 | 
			
		||||
        return props.version["point-in-times"].reduce(
 | 
			
		||||
            (accumulator: boolean, pit: StoredObjectPointInTime) =>
 | 
			
		||||
                accumulator || "keep-before-conversion" === pit.reason,
 | 
			
		||||
            false,
 | 
			
		||||
        );
 | 
			
		||||
    } else {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
  if ("point-in-times" in props.version) {
 | 
			
		||||
    return props.version["point-in-times"].reduce(
 | 
			
		||||
      (accumulator: boolean, pit: StoredObjectPointInTime) =>
 | 
			
		||||
        accumulator || "keep-before-conversion" === pit.reason,
 | 
			
		||||
      false,
 | 
			
		||||
    );
 | 
			
		||||
  } else {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const isRestored = computed<boolean>(
 | 
			
		||||
    () => props.version.version > 0 && null !== props.version["from-restored"],
 | 
			
		||||
  () => props.version.version > 0 && null !== props.version["from-restored"],
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const isDuplicated = computed<boolean>(
 | 
			
		||||
    () =>
 | 
			
		||||
        props.version.version === 0 && null !== props.version["from-restored"],
 | 
			
		||||
  () => props.version.version === 0 && null !== props.version["from-restored"],
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const classes = computed<{
 | 
			
		||||
    row: true;
 | 
			
		||||
    "row-hover": true;
 | 
			
		||||
    "blinking-1": boolean;
 | 
			
		||||
    "blinking-2": boolean;
 | 
			
		||||
  row: true;
 | 
			
		||||
  "row-hover": true;
 | 
			
		||||
  "blinking-1": boolean;
 | 
			
		||||
  "blinking-2": boolean;
 | 
			
		||||
}>(() => ({
 | 
			
		||||
    row: true,
 | 
			
		||||
    "row-hover": true,
 | 
			
		||||
    "blinking-1": props.isRestored && 0 === props.version.version % 2,
 | 
			
		||||
    "blinking-2": props.isRestored && 1 === props.version.version % 2,
 | 
			
		||||
  row: true,
 | 
			
		||||
  "row-hover": true,
 | 
			
		||||
  "blinking-1": props.isRestored && 0 === props.version.version % 2,
 | 
			
		||||
  "blinking-2": props.isRestored && 1 === props.version.version % 2,
 | 
			
		||||
}));
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
    <div :class="classes">
 | 
			
		||||
        <div
 | 
			
		||||
            class="col-12 tags"
 | 
			
		||||
            v-if="
 | 
			
		||||
                isCurrent ||
 | 
			
		||||
                isKeptBeforeConversion ||
 | 
			
		||||
                isRestored ||
 | 
			
		||||
                isDuplicated
 | 
			
		||||
            "
 | 
			
		||||
        >
 | 
			
		||||
            <span class="badge bg-success" v-if="isCurrent"
 | 
			
		||||
                >Version actuelle</span
 | 
			
		||||
            >
 | 
			
		||||
            <span class="badge bg-info" v-if="isKeptBeforeConversion"
 | 
			
		||||
                >Conservée avant conversion dans un autre format</span
 | 
			
		||||
            >
 | 
			
		||||
            <span class="badge bg-info" v-if="isRestored"
 | 
			
		||||
                >Restaurée depuis la version
 | 
			
		||||
                {{ version["from-restored"]?.version + 1 }}</span
 | 
			
		||||
            >
 | 
			
		||||
            <span class="badge bg-info" v-if="isDuplicated"
 | 
			
		||||
                >Dupliqué depuis un autre document</span
 | 
			
		||||
            >
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <file-icon :type="version.type"></file-icon>
 | 
			
		||||
            <span
 | 
			
		||||
                ><strong> #{{ version.version + 1 }} </strong></span
 | 
			
		||||
            >
 | 
			
		||||
            <template
 | 
			
		||||
                v-if="version.createdBy !== null && version.createdAt !== null"
 | 
			
		||||
                ><strong v-if="version.version == 0">créé par</strong
 | 
			
		||||
                ><strong v-else>modifié par</strong>
 | 
			
		||||
                <span class="badge-user"
 | 
			
		||||
                    ><UserRenderBoxBadge
 | 
			
		||||
                        :user="version.createdBy"
 | 
			
		||||
                    ></UserRenderBoxBadge
 | 
			
		||||
                ></span>
 | 
			
		||||
                <strong>à</strong>
 | 
			
		||||
                {{
 | 
			
		||||
                    $d(ISOToDatetime(version.createdAt.datetime8601), "long")
 | 
			
		||||
                }}</template
 | 
			
		||||
            ><template
 | 
			
		||||
                v-if="version.createdBy === null && version.createdAt !== null"
 | 
			
		||||
                ><strong v-if="version.version == 0">Créé le</strong
 | 
			
		||||
                ><strong v-else>modifié le</strong>
 | 
			
		||||
                {{
 | 
			
		||||
                    $d(ISOToDatetime(version.createdAt.datetime8601), "long")
 | 
			
		||||
                }}</template
 | 
			
		||||
            >
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="col-12">
 | 
			
		||||
            <ul class="record_actions small slim on-version-actions">
 | 
			
		||||
                <li v-if="canEdit && !isCurrent">
 | 
			
		||||
                    <restore-version-button
 | 
			
		||||
                        :stored-object-version="props.version"
 | 
			
		||||
                        @restore-version="onRestore"
 | 
			
		||||
                    ></restore-version-button>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li>
 | 
			
		||||
                    <download-button
 | 
			
		||||
                        :stored-object="storedObject"
 | 
			
		||||
                        :at-version="version"
 | 
			
		||||
                        :classes="{
 | 
			
		||||
                            btn: true,
 | 
			
		||||
                            'btn-outline-primary': true,
 | 
			
		||||
                            'btn-sm': true,
 | 
			
		||||
                        }"
 | 
			
		||||
                        :display-action-string-in-button="false"
 | 
			
		||||
                    ></download-button>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
  <div :class="classes">
 | 
			
		||||
    <div
 | 
			
		||||
      class="col-12 tags"
 | 
			
		||||
      v-if="isCurrent || isKeptBeforeConversion || isRestored || isDuplicated"
 | 
			
		||||
    >
 | 
			
		||||
      <span class="badge bg-success" v-if="isCurrent">Version actuelle</span>
 | 
			
		||||
      <span class="badge bg-info" v-if="isKeptBeforeConversion"
 | 
			
		||||
        >Conservée avant conversion dans un autre format</span
 | 
			
		||||
      >
 | 
			
		||||
      <span class="badge bg-info" v-if="isRestored"
 | 
			
		||||
        >Restaurée depuis la version
 | 
			
		||||
        {{ version["from-restored"]?.version + 1 }}</span
 | 
			
		||||
      >
 | 
			
		||||
      <span class="badge bg-info" v-if="isDuplicated"
 | 
			
		||||
        >Dupliqué depuis un autre document</span
 | 
			
		||||
      >
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col-12">
 | 
			
		||||
      <file-icon :type="version.type"></file-icon>
 | 
			
		||||
      <span
 | 
			
		||||
        ><strong> #{{ version.version + 1 }} </strong></span
 | 
			
		||||
      >
 | 
			
		||||
      <template v-if="version.createdBy !== null && version.createdAt !== null"
 | 
			
		||||
        ><strong v-if="version.version == 0">créé par</strong
 | 
			
		||||
        ><strong v-else>modifié par</strong>
 | 
			
		||||
        <span class="badge-user"
 | 
			
		||||
          ><UserRenderBoxBadge :user="version.createdBy"></UserRenderBoxBadge
 | 
			
		||||
        ></span>
 | 
			
		||||
        <strong>à</strong>
 | 
			
		||||
        {{
 | 
			
		||||
          $d(ISOToDatetime(version.createdAt.datetime8601), "long")
 | 
			
		||||
        }}</template
 | 
			
		||||
      ><template v-if="version.createdBy === null && version.createdAt !== null"
 | 
			
		||||
        ><strong v-if="version.version == 0">Créé le</strong
 | 
			
		||||
        ><strong v-else>modifié le</strong>
 | 
			
		||||
        {{
 | 
			
		||||
          $d(ISOToDatetime(version.createdAt.datetime8601), "long")
 | 
			
		||||
        }}</template
 | 
			
		||||
      >
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col-12">
 | 
			
		||||
      <ul class="record_actions small slim on-version-actions">
 | 
			
		||||
        <li v-if="canEdit && !isCurrent">
 | 
			
		||||
          <restore-version-button
 | 
			
		||||
            :stored-object-version="props.version"
 | 
			
		||||
            @restore-version="onRestore"
 | 
			
		||||
          ></restore-version-button>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <download-button
 | 
			
		||||
            :stored-object="storedObject"
 | 
			
		||||
            :at-version="version"
 | 
			
		||||
            :classes="{
 | 
			
		||||
              btn: true,
 | 
			
		||||
              'btn-outline-primary': true,
 | 
			
		||||
              'btn-sm': true,
 | 
			
		||||
            }"
 | 
			
		||||
            :display-action-string-in-button="false"
 | 
			
		||||
          ></download-button>
 | 
			
		||||
        </li>
 | 
			
		||||
      </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
div.tags {
 | 
			
		||||
    span.badge:not(:last-child) {
 | 
			
		||||
        margin-right: 0.5rem;
 | 
			
		||||
    }
 | 
			
		||||
  span.badge:not(:last-child) {
 | 
			
		||||
    margin-right: 0.5rem;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// to make the animation restart, we have the same animation twice,
 | 
			
		||||
// and alternate between both
 | 
			
		||||
.blinking-1 {
 | 
			
		||||
    animation-name: backgroundColorPalette-1;
 | 
			
		||||
    animation-duration: 8s;
 | 
			
		||||
    animation-iteration-count: 1;
 | 
			
		||||
    animation-direction: normal;
 | 
			
		||||
    animation-timing-function: linear;
 | 
			
		||||
  animation-name: backgroundColorPalette-1;
 | 
			
		||||
  animation-duration: 8s;
 | 
			
		||||
  animation-iteration-count: 1;
 | 
			
		||||
  animation-direction: normal;
 | 
			
		||||
  animation-timing-function: linear;
 | 
			
		||||
}
 | 
			
		||||
@keyframes backgroundColorPalette-1 {
 | 
			
		||||
    0% {
 | 
			
		||||
        background: var(--bs-chill-green-dark);
 | 
			
		||||
    }
 | 
			
		||||
    25% {
 | 
			
		||||
        background: var(--bs-chill-green);
 | 
			
		||||
    }
 | 
			
		||||
    65% {
 | 
			
		||||
        background: var(--bs-chill-beige);
 | 
			
		||||
    }
 | 
			
		||||
    100% {
 | 
			
		||||
        background: unset;
 | 
			
		||||
    }
 | 
			
		||||
  0% {
 | 
			
		||||
    background: var(--bs-chill-green-dark);
 | 
			
		||||
  }
 | 
			
		||||
  25% {
 | 
			
		||||
    background: var(--bs-chill-green);
 | 
			
		||||
  }
 | 
			
		||||
  65% {
 | 
			
		||||
    background: var(--bs-chill-beige);
 | 
			
		||||
  }
 | 
			
		||||
  100% {
 | 
			
		||||
    background: unset;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.blinking-2 {
 | 
			
		||||
    animation-name: backgroundColorPalette-2;
 | 
			
		||||
    animation-duration: 8s;
 | 
			
		||||
    animation-iteration-count: 1;
 | 
			
		||||
    animation-direction: normal;
 | 
			
		||||
    animation-timing-function: linear;
 | 
			
		||||
  animation-name: backgroundColorPalette-2;
 | 
			
		||||
  animation-duration: 8s;
 | 
			
		||||
  animation-iteration-count: 1;
 | 
			
		||||
  animation-direction: normal;
 | 
			
		||||
  animation-timing-function: linear;
 | 
			
		||||
}
 | 
			
		||||
@keyframes backgroundColorPalette-2 {
 | 
			
		||||
    0% {
 | 
			
		||||
        background: var(--bs-chill-green-dark);
 | 
			
		||||
    }
 | 
			
		||||
    25% {
 | 
			
		||||
        background: var(--bs-chill-green);
 | 
			
		||||
    }
 | 
			
		||||
    65% {
 | 
			
		||||
        background: var(--bs-chill-beige);
 | 
			
		||||
    }
 | 
			
		||||
    100% {
 | 
			
		||||
        background: unset;
 | 
			
		||||
    }
 | 
			
		||||
  0% {
 | 
			
		||||
    background: var(--bs-chill-green-dark);
 | 
			
		||||
  }
 | 
			
		||||
  25% {
 | 
			
		||||
    background: var(--bs-chill-green);
 | 
			
		||||
  }
 | 
			
		||||
  65% {
 | 
			
		||||
    background: var(--bs-chill-beige);
 | 
			
		||||
  }
 | 
			
		||||
  100% {
 | 
			
		||||
    background: unset;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,54 +3,54 @@ import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
 | 
			
		||||
import { reactive } from "vue";
 | 
			
		||||
import HistoryButtonList from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue";
 | 
			
		||||
import {
 | 
			
		||||
    StoredObject,
 | 
			
		||||
    StoredObjectVersionWithPointInTime,
 | 
			
		||||
  StoredObject,
 | 
			
		||||
  StoredObjectVersionWithPointInTime,
 | 
			
		||||
} from "./../../../types";
 | 
			
		||||
 | 
			
		||||
interface HistoryButtonListConfig {
 | 
			
		||||
    versions: StoredObjectVersionWithPointInTime[];
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
    canEdit: boolean;
 | 
			
		||||
  versions: StoredObjectVersionWithPointInTime[];
 | 
			
		||||
  storedObject: StoredObject;
 | 
			
		||||
  canEdit: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
    restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
 | 
			
		||||
  restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
interface HistoryButtonModalState {
 | 
			
		||||
    opened: boolean;
 | 
			
		||||
  opened: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<HistoryButtonListConfig>();
 | 
			
		||||
const state = reactive<HistoryButtonModalState>({ opened: false });
 | 
			
		||||
 | 
			
		||||
const open = () => {
 | 
			
		||||
    state.opened = true;
 | 
			
		||||
  state.opened = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onRestoreVersion = (payload: {
 | 
			
		||||
    newVersion: StoredObjectVersionWithPointInTime;
 | 
			
		||||
  newVersion: StoredObjectVersionWithPointInTime;
 | 
			
		||||
}) => emit("restoreVersion", payload);
 | 
			
		||||
 | 
			
		||||
defineExpose({ open });
 | 
			
		||||
</script>
 | 
			
		||||
<template>
 | 
			
		||||
    <Teleport to="body">
 | 
			
		||||
        <modal v-if="state.opened" @close="state.opened = false">
 | 
			
		||||
            <template v-slot:header>
 | 
			
		||||
                <h3>Historique des versions du document</h3>
 | 
			
		||||
            </template>
 | 
			
		||||
            <template v-slot:body>
 | 
			
		||||
                <p>Les versions sont conservées pendant 90 jours.</p>
 | 
			
		||||
                <history-button-list
 | 
			
		||||
                    :versions="props.versions"
 | 
			
		||||
                    :can-edit="canEdit"
 | 
			
		||||
                    :stored-object="storedObject"
 | 
			
		||||
                    @restore-version="onRestoreVersion"
 | 
			
		||||
                ></history-button-list>
 | 
			
		||||
            </template>
 | 
			
		||||
        </modal>
 | 
			
		||||
    </Teleport>
 | 
			
		||||
  <Teleport to="body">
 | 
			
		||||
    <modal v-if="state.opened" @close="state.opened = false">
 | 
			
		||||
      <template v-slot:header>
 | 
			
		||||
        <h3>Historique des versions du document</h3>
 | 
			
		||||
      </template>
 | 
			
		||||
      <template v-slot:body>
 | 
			
		||||
        <p>Les versions sont conservées pendant 90 jours.</p>
 | 
			
		||||
        <history-button-list
 | 
			
		||||
          :versions="props.versions"
 | 
			
		||||
          :can-edit="canEdit"
 | 
			
		||||
          :stored-object="storedObject"
 | 
			
		||||
          @restore-version="onRestoreVersion"
 | 
			
		||||
        ></history-button-list>
 | 
			
		||||
      </template>
 | 
			
		||||
    </modal>
 | 
			
		||||
  </Teleport>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,17 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import {
 | 
			
		||||
    StoredObjectVersionPersisted,
 | 
			
		||||
    StoredObjectVersionWithPointInTime,
 | 
			
		||||
  StoredObjectVersionPersisted,
 | 
			
		||||
  StoredObjectVersionWithPointInTime,
 | 
			
		||||
} from "../../../types";
 | 
			
		||||
import { useToast } from "vue-toast-notification";
 | 
			
		||||
import { restore_version } from "./api";
 | 
			
		||||
 | 
			
		||||
interface RestoreVersionButtonProps {
 | 
			
		||||
    storedObjectVersion: StoredObjectVersionPersisted;
 | 
			
		||||
  storedObjectVersion: StoredObjectVersionPersisted;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const emit = defineEmits<{
 | 
			
		||||
    restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
 | 
			
		||||
  restoreVersion: [newVersion: StoredObjectVersionWithPointInTime];
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const props = defineProps<RestoreVersionButtonProps>();
 | 
			
		||||
@@ -19,21 +19,21 @@ const props = defineProps<RestoreVersionButtonProps>();
 | 
			
		||||
const $toast = useToast();
 | 
			
		||||
 | 
			
		||||
const restore_version_fn = async () => {
 | 
			
		||||
    const newVersion = await restore_version(props.storedObjectVersion);
 | 
			
		||||
  const newVersion = await restore_version(props.storedObjectVersion);
 | 
			
		||||
 | 
			
		||||
    $toast.success("Version restaurée");
 | 
			
		||||
    emit("restoreVersion", { newVersion });
 | 
			
		||||
  $toast.success("Version restaurée");
 | 
			
		||||
  emit("restoreVersion", { newVersion });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
    <button
 | 
			
		||||
        class="btn btn-outline-action"
 | 
			
		||||
        @click="restore_version_fn"
 | 
			
		||||
        title="Restaurer"
 | 
			
		||||
    >
 | 
			
		||||
        <i class="fa fa-rotate-left"></i> Restaurer
 | 
			
		||||
    </button>
 | 
			
		||||
  <button
 | 
			
		||||
    class="btn btn-outline-action"
 | 
			
		||||
    @click="restore_version_fn"
 | 
			
		||||
    title="Restaurer"
 | 
			
		||||
  >
 | 
			
		||||
    <i class="fa fa-rotate-left"></i> Restaurer
 | 
			
		||||
  </button>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss"></style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,33 @@
 | 
			
		||||
import {
 | 
			
		||||
    StoredObject,
 | 
			
		||||
    StoredObjectVersionPersisted,
 | 
			
		||||
    StoredObjectVersionWithPointInTime,
 | 
			
		||||
  StoredObject,
 | 
			
		||||
  StoredObjectVersionPersisted,
 | 
			
		||||
  StoredObjectVersionWithPointInTime,
 | 
			
		||||
} from "../../../types";
 | 
			
		||||
import {
 | 
			
		||||
    fetchResults,
 | 
			
		||||
    makeFetch,
 | 
			
		||||
  fetchResults,
 | 
			
		||||
  makeFetch,
 | 
			
		||||
} from "../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
 | 
			
		||||
 | 
			
		||||
export const get_versions = async (
 | 
			
		||||
    storedObject: StoredObject,
 | 
			
		||||
  storedObject: StoredObject,
 | 
			
		||||
): Promise<StoredObjectVersionWithPointInTime[]> => {
 | 
			
		||||
    const versions = await fetchResults<StoredObjectVersionWithPointInTime>(
 | 
			
		||||
        `/api/1.0/doc-store/stored-object/${storedObject.uuid}/versions`,
 | 
			
		||||
    );
 | 
			
		||||
  const versions = await fetchResults<StoredObjectVersionWithPointInTime>(
 | 
			
		||||
    `/api/1.0/doc-store/stored-object/${storedObject.uuid}/versions`,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    return versions.sort(
 | 
			
		||||
        (
 | 
			
		||||
            a: StoredObjectVersionWithPointInTime,
 | 
			
		||||
            b: StoredObjectVersionWithPointInTime,
 | 
			
		||||
        ) => b.version - a.version,
 | 
			
		||||
    );
 | 
			
		||||
  return versions.sort(
 | 
			
		||||
    (
 | 
			
		||||
      a: StoredObjectVersionWithPointInTime,
 | 
			
		||||
      b: StoredObjectVersionWithPointInTime,
 | 
			
		||||
    ) => b.version - a.version,
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const restore_version = async (
 | 
			
		||||
    version: StoredObjectVersionPersisted,
 | 
			
		||||
  version: StoredObjectVersionPersisted,
 | 
			
		||||
): Promise<StoredObjectVersionWithPointInTime> => {
 | 
			
		||||
    return await makeFetch<null, StoredObjectVersionWithPointInTime>(
 | 
			
		||||
        "POST",
 | 
			
		||||
        `/api/1.0/doc-store/stored-object/restore-from-version/${version.id}`,
 | 
			
		||||
    );
 | 
			
		||||
  return await makeFetch<null, StoredObjectVersionWithPointInTime>(
 | 
			
		||||
    "POST",
 | 
			
		||||
    `/api/1.0/doc-store/stored-object/restore-from-version/${version.id}`,
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,27 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <a
 | 
			
		||||
        :class="Object.assign(props.classes, { btn: true })"
 | 
			
		||||
        @click="beforeLeave($event)"
 | 
			
		||||
        :href="
 | 
			
		||||
            build_wopi_editor_link(props.storedObject.uuid, props.returnPath)
 | 
			
		||||
        "
 | 
			
		||||
    >
 | 
			
		||||
        <i class="fa fa-paragraph"></i>
 | 
			
		||||
        Editer en ligne
 | 
			
		||||
    </a>
 | 
			
		||||
  <a
 | 
			
		||||
    :class="Object.assign(props.classes, { btn: true })"
 | 
			
		||||
    @click="beforeLeave($event)"
 | 
			
		||||
    :href="build_wopi_editor_link(props.storedObject.uuid, props.returnPath)"
 | 
			
		||||
  >
 | 
			
		||||
    <i class="fa fa-paragraph"></i>
 | 
			
		||||
    Editer en ligne
 | 
			
		||||
  </a>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import WopiEditButton from "./WopiEditButton.vue";
 | 
			
		||||
import { build_wopi_editor_link } from "./helpers";
 | 
			
		||||
import {
 | 
			
		||||
    StoredObject,
 | 
			
		||||
    WopiEditButtonExecutableBeforeLeaveFunction,
 | 
			
		||||
  StoredObject,
 | 
			
		||||
  WopiEditButtonExecutableBeforeLeaveFunction,
 | 
			
		||||
} from "../../types";
 | 
			
		||||
 | 
			
		||||
interface WopiEditButtonConfig {
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
    returnPath?: string;
 | 
			
		||||
    classes: Record<string, boolean>;
 | 
			
		||||
    executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
 | 
			
		||||
  storedObject: StoredObject;
 | 
			
		||||
  returnPath?: string;
 | 
			
		||||
  classes: Record<string, boolean>;
 | 
			
		||||
  executeBeforeLeave?: WopiEditButtonExecutableBeforeLeaveFunction;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<WopiEditButtonConfig>();
 | 
			
		||||
@@ -31,24 +29,24 @@ const props = defineProps<WopiEditButtonConfig>();
 | 
			
		||||
let executed = false;
 | 
			
		||||
 | 
			
		||||
async function beforeLeave(event: Event): Promise<true> {
 | 
			
		||||
    if (props.executeBeforeLeave === undefined || executed === true) {
 | 
			
		||||
        return Promise.resolve(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
 | 
			
		||||
    await props.executeBeforeLeave();
 | 
			
		||||
    executed = true;
 | 
			
		||||
 | 
			
		||||
    const link = event.target as HTMLAnchorElement;
 | 
			
		||||
    link.click();
 | 
			
		||||
 | 
			
		||||
  if (props.executeBeforeLeave === undefined || executed === true) {
 | 
			
		||||
    return Promise.resolve(true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  event.preventDefault();
 | 
			
		||||
 | 
			
		||||
  await props.executeBeforeLeave();
 | 
			
		||||
  executed = true;
 | 
			
		||||
 | 
			
		||||
  const link = event.target as HTMLAnchorElement;
 | 
			
		||||
  link.click();
 | 
			
		||||
 | 
			
		||||
  return Promise.resolve(true);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
i.fa::before {
 | 
			
		||||
    color: var(--bs-dropdown-link-hover-color);
 | 
			
		||||
  color: var(--bs-dropdown-link-hover-color);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,235 +1,230 @@
 | 
			
		||||
import {
 | 
			
		||||
    StoredObject,
 | 
			
		||||
    StoredObjectStatus,
 | 
			
		||||
    StoredObjectStatusChange,
 | 
			
		||||
    StoredObjectVersion,
 | 
			
		||||
  StoredObject,
 | 
			
		||||
  StoredObjectStatus,
 | 
			
		||||
  StoredObjectStatusChange,
 | 
			
		||||
  StoredObjectVersion,
 | 
			
		||||
} from "../../types";
 | 
			
		||||
import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
 | 
			
		||||
 | 
			
		||||
const MIMES_EDIT = new Set([
 | 
			
		||||
    "application/vnd.ms-powerpoint",
 | 
			
		||||
    "application/vnd.ms-excel",
 | 
			
		||||
    "application/vnd.oasis.opendocument.text",
 | 
			
		||||
    "application/vnd.oasis.opendocument.text-flat-xml",
 | 
			
		||||
    "application/vnd.oasis.opendocument.spreadsheet",
 | 
			
		||||
    "application/vnd.oasis.opendocument.spreadsheet-flat-xml",
 | 
			
		||||
    "application/vnd.oasis.opendocument.presentation",
 | 
			
		||||
    "application/vnd.oasis.opendocument.presentation-flat-xml",
 | 
			
		||||
    "application/vnd.oasis.opendocument.graphics",
 | 
			
		||||
    "application/vnd.oasis.opendocument.graphics-flat-xml",
 | 
			
		||||
    "application/vnd.oasis.opendocument.chart",
 | 
			
		||||
    "application/msword",
 | 
			
		||||
    "application/vnd.ms-excel",
 | 
			
		||||
    "application/vnd.ms-powerpoint",
 | 
			
		||||
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
 | 
			
		||||
    "application/vnd.ms-word.document.macroEnabled.12",
 | 
			
		||||
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 | 
			
		||||
    "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
 | 
			
		||||
    "application/vnd.ms-excel.sheet.macroEnabled.12",
 | 
			
		||||
    "application/vnd.openxmlformats-officedocument.presentationml.presentation",
 | 
			
		||||
    "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
 | 
			
		||||
    "application/x-dif-document",
 | 
			
		||||
    "text/spreadsheet",
 | 
			
		||||
    "text/csv",
 | 
			
		||||
    "application/x-dbase",
 | 
			
		||||
    "text/rtf",
 | 
			
		||||
    "text/plain",
 | 
			
		||||
    "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
 | 
			
		||||
  "application/vnd.ms-powerpoint",
 | 
			
		||||
  "application/vnd.ms-excel",
 | 
			
		||||
  "application/vnd.oasis.opendocument.text",
 | 
			
		||||
  "application/vnd.oasis.opendocument.text-flat-xml",
 | 
			
		||||
  "application/vnd.oasis.opendocument.spreadsheet",
 | 
			
		||||
  "application/vnd.oasis.opendocument.spreadsheet-flat-xml",
 | 
			
		||||
  "application/vnd.oasis.opendocument.presentation",
 | 
			
		||||
  "application/vnd.oasis.opendocument.presentation-flat-xml",
 | 
			
		||||
  "application/vnd.oasis.opendocument.graphics",
 | 
			
		||||
  "application/vnd.oasis.opendocument.graphics-flat-xml",
 | 
			
		||||
  "application/vnd.oasis.opendocument.chart",
 | 
			
		||||
  "application/msword",
 | 
			
		||||
  "application/vnd.ms-excel",
 | 
			
		||||
  "application/vnd.ms-powerpoint",
 | 
			
		||||
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
 | 
			
		||||
  "application/vnd.ms-word.document.macroEnabled.12",
 | 
			
		||||
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
 | 
			
		||||
  "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
 | 
			
		||||
  "application/vnd.ms-excel.sheet.macroEnabled.12",
 | 
			
		||||
  "application/vnd.openxmlformats-officedocument.presentationml.presentation",
 | 
			
		||||
  "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
 | 
			
		||||
  "application/x-dif-document",
 | 
			
		||||
  "text/spreadsheet",
 | 
			
		||||
  "text/csv",
 | 
			
		||||
  "application/x-dbase",
 | 
			
		||||
  "text/rtf",
 | 
			
		||||
  "text/plain",
 | 
			
		||||
  "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
const MIMES_VIEW = new Set([
 | 
			
		||||
    ...MIMES_EDIT,
 | 
			
		||||
    [
 | 
			
		||||
        "image/svg+xml",
 | 
			
		||||
        "application/vnd.sun.xml.writer",
 | 
			
		||||
        "application/vnd.sun.xml.calc",
 | 
			
		||||
        "application/vnd.sun.xml.impress",
 | 
			
		||||
        "application/vnd.sun.xml.draw",
 | 
			
		||||
        "application/vnd.sun.xml.writer.global",
 | 
			
		||||
        "application/vnd.sun.xml.writer.template",
 | 
			
		||||
        "application/vnd.sun.xml.calc.template",
 | 
			
		||||
        "application/vnd.sun.xml.impress.template",
 | 
			
		||||
        "application/vnd.sun.xml.draw.template",
 | 
			
		||||
        "application/vnd.oasis.opendocument.text-master",
 | 
			
		||||
        "application/vnd.oasis.opendocument.text-template",
 | 
			
		||||
        "application/vnd.oasis.opendocument.text-master-template",
 | 
			
		||||
        "application/vnd.oasis.opendocument.spreadsheet-template",
 | 
			
		||||
        "application/vnd.oasis.opendocument.presentation-template",
 | 
			
		||||
        "application/vnd.oasis.opendocument.graphics-template",
 | 
			
		||||
        "application/vnd.ms-word.template.macroEnabled.12",
 | 
			
		||||
        "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
 | 
			
		||||
        "application/vnd.ms-excel.template.macroEnabled.12",
 | 
			
		||||
        "application/vnd.openxmlformats-officedocument.presentationml.template",
 | 
			
		||||
        "application/vnd.ms-powerpoint.template.macroEnabled.12",
 | 
			
		||||
        "application/vnd.wordperfect",
 | 
			
		||||
        "application/x-aportisdoc",
 | 
			
		||||
        "application/x-hwp",
 | 
			
		||||
        "application/vnd.ms-works",
 | 
			
		||||
        "application/x-mswrite",
 | 
			
		||||
        "application/vnd.lotus-1-2-3",
 | 
			
		||||
        "image/cgm",
 | 
			
		||||
        "image/vnd.dxf",
 | 
			
		||||
        "image/x-emf",
 | 
			
		||||
        "image/x-wmf",
 | 
			
		||||
        "application/coreldraw",
 | 
			
		||||
        "application/vnd.visio2013",
 | 
			
		||||
        "application/vnd.visio",
 | 
			
		||||
        "application/vnd.ms-visio.drawing",
 | 
			
		||||
        "application/x-mspublisher",
 | 
			
		||||
        "application/x-sony-bbeb",
 | 
			
		||||
        "application/x-gnumeric",
 | 
			
		||||
        "application/macwriteii",
 | 
			
		||||
        "application/x-iwork-numbers-sffnumbers",
 | 
			
		||||
        "application/vnd.oasis.opendocument.text-web",
 | 
			
		||||
        "application/x-pagemaker",
 | 
			
		||||
        "application/x-fictionbook+xml",
 | 
			
		||||
        "application/clarisworks",
 | 
			
		||||
        "image/x-wpg",
 | 
			
		||||
        "application/x-iwork-pages-sffpages",
 | 
			
		||||
        "application/x-iwork-keynote-sffkey",
 | 
			
		||||
        "application/x-abiword",
 | 
			
		||||
        "image/x-freehand",
 | 
			
		||||
        "application/vnd.sun.xml.chart",
 | 
			
		||||
        "application/x-t602",
 | 
			
		||||
        "image/bmp",
 | 
			
		||||
        "image/png",
 | 
			
		||||
        "image/gif",
 | 
			
		||||
        "image/tiff",
 | 
			
		||||
        "image/jpg",
 | 
			
		||||
        "image/jpeg",
 | 
			
		||||
        "application/pdf",
 | 
			
		||||
    ],
 | 
			
		||||
  ...MIMES_EDIT,
 | 
			
		||||
  [
 | 
			
		||||
    "image/svg+xml",
 | 
			
		||||
    "application/vnd.sun.xml.writer",
 | 
			
		||||
    "application/vnd.sun.xml.calc",
 | 
			
		||||
    "application/vnd.sun.xml.impress",
 | 
			
		||||
    "application/vnd.sun.xml.draw",
 | 
			
		||||
    "application/vnd.sun.xml.writer.global",
 | 
			
		||||
    "application/vnd.sun.xml.writer.template",
 | 
			
		||||
    "application/vnd.sun.xml.calc.template",
 | 
			
		||||
    "application/vnd.sun.xml.impress.template",
 | 
			
		||||
    "application/vnd.sun.xml.draw.template",
 | 
			
		||||
    "application/vnd.oasis.opendocument.text-master",
 | 
			
		||||
    "application/vnd.oasis.opendocument.text-template",
 | 
			
		||||
    "application/vnd.oasis.opendocument.text-master-template",
 | 
			
		||||
    "application/vnd.oasis.opendocument.spreadsheet-template",
 | 
			
		||||
    "application/vnd.oasis.opendocument.presentation-template",
 | 
			
		||||
    "application/vnd.oasis.opendocument.graphics-template",
 | 
			
		||||
    "application/vnd.ms-word.template.macroEnabled.12",
 | 
			
		||||
    "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
 | 
			
		||||
    "application/vnd.ms-excel.template.macroEnabled.12",
 | 
			
		||||
    "application/vnd.openxmlformats-officedocument.presentationml.template",
 | 
			
		||||
    "application/vnd.ms-powerpoint.template.macroEnabled.12",
 | 
			
		||||
    "application/vnd.wordperfect",
 | 
			
		||||
    "application/x-aportisdoc",
 | 
			
		||||
    "application/x-hwp",
 | 
			
		||||
    "application/vnd.ms-works",
 | 
			
		||||
    "application/x-mswrite",
 | 
			
		||||
    "application/vnd.lotus-1-2-3",
 | 
			
		||||
    "image/cgm",
 | 
			
		||||
    "image/vnd.dxf",
 | 
			
		||||
    "image/x-emf",
 | 
			
		||||
    "image/x-wmf",
 | 
			
		||||
    "application/coreldraw",
 | 
			
		||||
    "application/vnd.visio2013",
 | 
			
		||||
    "application/vnd.visio",
 | 
			
		||||
    "application/vnd.ms-visio.drawing",
 | 
			
		||||
    "application/x-mspublisher",
 | 
			
		||||
    "application/x-sony-bbeb",
 | 
			
		||||
    "application/x-gnumeric",
 | 
			
		||||
    "application/macwriteii",
 | 
			
		||||
    "application/x-iwork-numbers-sffnumbers",
 | 
			
		||||
    "application/vnd.oasis.opendocument.text-web",
 | 
			
		||||
    "application/x-pagemaker",
 | 
			
		||||
    "application/x-fictionbook+xml",
 | 
			
		||||
    "application/clarisworks",
 | 
			
		||||
    "image/x-wpg",
 | 
			
		||||
    "application/x-iwork-pages-sffpages",
 | 
			
		||||
    "application/x-iwork-keynote-sffkey",
 | 
			
		||||
    "application/x-abiword",
 | 
			
		||||
    "image/x-freehand",
 | 
			
		||||
    "application/vnd.sun.xml.chart",
 | 
			
		||||
    "application/x-t602",
 | 
			
		||||
    "image/bmp",
 | 
			
		||||
    "image/png",
 | 
			
		||||
    "image/gif",
 | 
			
		||||
    "image/tiff",
 | 
			
		||||
    "image/jpg",
 | 
			
		||||
    "image/jpeg",
 | 
			
		||||
    "application/pdf",
 | 
			
		||||
  ],
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
export interface SignedUrlGet {
 | 
			
		||||
    method: "GET" | "HEAD";
 | 
			
		||||
    url: string;
 | 
			
		||||
    expires: number;
 | 
			
		||||
    object_name: string;
 | 
			
		||||
  method: "GET" | "HEAD";
 | 
			
		||||
  url: string;
 | 
			
		||||
  expires: number;
 | 
			
		||||
  object_name: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function is_extension_editable(mimeType: string): boolean {
 | 
			
		||||
    return MIMES_EDIT.has(mimeType);
 | 
			
		||||
  return MIMES_EDIT.has(mimeType);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function is_extension_viewable(mimeType: string): boolean {
 | 
			
		||||
    return MIMES_VIEW.has(mimeType);
 | 
			
		||||
  return MIMES_VIEW.has(mimeType);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function build_convert_link(uuid: string) {
 | 
			
		||||
    return `/chill/wopi/convert/${uuid}`;
 | 
			
		||||
  return `/chill/wopi/convert/${uuid}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function build_download_info_link(
 | 
			
		||||
    storedObject: StoredObject,
 | 
			
		||||
    atVersion: null | StoredObjectVersion,
 | 
			
		||||
  storedObject: StoredObject,
 | 
			
		||||
  atVersion: null | StoredObjectVersion,
 | 
			
		||||
): string {
 | 
			
		||||
    const url = `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/get`;
 | 
			
		||||
  const url = `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/get`;
 | 
			
		||||
 | 
			
		||||
    if (null !== atVersion) {
 | 
			
		||||
        const params = new URLSearchParams({ version: atVersion.filename });
 | 
			
		||||
  if (null !== atVersion) {
 | 
			
		||||
    const params = new URLSearchParams({ version: atVersion.filename });
 | 
			
		||||
 | 
			
		||||
        return url + "?" + params.toString();
 | 
			
		||||
    }
 | 
			
		||||
    return url + "?" + params.toString();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return url;
 | 
			
		||||
  return url;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function download_info_link(
 | 
			
		||||
    storedObject: StoredObject,
 | 
			
		||||
    atVersion: null | StoredObjectVersion,
 | 
			
		||||
  storedObject: StoredObject,
 | 
			
		||||
  atVersion: null | StoredObjectVersion,
 | 
			
		||||
): Promise<SignedUrlGet> {
 | 
			
		||||
    return makeFetch("GET", build_download_info_link(storedObject, atVersion));
 | 
			
		||||
  return makeFetch("GET", build_download_info_link(storedObject, atVersion));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function build_wopi_editor_link(uuid: string, returnPath?: string) {
 | 
			
		||||
    if (returnPath === undefined) {
 | 
			
		||||
        returnPath =
 | 
			
		||||
            window.location.pathname +
 | 
			
		||||
            window.location.search +
 | 
			
		||||
            window.location.hash;
 | 
			
		||||
    }
 | 
			
		||||
  if (returnPath === undefined) {
 | 
			
		||||
    returnPath =
 | 
			
		||||
      window.location.pathname + window.location.search + window.location.hash;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        `/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath)
 | 
			
		||||
    );
 | 
			
		||||
  return (
 | 
			
		||||
    `/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath)
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function download_doc(url: string): Promise<Blob> {
 | 
			
		||||
    return window.fetch(url).then((r) => {
 | 
			
		||||
        if (r.ok) {
 | 
			
		||||
            return r.blob();
 | 
			
		||||
        }
 | 
			
		||||
  return window.fetch(url).then((r) => {
 | 
			
		||||
    if (r.ok) {
 | 
			
		||||
      return r.blob();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        throw new Error("Could not download document");
 | 
			
		||||
    });
 | 
			
		||||
    throw new Error("Could not download document");
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function download_and_decrypt_doc(
 | 
			
		||||
    storedObject: StoredObject,
 | 
			
		||||
    atVersion: null | StoredObjectVersion,
 | 
			
		||||
  storedObject: StoredObject,
 | 
			
		||||
  atVersion: null | StoredObjectVersion,
 | 
			
		||||
): Promise<Blob> {
 | 
			
		||||
    const algo = "AES-CBC";
 | 
			
		||||
  const algo = "AES-CBC";
 | 
			
		||||
 | 
			
		||||
    const atVersionToDownload = atVersion ?? storedObject.currentVersion;
 | 
			
		||||
  const atVersionToDownload = atVersion ?? storedObject.currentVersion;
 | 
			
		||||
 | 
			
		||||
    if (null === atVersionToDownload) {
 | 
			
		||||
        throw new Error("no version associated to stored object");
 | 
			
		||||
    }
 | 
			
		||||
  if (null === atVersionToDownload) {
 | 
			
		||||
    throw new Error("no version associated to stored object");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    // sometimes, the downloadInfo may be embedded into the storedObject
 | 
			
		||||
    console.log("storedObject", storedObject);
 | 
			
		||||
    let downloadInfo;
 | 
			
		||||
    if (
 | 
			
		||||
        typeof storedObject._links !== "undefined" &&
 | 
			
		||||
        typeof storedObject._links.downloadLink !== "undefined"
 | 
			
		||||
    ) {
 | 
			
		||||
        downloadInfo = storedObject._links.downloadLink;
 | 
			
		||||
    } else {
 | 
			
		||||
        downloadInfo = await download_info_link(
 | 
			
		||||
            storedObject,
 | 
			
		||||
            atVersionToDownload,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
  // sometimes, the downloadInfo may be embedded into the storedObject
 | 
			
		||||
  console.log("storedObject", storedObject);
 | 
			
		||||
  let downloadInfo;
 | 
			
		||||
  if (
 | 
			
		||||
    typeof storedObject._links !== "undefined" &&
 | 
			
		||||
    typeof storedObject._links.downloadLink !== "undefined"
 | 
			
		||||
  ) {
 | 
			
		||||
    downloadInfo = storedObject._links.downloadLink;
 | 
			
		||||
  } else {
 | 
			
		||||
    downloadInfo = await download_info_link(storedObject, atVersionToDownload);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const rawResponse = await window.fetch(downloadInfo.url);
 | 
			
		||||
  const rawResponse = await window.fetch(downloadInfo.url);
 | 
			
		||||
 | 
			
		||||
    if (!rawResponse.ok) {
 | 
			
		||||
        throw new Error(
 | 
			
		||||
            "error while downloading raw file " +
 | 
			
		||||
                rawResponse.status +
 | 
			
		||||
                " " +
 | 
			
		||||
                rawResponse.statusText,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
  if (!rawResponse.ok) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      "error while downloading raw file " +
 | 
			
		||||
        rawResponse.status +
 | 
			
		||||
        " " +
 | 
			
		||||
        rawResponse.statusText,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    if (atVersionToDownload.iv.length === 0) {
 | 
			
		||||
        return rawResponse.blob();
 | 
			
		||||
    }
 | 
			
		||||
  if (atVersionToDownload.iv.length === 0) {
 | 
			
		||||
    return rawResponse.blob();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const rawBuffer = await rawResponse.arrayBuffer();
 | 
			
		||||
    try {
 | 
			
		||||
        const key = await window.crypto.subtle.importKey(
 | 
			
		||||
            "jwk",
 | 
			
		||||
            atVersionToDownload.keyInfos,
 | 
			
		||||
            { name: algo },
 | 
			
		||||
            false,
 | 
			
		||||
            ["decrypt"],
 | 
			
		||||
        );
 | 
			
		||||
        const iv = Uint8Array.from(atVersionToDownload.iv);
 | 
			
		||||
        const decrypted = await window.crypto.subtle.decrypt(
 | 
			
		||||
            { name: algo, iv: iv },
 | 
			
		||||
            key,
 | 
			
		||||
            rawBuffer,
 | 
			
		||||
        );
 | 
			
		||||
  const rawBuffer = await rawResponse.arrayBuffer();
 | 
			
		||||
  try {
 | 
			
		||||
    const key = await window.crypto.subtle.importKey(
 | 
			
		||||
      "jwk",
 | 
			
		||||
      atVersionToDownload.keyInfos,
 | 
			
		||||
      { name: algo },
 | 
			
		||||
      false,
 | 
			
		||||
      ["decrypt"],
 | 
			
		||||
    );
 | 
			
		||||
    const iv = Uint8Array.from(atVersionToDownload.iv);
 | 
			
		||||
    const decrypted = await window.crypto.subtle.decrypt(
 | 
			
		||||
      { name: algo, iv: iv },
 | 
			
		||||
      key,
 | 
			
		||||
      rawBuffer,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
        return Promise.resolve(new Blob([decrypted]));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
        console.error("encounter error while keys and decrypt operations");
 | 
			
		||||
        console.error(e);
 | 
			
		||||
    return Promise.resolve(new Blob([decrypted]));
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error("encounter error while keys and decrypt operations");
 | 
			
		||||
    console.error(e);
 | 
			
		||||
 | 
			
		||||
        throw e;
 | 
			
		||||
    }
 | 
			
		||||
    throw e;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -239,48 +234,45 @@ async function download_and_decrypt_doc(
 | 
			
		||||
 * storage.
 | 
			
		||||
 */
 | 
			
		||||
async function download_doc_as_pdf(storedObject: StoredObject): Promise<Blob> {
 | 
			
		||||
    if (null === storedObject.currentVersion) {
 | 
			
		||||
        throw new Error("the stored object does not count any version");
 | 
			
		||||
    }
 | 
			
		||||
  if (null === storedObject.currentVersion) {
 | 
			
		||||
    throw new Error("the stored object does not count any version");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    if (storedObject.currentVersion?.type === "application/pdf") {
 | 
			
		||||
        return download_and_decrypt_doc(
 | 
			
		||||
            storedObject,
 | 
			
		||||
            storedObject.currentVersion,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
  if (storedObject.currentVersion?.type === "application/pdf") {
 | 
			
		||||
    return download_and_decrypt_doc(storedObject, storedObject.currentVersion);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    const convertLink = build_convert_link(storedObject.uuid);
 | 
			
		||||
    const response = await fetch(convertLink);
 | 
			
		||||
  const convertLink = build_convert_link(storedObject.uuid);
 | 
			
		||||
  const response = await fetch(convertLink);
 | 
			
		||||
 | 
			
		||||
    if (!response.ok) {
 | 
			
		||||
        throw new Error("Could not convert the document: " + response.status);
 | 
			
		||||
    }
 | 
			
		||||
  if (!response.ok) {
 | 
			
		||||
    throw new Error("Could not convert the document: " + response.status);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return response.blob();
 | 
			
		||||
  return response.blob();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function is_object_ready(
 | 
			
		||||
    storedObject: StoredObject,
 | 
			
		||||
  storedObject: StoredObject,
 | 
			
		||||
): Promise<StoredObjectStatusChange> {
 | 
			
		||||
    const new_status_response = await window.fetch(
 | 
			
		||||
        `/api/1.0/doc-store/stored-object/${storedObject.uuid}/is-ready`,
 | 
			
		||||
    );
 | 
			
		||||
  const new_status_response = await window.fetch(
 | 
			
		||||
    `/api/1.0/doc-store/stored-object/${storedObject.uuid}/is-ready`,
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    if (!new_status_response.ok) {
 | 
			
		||||
        throw new Error("could not fetch the new status");
 | 
			
		||||
    }
 | 
			
		||||
  if (!new_status_response.ok) {
 | 
			
		||||
    throw new Error("could not fetch the new status");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return await new_status_response.json();
 | 
			
		||||
  return await new_status_response.json();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
    build_convert_link,
 | 
			
		||||
    build_wopi_editor_link,
 | 
			
		||||
    download_and_decrypt_doc,
 | 
			
		||||
    download_doc,
 | 
			
		||||
    download_doc_as_pdf,
 | 
			
		||||
    is_extension_editable,
 | 
			
		||||
    is_extension_viewable,
 | 
			
		||||
    is_object_ready,
 | 
			
		||||
  build_convert_link,
 | 
			
		||||
  build_wopi_editor_link,
 | 
			
		||||
  download_and_decrypt_doc,
 | 
			
		||||
  download_doc,
 | 
			
		||||
  download_doc_as_pdf,
 | 
			
		||||
  is_extension_editable,
 | 
			
		||||
  is_extension_viewable,
 | 
			
		||||
  is_object_ready,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -46,16 +46,6 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
 | 
			
		||||
 | 
			
		||||
    public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool
 | 
			
		||||
    {
 | 
			
		||||
        // we first try to get the permission from the workflow, as attachement (this is the less intensive query)
 | 
			
		||||
        $workflowPermissionAsAttachment = match ($attribute) {
 | 
			
		||||
            StoredObjectRoleEnum::SEE => $this->workflowDocumentService->isAllowedByWorkflowForReadOperation($subject),
 | 
			
		||||
            StoredObjectRoleEnum::EDIT => $this->workflowDocumentService->isAllowedByWorkflowForWriteOperation($subject),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (WorkflowRelatedEntityPermissionHelper::FORCE_DENIED === $workflowPermissionAsAttachment) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Retrieve the related entity
 | 
			
		||||
        $entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject);
 | 
			
		||||
 | 
			
		||||
@@ -76,7 +66,7 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
 | 
			
		||||
        return match ($workflowPermission) {
 | 
			
		||||
            WorkflowRelatedEntityPermissionHelper::FORCE_GRANT => true,
 | 
			
		||||
            WorkflowRelatedEntityPermissionHelper::FORCE_DENIED => false,
 | 
			
		||||
            WorkflowRelatedEntityPermissionHelper::ABSTAIN => WorkflowRelatedEntityPermissionHelper::FORCE_GRANT === $workflowPermissionAsAttachment || $regularPermission,
 | 
			
		||||
            WorkflowRelatedEntityPermissionHelper::ABSTAIN => $regularPermission,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,12 +14,6 @@ namespace Chill\DocStoreBundle\Security\Authorization;
 | 
			
		||||
use Chill\DocStoreBundle\Entity\StoredObject;
 | 
			
		||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface for voting on stored object permissions.
 | 
			
		||||
 *
 | 
			
		||||
 * Each time a stored object is attached to a document, the voter is responsible for determining
 | 
			
		||||
 * whether the user has the necessary permissions to access or modify the stored object.
 | 
			
		||||
 */
 | 
			
		||||
interface StoredObjectVoterInterface
 | 
			
		||||
{
 | 
			
		||||
    public function supports(StoredObjectRoleEnum $attribute, StoredObject $subject): bool;
 | 
			
		||||
 
 | 
			
		||||
@@ -43,11 +43,17 @@ class StoredObjectVersionNormalizer implements NormalizerInterface, NormalizerAw
 | 
			
		||||
            'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, [...$context, UserNormalizer::AT_DATE => $object->getCreatedAt()]),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) {
 | 
			
		||||
        $normalizationGroups = $context[AbstractNormalizer::GROUPS] ?? [];
 | 
			
		||||
 | 
			
		||||
        if (is_string($normalizationGroups)) {
 | 
			
		||||
            $normalizationGroups = [$normalizationGroups];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $normalizationGroups, true)) {
 | 
			
		||||
            $data['point-in-times'] = $this->normalizer->normalize($object->getPointInTimes(), $format, $context);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array(self::WITH_RESTORED_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) {
 | 
			
		||||
        if (in_array(self::WITH_RESTORED_CONTEXT, $normalizationGroups, true)) {
 | 
			
		||||
            $data['from-restored'] = $this->normalizer->normalize($object->getCreatedFrom(), $format, [AbstractNormalizer::GROUPS => ['read']]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,6 @@ use Chill\DocStoreBundle\Entity\StoredObject;
 | 
			
		||||
use Chill\DocStoreBundle\Entity\StoredObjectPointInTime;
 | 
			
		||||
use Chill\DocStoreBundle\Entity\StoredObjectPointInTimeReasonEnum;
 | 
			
		||||
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
 | 
			
		||||
use Chill\DocStoreBundle\Exception\ConversionWithSameMimeTypeException;
 | 
			
		||||
use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
 | 
			
		||||
use Chill\WopiBundle\Service\WopiConverter;
 | 
			
		||||
use Symfony\Component\Mime\MimeTypesInterface;
 | 
			
		||||
@@ -42,10 +41,9 @@ class StoredObjectToPdfConverter
 | 
			
		||||
     *
 | 
			
		||||
     * @return array{0: StoredObjectPointInTime, 1: StoredObjectVersion, 2?: string} contains the point in time before conversion and the new version of the stored object. The converted content is included in the response if $includeConvertedContent is true
 | 
			
		||||
     *
 | 
			
		||||
     * @throws \UnexpectedValueException           if the preferred mime type for the conversion is not found
 | 
			
		||||
     * @throws \RuntimeException                   if the conversion or storage of the new version fails
 | 
			
		||||
     * @throws \UnexpectedValueException    if the preferred mime type for the conversion is not found
 | 
			
		||||
     * @throws \RuntimeException            if the conversion or storage of the new version fails
 | 
			
		||||
     * @throws StoredObjectManagerException
 | 
			
		||||
     * @throws ConversionWithSameMimeTypeException if the document has already the same mime type79*
 | 
			
		||||
     */
 | 
			
		||||
    public function addConvertedVersion(StoredObject $storedObject, string $lang, $convertTo = 'pdf', bool $includeConvertedContent = false): array
 | 
			
		||||
    {
 | 
			
		||||
@@ -58,7 +56,7 @@ class StoredObjectToPdfConverter
 | 
			
		||||
        $currentVersion = $storedObject->getCurrentVersion();
 | 
			
		||||
 | 
			
		||||
        if ($currentVersion->getType() === $newMimeType) {
 | 
			
		||||
            throw new ConversionWithSameMimeTypeException($newMimeType);
 | 
			
		||||
            throw new \UnexpectedValueException('Already at the same mime type');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $content = $this->storedObjectManager->read($currentVersion);
 | 
			
		||||
 
 | 
			
		||||
@@ -40,10 +40,6 @@ class StoredObjectVersionApiControllerTest extends \PHPUnit\Framework\TestCase
 | 
			
		||||
            $storedObject->registerVersion();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // remove one version in the history
 | 
			
		||||
        $v5 = $storedObject->getVersions()->get(5);
 | 
			
		||||
        $storedObject->removeVersion($v5);
 | 
			
		||||
 | 
			
		||||
        $security = $this->prophesize(Security::class);
 | 
			
		||||
        $security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)
 | 
			
		||||
            ->willReturn(true)
 | 
			
		||||
@@ -57,7 +53,6 @@ class StoredObjectVersionApiControllerTest extends \PHPUnit\Framework\TestCase
 | 
			
		||||
        self::assertEquals($response->getStatusCode(), 200);
 | 
			
		||||
        self::assertIsArray($body);
 | 
			
		||||
        self::assertArrayHasKey('results', $body);
 | 
			
		||||
        self::assertIsList($body['results']);
 | 
			
		||||
        self::assertCount(10, $body['results']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user