Compare commits
5 Commits
ce5fd5621e
...
testdurati
| Author | SHA1 | Date | |
|---|---|---|---|
| 95cc10a8de | |||
| 1439d33584 | |||
| 9fad10e7b7 | |||
| 62c6bb5fa5 | |||
| 56cca2b498 |
4
ressources.csv
Normal file
4
ressources.csv
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
ressource,profil,7/14/2025,7/15/2025,7/16/2025,7/17/2025,7/18/2025,7/19/2025,7/20/2025,7/21/2025,7/22/2025
|
||||||
|
Simon BOYER,dev,0,0.5,0.5,0.5,0.5,0,0.5,0.5,0.5
|
||||||
|
Fatima BROUM,dev,0,1,1,1,1,0,1,1,1
|
||||||
|
Need Dev,dev,0,0,0,0,0,0,1,1,1
|
||||||
|
@ -36,3 +36,11 @@ npm run build
|
|||||||
You can preview the production build with `npm run preview`.
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Pour créer la BDD en local :
|
||||||
|
npm run init-db
|
||||||
|
|
||||||
|
Pour lancer l'application
|
||||||
|
npm run dev
|
||||||
Binary file not shown.
@ -21,7 +21,8 @@ db.exec(`
|
|||||||
progress INTEGER,
|
progress INTEGER,
|
||||||
type TEXT,
|
type TEXT,
|
||||||
parent INTEGER,
|
parent INTEGER,
|
||||||
lazy BOOLEAN DEFAULT 0 -- ✅ Ajout ici
|
assignedTo TEXT,
|
||||||
|
lazy_loading BOOLEAN DEFAULT 0
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE links (
|
CREATE TABLE links (
|
||||||
@ -31,5 +32,16 @@ db.exec(`
|
|||||||
type TEXT
|
type TEXT
|
||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
// table pour les ressources
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS resource_planning (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
ressource TEXT NOT NULL,
|
||||||
|
profil TEXT NOT NULL,
|
||||||
|
date TEXT NOT NULL,
|
||||||
|
disponibilite REAL NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
|
||||||
console.log('✅ Base de données initialisée avec succès.');
|
console.log('✅ Base de données initialisée avec succès.');
|
||||||
@ -4,4 +4,14 @@ import { resolve } from 'path';
|
|||||||
// 📁 chemin absolu vers la BDD SQLite
|
// 📁 chemin absolu vers la BDD SQLite
|
||||||
const db = new Database(resolve('data.db'));
|
const db = new Database(resolve('data.db'));
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS resource_planning (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
ressource TEXT NOT NULL,
|
||||||
|
profil TEXT NOT NULL,
|
||||||
|
date TEXT NOT NULL,
|
||||||
|
disponibilite REAL NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
export default db;
|
export default db;
|
||||||
|
|||||||
@ -25,6 +25,7 @@
|
|||||||
<a href="/" class:selected={current === "/"} title="Accueil">🏠</a>
|
<a href="/" class:selected={current === "/"} title="Accueil">🏠</a>
|
||||||
<a href="/gantt" class:selected={current === "/gantt"} title="Gantt">📅</a>
|
<a href="/gantt" class:selected={current === "/gantt"} title="Gantt">📅</a>
|
||||||
<a href="/import" class:selected={current === "/import"} title="Importer">📂</a>
|
<a href="/import" class:selected={current === "/import"} title="Importer">📂</a>
|
||||||
|
<a href="/planning" class="block px-4 py-2 hover:bg-gray-100">📅</a>
|
||||||
<a href="/config" class:selected={current === "/config"} title="Config">⚙️</a>
|
<a href="/config" class:selected={current === "/config"} title="Config">⚙️</a>
|
||||||
</nav>
|
</nav>
|
||||||
<button class="reset-btn" on:click={resetData} title="Réinitialiser les données">
|
<button class="reset-btn" on:click={resetData} title="Réinitialiser les données">
|
||||||
|
|||||||
72
svar-gantt-app/src/routes/api/planning/+server.ts
Normal file
72
svar-gantt-app/src/routes/api/planning/+server.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { json } from '@sveltejs/kit';
|
||||||
|
import db from '$lib/server/db';
|
||||||
|
|
||||||
|
export async function POST({ request }) {
|
||||||
|
const body = await request.json();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Array.isArray(body)) {
|
||||||
|
if (body.length > 1) {
|
||||||
|
// Cas d’un import CSV → remplacement total
|
||||||
|
const insert = db.prepare(`
|
||||||
|
INSERT INTO resource_planning (ressource, profil, date, disponibilite)
|
||||||
|
VALUES (@ressource, @profil, @date, @disponibilite)
|
||||||
|
`);
|
||||||
|
const clear = db.prepare(`DELETE FROM resource_planning`);
|
||||||
|
const transaction = db.transaction((data: any[]) => {
|
||||||
|
clear.run();
|
||||||
|
for (const entry of data) {
|
||||||
|
insert.run(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
transaction(body);
|
||||||
|
} else if (body.length === 1) {
|
||||||
|
// Cas d'une modif manuelle → update ou insert
|
||||||
|
const { ressource, profil, date, disponibilite } = body[0];
|
||||||
|
const existing = db.prepare(`
|
||||||
|
SELECT id FROM resource_planning
|
||||||
|
WHERE ressource = ? AND profil = ? AND date = ?
|
||||||
|
`).get(ressource, profil, date);
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
db.prepare(`
|
||||||
|
UPDATE resource_planning
|
||||||
|
SET disponibilite = ?
|
||||||
|
WHERE id = ?
|
||||||
|
`).run(disponibilite, existing.id);
|
||||||
|
} else {
|
||||||
|
db.prepare(`
|
||||||
|
INSERT INTO resource_planning (ressource, profil, date, disponibilite)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
`).run(ressource, profil, date, disponibilite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json({ success: true });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Erreur POST /api/planning', e);
|
||||||
|
return json({ success: false, error: e.message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const rows = db.prepare(`SELECT * FROM resource_planning`).all();
|
||||||
|
|
||||||
|
const grouped = new Map();
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const key = `${row.ressource}|${row.profil}`;
|
||||||
|
if (!grouped.has(key)) {
|
||||||
|
grouped.set(key, {
|
||||||
|
ressource: row.ressource,
|
||||||
|
profil: row.profil,
|
||||||
|
disponibilites: {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
grouped.get(key).disponibilites[row.date] = row.disponibilite;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json(Array.from(grouped.values()));
|
||||||
|
}
|
||||||
@ -12,8 +12,30 @@ export async function POST({ request }) {
|
|||||||
const { tasks, links } = await request.json();
|
const { tasks, links } = await request.json();
|
||||||
|
|
||||||
const insertTask = db.prepare(`
|
const insertTask = db.prepare(`
|
||||||
INSERT INTO tasks (id, text, start, end, duration, progress, type, parent, lazy)
|
INSERT INTO tasks (
|
||||||
VALUES (@id, @text, @start, @end, @duration, @progress, @type, @parent, @lazy)
|
id,
|
||||||
|
text,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
duration,
|
||||||
|
progress,
|
||||||
|
type,
|
||||||
|
parent,
|
||||||
|
assignedTo,
|
||||||
|
lazy_loading
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
@id,
|
||||||
|
@text,
|
||||||
|
@start,
|
||||||
|
@end,
|
||||||
|
@duration,
|
||||||
|
@progress,
|
||||||
|
@type,
|
||||||
|
@parent,
|
||||||
|
@assignedTo,
|
||||||
|
@lazy_loading
|
||||||
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const insertLink = db.prepare(`
|
const insertLink = db.prepare(`
|
||||||
@ -21,21 +43,20 @@ export async function POST({ request }) {
|
|||||||
VALUES (@id, @source, @target, @type)
|
VALUES (@id, @source, @target, @type)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const taskTx = db.transaction((all) => {
|
const taskTx = db.transaction(({ tasks, links }) => {
|
||||||
db.prepare('DELETE FROM tasks').run();
|
db.prepare('DELETE FROM tasks').run();
|
||||||
db.prepare('DELETE FROM links').run();
|
db.prepare('DELETE FROM links').run();
|
||||||
|
|
||||||
for (const task of all.tasks) {
|
for (const task of tasks) {
|
||||||
insertTask.run({
|
insertTask.run({
|
||||||
...task,
|
...task,
|
||||||
start: typeof task.start === 'object' ? new Date(task.start).toISOString() : task.start,
|
start: typeof task.start === 'object' ? new Date(task.start).toISOString() : task.start,
|
||||||
end: typeof task.end === 'object' ? new Date(task.end).toISOString() : task.end,
|
end: typeof task.end === 'object' ? new Date(task.end).toISOString() : task.end,
|
||||||
lazy: task.lazy ? 1 : 0, // ⚠️ SQLite ne supporte pas le type boolean natif
|
lazy_loading: task.lazy_loading ? 1 : 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const link of links) {
|
||||||
for (const link of all.links) {
|
|
||||||
insertLink.run(link);
|
insertLink.run(link);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,39 +4,52 @@
|
|||||||
|
|
||||||
let tasks = [];
|
let tasks = [];
|
||||||
let links = [];
|
let links = [];
|
||||||
|
|
||||||
|
// 🗓️ Affichage : mois + jours
|
||||||
const scales = [
|
const scales = [
|
||||||
{ unit: 'month', step: 1, format: 'MMMM yyyy' },
|
{ unit: 'month', step: 1, format: 'MMMM yyyy' },
|
||||||
{ unit: 'day', step: 1, format: 'd' }
|
{ unit: 'day', step: 1, format: 'd' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 📋 Options : colonnes personnalisées
|
||||||
|
const options = {
|
||||||
|
taskList: {
|
||||||
|
visible: true, // ✅ Important
|
||||||
|
columns: [
|
||||||
|
{ id: 'text', label: 'Nom', value: 'text', width: 200 },
|
||||||
|
{ id: 'assignedTo', label: 'Assignée à', value: 'assignedTo', width: 150 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const res = await fetch('/api/tasks');
|
const res = await fetch('/api/tasks');
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const { tasks: loadedTasks, links: loadedLinks } = await res.json();
|
const { tasks: loadedTasks, links: loadedLinks } = await res.json();
|
||||||
|
|
||||||
tasks = loadedTasks.map(task => {
|
tasks = loadedTasks.map(task => {
|
||||||
// Assure que start est une date ISO
|
const start = new Date(task.start);
|
||||||
const start = new Date(task.start);
|
const duration = task.duration || task.estimation || 1;
|
||||||
const duration = task.duration || task.estimation || 1;
|
|
||||||
|
|
||||||
const end = new Date(start);
|
const end = new Date(start);
|
||||||
end.setDate(start.getDate() + duration);
|
end.setDate(start.getDate() + duration);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...task,
|
...task,
|
||||||
start: start.toISOString().slice(0, 10), // "YYYY-MM-DD"
|
start: start.toISOString().slice(0, 10),
|
||||||
end: end.toISOString().slice(0, 10)
|
end: end.toISOString().slice(0, 10),
|
||||||
};
|
duration: duration
|
||||||
});
|
};
|
||||||
|
});
|
||||||
links = loadedLinks;
|
|
||||||
} else {
|
|
||||||
console.error('Erreur de chargement des données depuis la base.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
links = loadedLinks;
|
||||||
|
} else {
|
||||||
|
console.error('Erreur de chargement des données depuis la base.');
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Willow>
|
<Willow>
|
||||||
<Gantt {tasks} {links} {scales} />
|
<Gantt {tasks} {links} {scales} {options} />
|
||||||
</Willow>
|
</Willow>
|
||||||
|
|||||||
@ -25,13 +25,14 @@
|
|||||||
return {
|
return {
|
||||||
id: index + 1,
|
id: index + 1,
|
||||||
text: entry['Nom Ticket'],
|
text: entry['Nom Ticket'],
|
||||||
start: start.toISOString().split('T')[0], // ⬅️ Converti en string
|
start: start.toISOString().split('T')[0],
|
||||||
end: end.toISOString().split('T')[0], // ⬅️ Converti en string
|
end: end.toISOString().split('T')[0],
|
||||||
duration: Number(entry['Estimation (j)']) || 1,
|
duration: Number(entry['Estimation (j)']) || 1,
|
||||||
progress: Number(entry['RAF']) || 0,
|
progress: Number(entry['RAF']) || 0,
|
||||||
type: 'task',
|
type: 'task',
|
||||||
parent: null,
|
parent: null,
|
||||||
lazy: false
|
assignedTo: entry['assignedTo'] || '', // ✅ Ajout ici
|
||||||
|
lazy_loading: false // ✅ Correspond à la colonne renommée
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,6 +54,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<h2>Importer un fichier CSV</h2>
|
<h2>Importer un fichier CSV</h2>
|
||||||
<input type="file" accept=".csv" on:change={handleFile} />
|
<input type="file" accept=".csv" on:change={handleFile} />
|
||||||
|
|
||||||
|
|||||||
201
svar-gantt-app/src/routes/planning/+page.svelte
Normal file
201
svar-gantt-app/src/routes/planning/+page.svelte
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Papa from 'papaparse';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { writable, get } from 'svelte/store';
|
||||||
|
|
||||||
|
const planning = writable<any[]>([]);
|
||||||
|
const dates = writable<string[]>([]);
|
||||||
|
|
||||||
|
let file: File | null = null;
|
||||||
|
|
||||||
|
const saveMessage = writable('');
|
||||||
|
let timeout: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
|
const joursFeries = [
|
||||||
|
'2025-01-01', '2025-04-21', '2025-05-01', '2025-05-08', '2025-05-29',
|
||||||
|
'2025-07-14', '2025-08-15', '2025-11-01', '2025-11-11', '2025-12-25'
|
||||||
|
];
|
||||||
|
|
||||||
|
function isWeekend(date: string) {
|
||||||
|
const d = new Date(date);
|
||||||
|
return d.getDay() === 0 || d.getDay() === 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isJourFerie(date: string) {
|
||||||
|
return joursFeries.includes(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateFr(dateStr: string) {
|
||||||
|
const d = new Date(dateStr);
|
||||||
|
return d.toLocaleDateString('fr-FR', {
|
||||||
|
weekday: 'short',
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'short'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSaveMessage(message: string = '✅ Sauvegarde enregistrée') {
|
||||||
|
saveMessage.set(message);
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => saveMessage.set(''), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const res = await fetch('/api/planning');
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.length) {
|
||||||
|
const allDates = new Set<string>();
|
||||||
|
data.forEach(row => {
|
||||||
|
Object.keys(row.disponibilites).forEach(d => allDates.add(d));
|
||||||
|
});
|
||||||
|
|
||||||
|
const sortedDates = Array.from(allDates).sort();
|
||||||
|
dates.set(sortedDates);
|
||||||
|
planning.set(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleFileUpload() {
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
Papa.parse(file, {
|
||||||
|
header: true,
|
||||||
|
skipEmptyLines: true,
|
||||||
|
complete: async (result) => {
|
||||||
|
const rows = result.data as any[];
|
||||||
|
const colonnesDates = Object.keys(rows[0]).filter(k => /^\d/.test(k));
|
||||||
|
dates.set(colonnesDates);
|
||||||
|
|
||||||
|
const structured = rows.map(row => ({
|
||||||
|
ressource: row.ressource,
|
||||||
|
profil: row.profil,
|
||||||
|
disponibilites: colonnesDates.reduce((acc, date) => {
|
||||||
|
acc[date] = parseFloat(row[date] || 0);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, number>)
|
||||||
|
}));
|
||||||
|
|
||||||
|
planning.set(structured);
|
||||||
|
|
||||||
|
const payload = [];
|
||||||
|
for (const row of structured) {
|
||||||
|
for (const date of colonnesDates) {
|
||||||
|
payload.push({
|
||||||
|
ressource: row.ressource,
|
||||||
|
profil: row.profil,
|
||||||
|
date,
|
||||||
|
disponibilite: row.disponibilites[date]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch('/api/planning', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
showSaveMessage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateCell(rowIndex: number, date: string, value: string) {
|
||||||
|
const val = parseFloat(value);
|
||||||
|
if (![0, 0.5, 1].includes(val)) return;
|
||||||
|
|
||||||
|
planning.update(current => {
|
||||||
|
current[rowIndex].disponibilites[date] = val;
|
||||||
|
return current;
|
||||||
|
});
|
||||||
|
|
||||||
|
const row = get(planning)[rowIndex];
|
||||||
|
|
||||||
|
await fetch('/api/planning', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify([
|
||||||
|
{
|
||||||
|
ressource: row.ressource,
|
||||||
|
profil: row.profil,
|
||||||
|
date,
|
||||||
|
disponibilite: val
|
||||||
|
}
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
showSaveMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalByDate(date: string) {
|
||||||
|
const rows = get(planning);
|
||||||
|
return rows.reduce((sum, r) => sum + (r.disponibilites[date] || 0), 0);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1 class="text-xl font-bold mb-4">📅 Planning Ressources</h1>
|
||||||
|
|
||||||
|
<input type="file" accept=".csv" on:change={(e) => file = e.target.files?.[0]} />
|
||||||
|
<button on:click={handleFileUpload} class="ml-2 bg-blue-600 text-white px-3 py-1 rounded">Charger</button>
|
||||||
|
|
||||||
|
{#if $planning.length > 0}
|
||||||
|
<table class="mt-6 border-collapse border w-full text-sm text-center">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="border px-2 py-1 bg-white text-left">Ressource</th>
|
||||||
|
<th class="border px-2 py-1 bg-white text-left">Profil</th>
|
||||||
|
{#each $dates as date}
|
||||||
|
<th
|
||||||
|
class="border px-2 py-1"
|
||||||
|
class:bg-red-100={isJourFerie(date)}
|
||||||
|
class:bg-gray-100={!isJourFerie(date) && isWeekend(date)}
|
||||||
|
>
|
||||||
|
{formatDateFr(date)}
|
||||||
|
</th>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each $planning as row, i}
|
||||||
|
<tr>
|
||||||
|
<td class="border px-2 py-1 text-left">{row.ressource}</td>
|
||||||
|
<td class="border px-2 py-1 text-left">{row.profil}</td>
|
||||||
|
{#each $dates as date}
|
||||||
|
<td class="border p-0">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.5"
|
||||||
|
value={row.disponibilites[date]}
|
||||||
|
class="w-full text-center py-1 bg-transparent"
|
||||||
|
on:change={(e) => updateCell(i, date, e.target.value)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td class="border font-semibold text-right px-2 py-1" colspan="2">Total</td>
|
||||||
|
{#each $dates as date}
|
||||||
|
<td class="border px-2 py-1 font-semibold">
|
||||||
|
{totalByDate(date)}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $saveMessage}
|
||||||
|
<div
|
||||||
|
class="fixed bottom-4 right-4 bg-green-600 text-white px-4 py-2 rounded shadow-lg transition-opacity duration-300"
|
||||||
|
>
|
||||||
|
{$saveMessage}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
14
tickets.csv
14
tickets.csv
@ -1,7 +1,7 @@
|
|||||||
ID Ticket,Nom Ticket,Estimation (j),Sprint,Statut,RAF
|
ID Ticket,Nom Ticket,Estimation (j),Sprint,Statut,RAF,assignedTo
|
||||||
T-001,Authentification,5,Sprint 1,Terminé,0
|
T-001,Authentification,5,Sprint 1,Terminé,0,Simon BOYER
|
||||||
T-002,Page d’accueil,3,Sprint 1,En cours,2
|
T-002,Page d’accueil,3,Sprint 1,En cours,2,Simon BOYER
|
||||||
T-003,Système de recherche,4,Sprint 2,À faire,4
|
T-003,Système de recherche,4,Sprint 2,À faire,4,Fatima BROUM
|
||||||
T-005,Sprint 3,3,Sprint 1,En cours,2
|
T-005,Sprint 3,3,Sprint 1,En cours,2,Fatima BROUM
|
||||||
T-004,Configuration BO,4,Sprint 2,À faire,4
|
T-004,Configuration BO,4,Sprint 2,À faire,4,Fatima BROUM
|
||||||
T-006,Endpoint à configurer sur api,1,Sprint 2,À faire,1
|
T-006,Endpoint à configurer sur api,1,Sprint 2,À faire,1,Fatima BROUM
|
||||||
|
Reference in New Issue
Block a user