avancement planning

This commit is contained in:
2025-12-04 17:54:16 +01:00
parent 53abb68514
commit eb403c3aac
15 changed files with 4156 additions and 598 deletions

1781
node_modules/.package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
module.exports = {
printWidth: 160,
tabWidth: 4,
singleQuote: true,
endOfLine: 'lf',
trailingComma: 'none',
arrowParens: 'avoid'
};

25
node_modules/encoding/.travis.yml generated vendored
View File

@@ -1,25 +0,0 @@
language: node_js
sudo: false
node_js:
- "0.10"
- 0.12
- iojs
- 4
- 5
env:
- CXX=g++-4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
notifications:
email:
- andris@kreata.ee
webhooks:
urls:
- https://webhooks.gitter.im/e/0ed18fd9b3e529b3c2cc
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: false # default: false

16
node_modules/encoding/LICENSE generated vendored
View File

@@ -1,16 +0,0 @@
Copyright (c) 2012-2014 Andris Reinman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

41
node_modules/encoding/README.md generated vendored
View File

@@ -1,41 +0,0 @@
# Encoding
**encoding** is a simple wrapper around [iconv-lite](https://github.com/ashtuchkin/iconv-lite/) to convert strings from one encoding to another.
[![Build Status](https://secure.travis-ci.org/andris9/encoding.svg)](http://travis-ci.org/andris9/Nodemailer)
[![npm version](https://badge.fury.io/js/encoding.svg)](http://badge.fury.io/js/encoding)
Initially _encoding_ was a wrapper around _node-iconv_ (main) and _iconv-lite_ (fallback) and was used as the encoding layer for Nodemailer/mailparser. Somehow it also ended up as a dependency for a bunch of other project, none of these actually using _node-iconv_. The loading mechanics caused issues for front-end projects and Nodemailer/malparser had moved on, so _node-iconv_ was removed.
## Install
Install through npm
npm install encoding
## Usage
Require the module
var encoding = require("encoding");
Convert with encoding.convert()
var resultBuffer = encoding.convert(text, toCharset, fromCharset);
Where
- **text** is either a Buffer or a String to be converted
- **toCharset** is the characterset to convert the string
- **fromCharset** (_optional_, defaults to UTF-8) is the source charset
Output of the conversion is always a Buffer object.
Example
var result = encoding.convert("ÕÄÖÜ", "Latin_1");
console.log(result); //<Buffer d5 c4 d6 dc>
## License
**MIT**

View File

@@ -1,83 +0,0 @@
'use strict';
var iconvLite = require('iconv-lite');
// Expose to the world
module.exports.convert = convert;
/**
* Convert encoding of an UTF-8 string or a buffer
*
* @param {String|Buffer} str String to be converted
* @param {String} to Encoding to be converted to
* @param {String} [from='UTF-8'] Encoding to be converted from
* @return {Buffer} Encoded string
*/
function convert(str, to, from) {
from = checkEncoding(from || 'UTF-8');
to = checkEncoding(to || 'UTF-8');
str = str || '';
var result;
if (from !== 'UTF-8' && typeof str === 'string') {
str = Buffer.from(str, 'binary');
}
if (from === to) {
if (typeof str === 'string') {
result = Buffer.from(str);
} else {
result = str;
}
} else {
try {
result = convertIconvLite(str, to, from);
} catch (E) {
console.error(E);
result = str;
}
}
if (typeof result === 'string') {
result = Buffer.from(result, 'utf-8');
}
return result;
}
/**
* Convert encoding of astring with iconv-lite
*
* @param {String|Buffer} str String to be converted
* @param {String} to Encoding to be converted to
* @param {String} [from='UTF-8'] Encoding to be converted from
* @return {Buffer} Encoded string
*/
function convertIconvLite(str, to, from) {
if (to === 'UTF-8') {
return iconvLite.decode(str, from);
} else if (from === 'UTF-8') {
return iconvLite.encode(str, to);
} else {
return iconvLite.encode(iconvLite.decode(str, from), to);
}
}
/**
* Converts charset name if needed
*
* @param {String} name Character set
* @return {String} Character set name
*/
function checkEncoding(name) {
return (name || '')
.toString()
.trim()
.replace(/^latin[\-_]?(\d+)$/i, 'ISO-8859-$1')
.replace(/^win(?:dows)?[\-_]?(\d+)$/i, 'WINDOWS-$1')
.replace(/^utf[\-_]?(\d+)$/i, 'UTF-$1')
.replace(/^ks_c_5601\-1987$/i, 'CP949')
.replace(/^us[\-_]?ascii$/i, 'ASCII')
.toUpperCase();
}

18
node_modules/encoding/package.json generated vendored
View File

@@ -1,18 +0,0 @@
{
"name": "encoding",
"version": "0.1.13",
"description": "Convert encodings, uses iconv-lite",
"main": "lib/encoding.js",
"scripts": {
"test": "nodeunit test"
},
"repository": "https://github.com/andris9/encoding.git",
"author": "Andris Reinman",
"license": "MIT",
"dependencies": {
"iconv-lite": "^0.6.2"
},
"devDependencies": {
"nodeunit": "0.11.3"
}
}

49
node_modules/encoding/test/test.js generated vendored
View File

@@ -1,49 +0,0 @@
'use strict';
var encoding = require('../lib/encoding');
exports['General tests'] = {
'From UTF-8 to Latin_1': function (test) {
var input = 'ÕÄÖÜ',
expected = Buffer.from([0xd5, 0xc4, 0xd6, 0xdc]);
test.deepEqual(encoding.convert(input, 'latin1'), expected);
test.done();
},
'From Latin_1 to UTF-8': function (test) {
var input = Buffer.from([0xd5, 0xc4, 0xd6, 0xdc]),
expected = 'ÕÄÖÜ';
test.deepEqual(encoding.convert(input, 'utf-8', 'latin1').toString(), expected);
test.done();
},
'From UTF-8 to UTF-8': function (test) {
var input = 'ÕÄÖÜ',
expected = Buffer.from('ÕÄÖÜ');
test.deepEqual(encoding.convert(input, 'utf-8', 'utf-8'), expected);
test.done();
},
'From Latin_13 to Latin_15': function (test) {
var input = Buffer.from([0xd5, 0xc4, 0xd6, 0xdc, 0xd0]),
expected = Buffer.from([0xd5, 0xc4, 0xd6, 0xdc, 0xa6]);
test.deepEqual(encoding.convert(input, 'latin_15', 'latin13'), expected);
test.done();
}
/*
// ISO-2022-JP is not supported by iconv-lite
"From ISO-2022-JP to UTF-8 with Iconv": function (test) {
var input = Buffer.from(
"GyRCM1g5OzU7PVEwdzgmPSQ4IUYkMnFKczlwGyhC",
"base64"
),
expected = Buffer.from(
"5a2m5qCh5oqA6KGT5ZOh56CU5L+u5qSc6KiO5Lya5aCx5ZGK",
"base64"
);
test.deepEqual(encoding.convert(input, "utf-8", "ISO-2022-JP"), expected);
test.done();
},
*/
};

3
openapi-generator.yaml Normal file
View File

@@ -0,0 +1,3 @@
additionalProperties:
fileNaming: kebab-case
modelPropertyNaming: camelCase

1783
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,8 @@
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
"watch": "ng build --watch --configuration development",
"openapi": "rimraf src/app/services/api && openapi-generator-cli generate -i http://localhost:5298/swagger/v1/swagger.json -g typescript-angular -o src/app/services/api -c openapi-generator.yaml"
},
"private": true,
"dependencies": {
@@ -15,6 +16,7 @@
"@angular/forms": "^20.2.0-next",
"@angular/platform-browser": "^20.2.0-next",
"@angular/router": "^20.2.0-next",
"@openapitools/openapi-generator-cli": "^2.25.2",
"ng-zorro-antd": "^20.4.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
@@ -26,6 +28,7 @@
"@angular/compiler-cli": "^20.2.0-next",
"@types/node": "^16.11.35",
"less": "^4.2.0",
"rimraf": "^6.1.2",
"ts-node": "~10.9.0",
"typescript": "~5.8.0"
}

View File

@@ -1,14 +1,17 @@
.background {
padding: 20px;
min-height: 100vh;
background: #f5f5f0;
height: 100vh;
overflow: hidden;
background: linear-gradient(135deg, #f5f5f0 0%, #faf8f5 100%);
padding: 15px;
box-sizing: border-box;
}
.planning-container {
.planning {
height: 100%;
display: grid;
grid-template-columns: 300px 1fr;
gap: 20px;
max-width: 1400px;
grid-template-columns: 280px 1fr 240px;
gap: 15px;
max-width: 100%;
margin: 0 auto;
}
@@ -16,80 +19,98 @@
.calendar-section {
display: flex;
flex-direction: column;
gap: 15px;
gap: 12px;
overflow: hidden;
}
.calendar-title {
font-size: 18px;
font-weight: 600;
font-size: 16px;
font-weight: 700;
color: #333;
letter-spacing: 1px;
letter-spacing: 1.5px;
padding-left: 5px;
}
.calendar-date-info {
display: flex;
align-items: center;
gap: 15px;
padding: 15px;
gap: 12px;
padding: 14px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border-radius: 14px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.06);
transition: transform 0.2s ease;
}
.calendar-date-info:hover {
transform: translateY(-2px);
}
.date-badge {
background: #8b7b8b;
background: linear-gradient(135deg, var(--mauve) 0%, #8b7ba8 100%);
color: white;
padding: 10px;
border-radius: 8px;
border-radius: 10px;
text-align: center;
min-width: 60px;
min-width: 55px;
box-shadow: 0 3px 10px rgba(106, 90, 139, 0.3);
}
.month-abbr {
font-size: 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.day-number {
font-size: 24px;
font-weight: 700;
line-height: 1;
margin-top: 2px;
}
.date-full {
display: flex;
flex-direction: column;
gap: 2px;
}
.date-text {
font-size: 14px;
font-size: 13px;
font-weight: 600;
color: #333;
}
.day-text {
font-size: 13px;
font-size: 12px;
color: #999;
font-weight: 500;
}
.card {
background: #fefdfb;
border: 2px solid #d4a574;
border-radius: 20px;
padding: 15px;
box-shadow: 0 4px 12px rgba(212, 165, 116, 0.1);
background: white;
border: 2px solid var(--ugly-yellow);
border-radius: 16px;
padding: 16px;
box-shadow: 0 3px 12px rgba(212, 165, 116, 0.15);
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 0 15px;
padding: 0 0 14px;
}
.month-title {
font-size: 18px;
font-weight: 600;
color: #d4a574;
font-size: 16px;
font-weight: 700;
color: var(--ugly-yellow);
margin: 0;
}
@@ -103,16 +124,20 @@
justify-content: center;
transition: all 0.2s ease;
opacity: 0.7;
border-radius: 6px;
}
.nav-button:hover {
opacity: 1;
background: rgba(212, 165, 116, 0.1);
transform: scale(1.1);
}
/* Styles pour le calendrier NG-ZORRO */
::ng-deep .card nz-calendar {
background: transparent;
flex: 1;
overflow: hidden;
}
::ng-deep .card .ant-picker-calendar {
@@ -134,43 +159,40 @@
::ng-deep .card thead th {
color: #8b6f47;
font-weight: 600;
font-size: 11px;
font-size: 10px;
padding: 8px 0;
text-align: center;
border-bottom: none;
text-transform: uppercase;
}
::ng-deep .card .ant-picker-cell {
padding: 4px 0;
padding: 2px 0;
text-align: center;
}
::ng-deep .card .ant-picker-cell-inner {
width: 32px;
height: 32px;
line-height: 32px;
width: 30px;
height: 30px;
line-height: 30px;
border-radius: 8px;
margin: 0 auto;
color: #d4a574;
font-size: 13px;
font-weight: 500;
color: var(--ugly-yellow);
font-size: 12px;
font-weight: 600;
transition: all 0.2s ease;
background: transparent;
}
::ng-deep .card .ant-picker-cell:hover .ant-picker-cell-inner {
background: #f9f3ec;
background: rgba(212, 165, 116, 0.15);
transform: scale(1.05);
}
::ng-deep .card .ant-picker-cell-inner.in-selection {
background: #d4a574 !important;
background: linear-gradient(135deg, var(--ugly-yellow) 0%, #c49563 100%) !important;
color: white !important;
box-shadow: 0 2px 8px rgba(212, 165, 116, 0.3);
}
::ng-deep .card .ant-picker-cell-inner.in-selection:hover {
background: #c49563 !important;
box-shadow: 0 2px 8px rgba(212, 165, 116, 0.4);
}
::ng-deep .card .ant-picker-cell-disabled .ant-picker-cell-inner {
@@ -178,127 +200,107 @@
}
::ng-deep .card tbody tr {
height: 40px;
height: 36px;
}
/* Section Planning (droite) */
/* Section Planning (centre) */
.week-section {
background: white;
border-radius: 20px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
padding: 20px;
border-radius: 16px;
box-shadow: 0 3px 16px rgba(0, 0, 0, 0.08);
padding: 18px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.empty-state {
display: flex;
align-items: center;
justify-content: center;
min-height: 500px;
}
.empty-message {
text-align: center;
color: #999;
font-size: 14px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.week-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 20px;
padding-bottom: 14px;
border-bottom: 2px solid #f0f0f0;
margin-bottom: 15px;
margin-bottom: 14px;
}
.week-actions {
display: flex;
gap: 10px;
gap: 8px;
}
.action-btn {
padding: 8px 16px;
border: 1px solid #d9d9d9;
display: flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: 2px solid #e0e0e0;
background: white;
border-radius: 6px;
border-radius: 8px;
cursor: pointer;
font-size: 13px;
font-size: 12px;
font-weight: 600;
transition: all 0.2s ease;
color: #666;
}
.action-btn:hover {
border-color: #d4a574;
border-color: var(--ugly-yellow);
background: rgba(212, 165, 116, 0.05);
transform: translateY(-1px);
}
.action-btn.active {
background: #d4a574;
background: linear-gradient(135deg, var(--ugly-yellow) 0%, #c49563 100%);
color: white;
border-color: #d4a574;
border-color: var(--ugly-yellow);
box-shadow: 0 3px 10px rgba(212, 165, 116, 0.3);
}
.week-nav {
display: flex;
align-items: center;
gap: 15px;
}
.today-label {
font-weight: 600;
color: #999;
font-size: 14px;
gap: 10px;
}
.today-btn {
padding: 8px 16px;
padding: 8px 14px;
background: white;
border: 1px solid #d9d9d9;
border-radius: 6px;
border: 2px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
font-size: 13px;
font-size: 12px;
font-weight: 600;
transition: all 0.2s ease;
color: #666;
}
.today-btn:hover {
border-color: #8b7b8b;
}
.new-project-btn {
padding: 8px 20px;
background: #8b7b8b;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
font-size: 13px;
transition: all 0.2s ease;
}
.new-project-btn:hover {
background: #7a6a7a;
border-color: var(--mauve);
background: rgba(106, 90, 139, 0.05);
transform: translateY(-1px);
}
.week-calendar {
overflow-x: auto;
position: relative;
}
.week-nav-header {
position: absolute;
top: 8px;
right: 20px;
display: flex;
gap: 8px;
z-index: 10;
}
.nav-button-week {
background: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 4px 8px;
border: 2px solid #e0e0e0;
border-radius: 6px;
padding: 6px 8px;
cursor: pointer;
display: flex;
align-items: center;
@@ -308,37 +310,47 @@
.nav-button-week:hover {
border-color: #999;
background: #f9f9f9;
transform: scale(1.05);
}
.week-calendar {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
.week-grid {
display: grid;
grid-template-columns: 70px repeat(7, 1fr);
grid-template-columns: 50px repeat(7, 1fr);
gap: 0;
min-width: 900px;
height: 100%;
overflow: hidden;
}
.time-column {
border-right: 1px solid #e8e8e8;
border-right: 2px solid #f0f0f0;
}
.time-header {
height: 70px;
height: 50px;
border-bottom: 2px solid #e8e8e8;
}
.time-slot {
height: 70px;
height: 60px;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 5px;
font-size: 11px;
font-size: 10px;
color: #999;
border-bottom: 1px solid #f0f0f0;
font-weight: 600;
border-bottom: 1px solid #f5f5f5;
}
.day-column {
border-right: 1px solid #e8e8e8;
border-right: 1px solid #f0f0f0;
}
.day-column:last-child {
@@ -346,34 +358,37 @@
}
.day-header {
height: 70px;
height: 50px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-bottom: 2px solid #e8e8e8;
background: #fafafa;
transition: all 0.2s ease;
transition: all 0.3s ease;
gap: 2px;
}
.day-header.today {
background: #8b7b8b;
background: linear-gradient(135deg, var(--mauve) 0%, #8b7ba8 100%);
color: white;
box-shadow: 0 3px 10px rgba(106, 90, 139, 0.3);
}
.day-name {
font-size: 13px;
font-weight: 600;
margin-bottom: 3px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.5px;
}
.day-date {
font-size: 11px;
color: #999;
font-size: 16px;
font-weight: 700;
color: var(--ugly-yellow);
}
.day-header.today .day-date {
color: rgba(255, 255, 255, 0.8);
color: white;
}
.day-slots {
@@ -382,14 +397,203 @@
}
.hour-slot {
height: 70px;
border-bottom: 1px solid #f0f0f0;
height: 60px;
border-bottom: 1px solid #f5f5f5;
padding: 3px;
position: relative;
transition: background 0.2s ease;
transition: all 0.2s ease;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.hour-slot:hover {
background: #f9f9f9;
cursor: pointer;
background: rgba(212, 165, 116, 0.08);
}
.hour-slot.selected {
background: rgba(212, 165, 116, 0.2);
border-left: 3px solid var(--ugly-yellow);
}
.slot-indicator {
width: 10px;
height: 10px;
background: var(--ugly-yellow);
border-radius: 50%;
box-shadow: 0 2px 6px rgba(212, 165, 116, 0.4);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.2);
opacity: 0.8;
}
}
/* Sidebar (droite) */
.sidebar-section {
background: white;
border-radius: 16px;
box-shadow: 0 3px 16px rgba(0, 0, 0, 0.08);
padding: 18px;
overflow-y: auto;
max-height: 100%;
}
.sidebar-section::-webkit-scrollbar {
width: 6px;
}
.sidebar-section::-webkit-scrollbar-track {
background: #f0f0f0;
border-radius: 10px;
}
.sidebar-section::-webkit-scrollbar-thumb {
background: var(--ugly-yellow);
border-radius: 10px;
}
.sidebar-header {
padding-bottom: 14px;
border-bottom: 2px solid #f0f0f0;
margin-bottom: 14px;
}
.sidebar-title {
font-size: 16px;
font-weight: 700;
color: #333;
margin: 0;
}
.sidebar-content {
display: flex;
flex-direction: column;
gap: 14px;
}
.sidebar-block {
padding: 14px;
background: linear-gradient(135deg, #fafafa 0%, #f5f5f5 100%);
border-radius: 12px;
border-left: 3px solid var(--ugly-yellow);
transition: all 0.2s ease;
}
.sidebar-block:hover {
transform: translateX(3px);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.06);
}
.sidebar-block h4 {
font-size: 12px;
font-weight: 700;
color: #333;
margin: 0 0 10px 0;
display: flex;
align-items: center;
gap: 6px;
}
.sidebar-block p {
font-size: 12px;
color: #666;
margin: 0;
line-height: 1.4;
}
.slot-info {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 10px;
}
.slot-info p {
padding: 8px 10px;
background: white;
border-radius: 6px;
border-left: 3px solid var(--ugly-yellow);
}
.sidebar-btn {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
padding: 10px 12px;
margin-bottom: 8px;
background: white;
border: 2px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
font-size: 11px;
font-weight: 600;
transition: all 0.2s ease;
color: #666;
}
.sidebar-btn:hover {
background: rgba(212, 165, 116, 0.05);
border-color: var(--ugly-yellow);
transform: translateY(-1px);
}
.sidebar-btn.primary {
background: linear-gradient(135deg, var(--ugly-yellow) 0%, #c49563 100%);
color: white;
border-color: var(--ugly-yellow);
box-shadow: 0 3px 10px rgba(212, 165, 116, 0.3);
}
.sidebar-btn.primary:hover {
box-shadow: 0 4px 12px rgba(212, 165, 116, 0.4);
}
.sidebar-btn:last-child {
margin-bottom: 0;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-top: 10px;
}
.stat-item {
background: white;
padding: 12px;
border-radius: 8px;
text-align: center;
border: 2px solid #f0f0f0;
transition: all 0.2s ease;
}
.stat-item:hover {
border-color: var(--ugly-yellow);
transform: translateY(-1px);
}
.stat-value {
font-size: 20px;
font-weight: 700;
color: var(--ugly-yellow);
margin-bottom: 3px;
}
.stat-label {
font-size: 9px;
color: #999;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}

View File

@@ -1,41 +1,40 @@
<div class="background">
<div class="planning-container">
<div class="planning">
<!-- Calendrier à gauche -->
<div class="calendar-section">
<div class="calendar-title">CALENDAR</div>
<div class="calendar-title">CALENDRIER</div>
<div class="calendar-date-info">
<div class="date-badge">
<div class="month-abbr">JAN</div>
<div class="day-number">21</div>
<div class="month-abbr">{{ getCurrentMonth() }}</div>
<div class="day-number">{{ getCurrentDay() }}</div>
</div>
<div class="date-full">
<div class="date-text">21 janvier 2026</div>
<div class="day-text">Mercredi</div>
<div class="date-text">{{ getCurrentDate() }}</div>
<div class="day-text">{{ getDayWeek() }}</div>
</div>
</div>
<div class="card">
<div class="calendar-header">
<button class="nav-button" (click)="previousMonth()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M15 18L9 12L15 6" stroke="#d4a574" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<h2 class="month-title">{{ currentMonthYear }}</h2>
<button class="nav-button" (click)="nextMonth()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M9 18L15 12L9 6" stroke="#d4a574" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<nz-calendar
[nzFullscreen]="false"
[ngModel]="currentDate"
(nzSelectChange)="onValueChange($event)"
(nzPanelChange)="onPanelChange($event)"
[nzDateFullCell]="dateCellTemplate"
[nzFullscreen]="false"
[ngModel]="currentDate"
(nzSelectChange)="onValueChange($event)"
(nzPanelChange)="onPanelChange($event)"
[nzDateFullCell]="dateCellTemplate"
></nz-calendar>
<ng-template #dateCellTemplate let-date>
<div class="ant-picker-cell-inner" [class.in-selection]="isDateSelected(date)">
{{ date.getDate() }}
@@ -44,37 +43,45 @@
</div>
</div>
<!-- Planning de la semaine à droite -->
<!-- Planning de la semaine au centre -->
<div class="week-section" *ngIf="selectedDates.length > 0">
<div class="week-toolbar">
<div class="week-actions">
<button class="action-btn">Lorem</button>
<button class="action-btn active">Camion</button>
<button class="action-btn">Show</button>
<button [class]="isCamionFilterActive ? 'action-btn active' : 'action-btn'" (click)="camionFilter()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<rect x="1" y="3" width="15" height="13" rx="2" ry="2" stroke-width="2"/>
<path d="M16 8h5l3 3v5h-4" stroke-width="2"/>
<circle cx="5.5" cy="18.5" r="2.5" stroke-width="2"/>
<circle cx="18.5" cy="18.5" r="2.5" stroke-width="2"/>
</svg>
Camion
</button>
<button [class]="isShowFilterActive ? 'action-btn active' : 'action-btn'" (click)="showFilter()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="11" cy="11" r="8" stroke-width="2"/>
<path d="M21 21l-4.35-4.35" stroke-width="2" stroke-linecap="round"/>
</svg>
Show
</button>
</div>
<div class="week-nav">
<span class="today-label">LOREM</span>
<button class="today-btn" (click)="goToToday()">
< TODAY >
<button class="nav-button-week" (click)="previousWeek()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M15 18L9 12L15 6" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button class="today-btn" (click)="goToToday()">
Aujourd'hui
</button>
<button class="nav-button-week" (click)="nextWeek()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M9 18L15 12L9 6" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button class="new-project-btn">NEW PROJECT</button>
</div>
</div>
<div class="week-calendar">
<div class="week-nav-header">
<button class="nav-button-week" (click)="previousWeek()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M15 18L9 12L15 6" stroke="#999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button class="nav-button-week" (click)="nextWeek()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M9 18L15 12L9 6" stroke="#999" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<div class="week-grid">
<!-- Colonne des heures -->
<div class="time-column">
@@ -88,11 +95,14 @@
<div class="day-column" *ngFor="let date of selectedDates">
<div class="day-header" [class.today]="isToday(date)">
<div class="day-name">{{ getDayName(date) }}</div>
<div class="day-date">{{ formatDate(date) }}</div>
<div class="day-date">{{ date.getDate() }}</div>
</div>
<div class="day-slots">
<div class="hour-slot" *ngFor="let hour of getHours()">
<!-- Ici on peut ajouter des événements plus tard -->
<div class="hour-slot"
*ngFor="let hour of getHours()"
(click)="selectSlot(date, hour)"
[class.selected]="isSlotSelected(date, hour)">
<div class="slot-indicator" *ngIf="isSlotSelected(date, hour)"></div>
</div>
</div>
</div>
@@ -103,8 +113,90 @@
<!-- Message si aucune semaine sélectionnée -->
<div class="week-section empty-state" *ngIf="selectedDates.length === 0">
<div class="empty-message">
<p>Sélectionnez une date dans le calendrier pour voir le planning de la semaine</p>
<p>Sélectionnez une date dans le calendrier</p>
</div>
</div>
<!-- Sidebar à droite -->
<div class="sidebar-section">
<div class="sidebar-header">
<h3 class="sidebar-title">Détails</h3>
</div>
<div class="sidebar-content">
<div class="sidebar-block" *ngIf="selectedSlot">
<h4>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
<polyline points="12 6 12 12 16 14" stroke-width="2" stroke-linecap="round"/>
</svg>
Spéctacle sélectionné :
</h4>
<div class="slot-info">
@for (show of shows(); track show.id) {
<p><strong>{{ show.name }}</strong></p>
<p>{{ show.place }}</p>
<p>{{ show.date }}</p>
<p>{{ show.description }}</p>
}
</div>
</div>
<div class="sidebar-block">
<h4>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Statistiques
</h4>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value">{{ getTotalEvents() }}</div>
<div class="stat-label">Événements</div>
</div>
<div class="stat-item">
<div class="stat-value">S{{ getWeekNumber() }}</div>
<div class="stat-label">Semaine</div>
</div>
</div>
</div>
<div class="sidebar-block">
<h4>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<circle cx="12" cy="12" r="3" stroke-width="2"/>
<path d="M12 1v6m0 6v6M1 12h6m6 0h6" stroke-width="2" stroke-linecap="round"/>
</svg>
Actions rapides
</h4>
<button class="sidebar-btn" nzType="default" (click)="showModal()">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M12 5v14M5 12h14" stroke-width="2" stroke-linecap="round"/>
</svg>
<span>Créer événement</span>
</button>
<nz-modal
(nzOnCancel)="handleCancel()"
(nzOnOk)="handleOk()"
[(nzVisible)]="isVisible"
nzCentered
nzDraggable
nzTitle="Création d'évènement"
>
<ng-container *nzModalContent>
<p>Just don't learn physics at school and your life will be full of magic and miracles.</p>
<p>Day before yesterday I saw a rabbit, and yesterday a deer, and today, you.</p>
</ng-container>
</nz-modal>
<button class="sidebar-btn">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" stroke-width="2"/>
<polyline points="14 2 14 8 20 8" stroke-width="2"/>
</svg>
Voir rapports
</button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,106 +1,234 @@
import { Component } from '@angular/core';
import {Component, inject, OnInit, signal} from '@angular/core';
import { NzCalendarMode, NzCalendarModule } from 'ng-zorro-antd/calendar';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import {ReadShowDto, ShowsService} from "../../../services/api";
import {firstValueFrom} from "rxjs";
import {NzTableComponent} from "ng-zorro-antd/table";
import {NzModalComponent, NzModalModule} from "ng-zorro-antd/modal";
import {NzButtonModule} from "ng-zorro-antd/button";
@Component({
selector: 'app-planning',
imports: [NzCalendarModule, CommonModule, FormsModule],
templateUrl: './planning.html',
styleUrl: './planning.css',
selector: 'app-planning',
imports: [NzCalendarModule, CommonModule, FormsModule, NzModalComponent, NzButtonModule, NzModalModule],
templateUrl: './planning.html',
styleUrl: './planning.css',
})
export class Planning {
currentDate: Date = new Date();
selectedDates: Date[] = [];
monthNames = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin',
'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
mode: NzCalendarMode = 'month';
export class Planning implements OnInit{
private showsServices = inject(ShowsService)
get currentMonthYear(): string {
return `${this.monthNames[this.currentDate.getMonth()]} ${this.currentDate.getFullYear()}`;
}
shows = signal<ReadShowDto[]>([]);
onValueChange(value: Date): void {
console.log(`Current value: ${value}`);
this.currentDate = value;
// Sélectionner la date cliquée + les 6 jours suivants (7 jours au total)
this.selectedDates = [];
for (let i = 0; i < 7; i++) {
const date = new Date(value);
date.setDate(date.getDate() + i);
this.selectedDates.push(date);
async ngOnInit(){
await this.fetchShows();
}
console.log('Selected dates:', this.selectedDates);
}
onPanelChange(change: { date: Date; mode: string }): void {
console.log(`Current value: ${change.date}`);
console.log(`Current mode: ${change.mode}`);
this.currentDate = change.date;
}
previousMonth(): void {
const newDate = new Date(this.currentDate);
newDate.setMonth(newDate.getMonth() - 1);
this.currentDate = newDate;
}
nextMonth(): void {
const newDate = new Date(this.currentDate);
newDate.setMonth(newDate.getMonth() + 1);
this.currentDate = newDate;
}
previousWeek(): void {
if (this.selectedDates.length > 0) {
const newDate = new Date(this.selectedDates[0]);
newDate.setDate(newDate.getDate() - 7);
this.onValueChange(newDate);
async fetchShows() {
const shows = await firstValueFrom(this.showsServices.getAllShowsEndpoint());
this.shows.set(shows);
}
}
nextWeek(): void {
if (this.selectedDates.length > 0) {
const newDate = new Date(this.selectedDates[0]);
newDate.setDate(newDate.getDate() + 7);
this.onValueChange(newDate);
isVisible = false;
showModal(): void {
if (!this.isVisible) {
this.isVisible = true;
}
else {
this.isVisible = false;
}
}
}
goToToday(): void {
const today = new Date();
this.onValueChange(today);
}
handleOk(): void {
console.log('Button ok clicked!');
this.isVisible = false;
}
isDateSelected(date: Date): boolean {
return this.selectedDates.some(selectedDate =>
selectedDate.getFullYear() === date.getFullYear() &&
selectedDate.getMonth() === date.getMonth() &&
selectedDate.getDate() === date.getDate()
);
}
handleCancel(): void {
console.log('Button cancel clicked!');
this.isVisible = false;
}
isToday(date: Date): boolean {
const today = new Date();
return date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear();
}
getDayName(date: Date): string {
const days = ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'];
return days[date.getDay()];
}
selectSlot(date: Date, hour: string): void {
this.selectedSlot = { date, hour };
console.log('Slot sélectionné:', date, hour);
}
formatDate(date: Date): string {
const days = ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'];
return `${days[date.getDay()]} ${date.getDate()} ${date.getFullYear()}`;
}
getHours(): string[] {
return ['8 AM', '10 AM', '11 AM', '12 PM', '14 PM', '16 PM', '18 PM'];
}
}
currentDate: Date = new Date();
selectedDates: Date[] = [];
monthNames = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin',
'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
mode: NzCalendarMode = 'month';
get currentMonthYear(): string {
return `${this.monthNames[this.currentDate.getMonth()]} ${this.currentDate.getFullYear()}`;
}
getCurrentMonth(): string {
const today = new Date();
const mois = ['jan', 'fév', 'mar', 'avr', 'mai', 'jun', 'jul', 'aoû', 'sep', 'oct', 'nov', 'déc'];
return mois[today.getMonth()];
}
getCurrentDay(): string {
const today = new Date();
return String(today.getDate());
}
getCurrentDate(): string {
const today = new Date();
const mois = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'];
return `${today.getDate()} ${mois[today.getMonth()]} ${today.getFullYear()}`;
}
getDayWeek(): string {
const today = new Date();
const days = ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'];
return days[today.getDay()];
}
onValueChange(value: Date): void {
console.log(`Current value: ${value}`);
this.currentDate = value;
// Sélectionner la date cliquée + les 6 jours suivants (7 jours au total)
this.selectedDates = [];
for (let i = 0; i < 7; i++) {
const date = new Date(value);
date.setDate(date.getDate() + i);
this.selectedDates.push(date);
}
console.log('Selected dates:', this.selectedDates);
}
onPanelChange(change: { date: Date; mode: string }): void {
console.log(`Current value: ${change.date}`);
console.log(`Current mode: ${change.mode}`);
this.currentDate = change.date;
}
previousMonth(): void {
const newDate = new Date(this.currentDate);
newDate.setMonth(newDate.getMonth() - 1);
this.currentDate = newDate;
}
nextMonth(): void {
const newDate = new Date(this.currentDate);
newDate.setMonth(newDate.getMonth() + 1);
this.currentDate = newDate;
}
previousWeek(): void {
if (this.selectedDates.length > 0) {
const newDate = new Date(this.selectedDates[0]);
newDate.setDate(newDate.getDate() - 7);
this.onValueChange(newDate);
}
}
nextWeek(): void {
if (this.selectedDates.length > 0) {
const newDate = new Date(this.selectedDates[0]);
newDate.setDate(newDate.getDate() + 7);
this.onValueChange(newDate);
}
}
goToToday(): void {
const today = new Date();
this.onValueChange(today);
}
isDateSelected(date: Date): boolean {
return this.selectedDates.some(selectedDate =>
selectedDate.getFullYear() === date.getFullYear() &&
selectedDate.getMonth() === date.getMonth() &&
selectedDate.getDate() === date.getDate()
);
}
isToday(date: Date): boolean {
const today = new Date();
return date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear();
}
getDayName(date: Date): string {
const days = ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'];
return days[date.getDay()];
}
formatDate(date: Date): string {
const days = ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'];
return `${days[date.getDay()]} ${date.getDate()} ${date.getFullYear()}`;
}
getHours(): string[] {
return ['8h', '10h', '11h', '12h', '14h', '16h', '18h', '20h', '22h'];
}
// Filtres
isCamionFilterActive = false;
camionFilter(): void {
this.isCamionFilterActive = !this.isCamionFilterActive;
console.log('Filtre Camion:', this.isCamionFilterActive);
}
isShowFilterActive = false;
showFilter(): void {
this.isShowFilterActive = !this.isShowFilterActive;
console.log('Filtre Show:', this.isShowFilterActive);
}
// Sélection de créneaux
selectedSlot: { date: Date; hour: string } | null = null;
isSlotSelected(date: Date, hour: string): boolean {
if (!this.selectedSlot) return false;
return this.selectedSlot.date.getTime() === date.getTime() &&
this.selectedSlot.hour === hour;
}
formatSelectedDate(date: Date): string {
const days = ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'];
const months = ['janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'];
return `${days[date.getDay()]} ${date.getDate()} ${months[date.getMonth()]}`;
}
createNewProject(): void {
console.log('Création d\'un nouveau projet');
alert('Fonctionnalité à venir : Créer un nouveau projet');
}
getTotalEvents(): number {
// À implémenter avec de vraies données
return 0;
}
getWeekNumber(): number {
if (this.selectedDates.length === 0) return 0;
const date = new Date(this.selectedDates[0]);
const firstDayOfYear = new Date(date.getFullYear(), 0, 1);
const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000;
return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);
}
}

View File

@@ -1,4 +1,9 @@
/* You can add global styles to this file, and also import other style files */
:root {
--mauve: #8b7b8b;
--ugly-yellow: #d4a574;
}
* {
margin: 0;
padding: 0;
@@ -21,3 +26,6 @@ button.primary {
color: white;
border-radius: 8px;
}