JFIF $ d d 7 * Z qn )ZP 0 !1 A "#a$2Qr D8 a Ri [f \m I ykI w0 c u F c Rı?lO7 к _f˓ [C $殷WF< _W ԣ s Kc ëI zyQy /_LK ℂ ;C",p F A : /] =H ~ , l s / 9 ć: [=/#f ; ) x { ٛE Q )~ = 𘙲r*2~ a _V = ' ku m F D } KYYC )( { * g& f `툪 r y ` = ^ c J. I ]( * `wq 1dđ #̩ ͑ 0 ; H ] u 搂 @:~ ו K L N s h } O I R* 8 :2 ! lD JV o ( 3 = M ( z Ȱ+i *N A r6K n S l ) ! J J Ӂ* % ݉?|D } d5: eP0 R; {$X' x F @ .ÊB {,WJ uQ ɲRI ;9 Q E琯 6 2fT .DU J ;*c P A\IL Nj! J ۱+ O\ ͔ ] ޒ S J ȧ c%A N ol ՎprU L Zԛe r E2= X D Xg VQeӓ k y P7 U* o m Q I s ,K `) 6\G3t?pgjr m ۛجw luGt f h 9uyP 0D;Uڽ"OXl i f $ ) &|ML 0 Z rm1 [ H XP l P R0 'G=i 2N +0 e 2 ]] 9 VT P O 7 h(F * 癈 ' = Q VZD F,d ߬ ~ T X G[ `le 69C R(!S2! P < 0 x < !1AQ "Raq 02Br #S CTb ? Ζ "]mH5WR 7 k.ۛ ! } Q~ + yԏ z |@ T20 S~ K e k *zF f^2X *( @ 8 r ? C I uI|֓>^Ex LgNU Y+{.R Ѫ τV YT D I 6 2 '8Y2 7'\T P.6 d &˦ @V q i |8 -O Ε ] ʔ U= T L 8= ;6c | !qfF3 a ů& ~$ l} 'NW U s$U k^S V : U#6w + +s &r+n ڐ { @ 29gL u " T Ù M=6 (^ "7 r}=6Yݾ lC u hquymp Ǧ G jhs ǜNl ɻ} o 7 #S6 a w 4 ! OS r D 5 7 % | ?x> L | / nD 6?/8 w # [ ) L 7 +6〼T A T g ! %5 Mm Z /c -{1_Je "| ^$ 'O & ޱմ < H vNqϡQ$ < 6 &Nݛ ֨tǻN (9 n ru~'όN sMG& ,uH Kw *Ī 5@ u6 BԒ i YN * f 0 _ GhKW u lT <nj- 0{ M L~ | /a v\ E = ?1 \ɟuq Ђ Y O a K ޡP q $ H8o Q˼EV ~ k 0 h' L Pp M -M" m^ !6l nմ 쮝C U T . ! :vMǨb h ډ Vc r @~p # !1QAa q ? AL0L x LU 4 d ; x " 16ad0 7.\ߋ 0 = P l v/ " c A Ay A|2pO8"d MV #1] = \ " ˡ ] k v I9 d ۓ & q ) ǐq E ꞅ v "P )q X &Q З non]я u ג./ ; gZ$ M QР 2M ]h ni 0 P $ێ 3CɏGjώy : m c # S_ l@ '_ \m|C 2`K _ 1 cIvGc $? PCQ S+D ? K aײ k Fr b 8 ύ NEدg ĝ U 3 6 #Ik+ v { v i ( ވ ? a e) X xy/*@Q Ǽ Ծ ) $ IWO l c G; !i k{ gOŀ L ('x T rb $ w) R $ & N1E td U3Uȉ1p M"N* (DNy d96.(jQ) X 5 c QɎMy W ? Q *!R > 6=7)Xj 5`J ] e 8%t ! + ' ! 1Q 5 !1 AQaq ё #2 "0BRb ? G t^ ## .l lQ T $ v , ,m 㵜5u bV =sY+ @d { N ! dn O < .- B; _w J t 6;Q Jd. Q c%p{ 1 ,sND dF H I0 Гo X ш e黅X ۢ F :)[ F G X ƹ / w _c MeD ,ʡc c . W D tA $ j@ : ) - #u c1< @ۗ9F ) KJ- hpP] _ x [ q B lb p ʖ w q " LFGd ƶ * s +ډ_Zc " ? % t[I P 6 J ]# =ɺ V v v CGs Gh1 > )6 |e y ? L ӣ m,4 G W Ui ` ] uJVo VDG< SB 6ϏQ @ TiU l y O U 0 kfV ~ ~}S Z @ *WU Ui ## ; s /[ = ! 7}" W N ] ' (L ! ~ y5 g9T̅J kbM' +s :S +B) v @ M j e C f j E 0 Y\Qn z G 1 д~ W o{T 9?` Rm yhsy3 !HAD ] m c1 ~ 2 L S u7x T ;j$ `} 4-> L# vzŏ I L S ֭ T { r jGK C ; b pU= - ` Bs K. S F w4 M q] Z dH S 0 ) tLg
<?php
$statut = (string) ($projet['statut'] ?? '');
$autoEditReportId = isset($_GET['report_id']) ? (int) $_GET['report_id'] : 0;
$formatDate = static function (?string $value): string {
if (!$value) {
return 'Non renseignée';
}
$timestamp = strtotime($value);
if (!$timestamp) {
return (string) $value;
}
return strlen(trim((string) $value)) <= 10 ? date('d/m/Y', $timestamp) : date('d/m/Y H:i', $timestamp);
};
?>
<div class="crm-main crm-projet-page">
<div class="crm-projet-page-header">
<div>
<div class="crm-projet-breadcrumbs"><a href="index.php?page=projets">Projets</a> / <a href="index.php?page=projets&action=view&id=<?= htmlspecialchars((string) $projet['id']) ?>"><?= htmlspecialchars((string) ($projet['titre'] ?? 'Projet')) ?></a> / <span>Modifier</span></div>
<h1>Modifier le projet</h1>
<p class="crm-projet-page-subtitle">Mettez à jour les infos du projet, la datasheet et ajoutez des lignes de compte-rendu avec barre d'outils.</p>
</div>
<div class="crm-projet-page-actions">
<a href="index.php?page=projets&action=view&id=<?= htmlspecialchars((string) $projet['id']) ?>" class="button">Voir le projet</a>
<a href="index.php?page=projets" class="button">Retour aux projets</a>
</div>
</div>
<?php if (isset($_GET['saved'])): ?>
<div class="crm-projet-alert crm-projet-alert-success">Les informations du projet ont été enregistrées.</div>
<?php endif; ?>
<?php if (isset($_GET['report_saved'])): ?>
<div class="crm-projet-alert crm-projet-alert-success">Le compte-rendu a été ajouté au projet.</div>
<?php endif; ?>
<?php if (isset($_GET['report_updated'])): ?>
<div class="crm-projet-alert crm-projet-alert-success">Le compte-rendu a été mis à jour.</div>
<?php endif; ?>
<?php if (isset($_GET['missing_title'])): ?>
<div class="crm-projet-alert crm-projet-alert-warning">Le titre du projet est obligatoire.</div>
<?php endif; ?>
<?php if (isset($_GET['empty_report'])): ?>
<div class="crm-projet-alert crm-projet-alert-warning">Le compte-rendu est vide. Ajoutez du contenu avant de valider.</div>
<?php endif; ?>
<?php if (isset($_GET['report_missing'])): ?>
<div class="crm-projet-alert crm-projet-alert-warning">Le compte-rendu à modifier est introuvable.</div>
<?php endif; ?>
<!-- Retroplanning alerts removed -->
<div class="crm-projet-sections">
<section class="crm-projet-panel crm-projet-panel-full">
<h2>Édition du projet</h2>
<form method="post" action="index.php?page=projets&action=save" class="crm-projet-rich-form">
<input type="hidden" name="projet_id" value="<?= htmlspecialchars((string) $projet['id']) ?>">
<input type="hidden" name="redirect_to" value="edit">
<div class="crm-projet-form-grid">
<label class="crm-projet-field" style="position:relative;">
<span>Client</span>
<input type="text" id="crm-projet-client-autocomplete" autocomplete="off" placeholder="Nom, prénom ou entreprise..." value="<?php
if (!empty($projet['client_prenom']) || !empty($projet['client_nom'])) {
echo htmlspecialchars(trim(($projet['client_prenom'] ?? '') . ' ' . ($projet['client_nom'] ?? '')));
}
?>">
<input type="hidden" name="client_id" id="crm-projet-client-id" value="<?= htmlspecialchars((string) ($projet['client_id'] ?? '')) ?>">
</label>
<label class="crm-projet-field">
<span>Titre du projet</span>
<input type="text" name="titre" value="<?= htmlspecialchars((string) ($projet['titre'] ?? '')) ?>" required>
</label>
<label class="crm-projet-field">
<span>Date projet</span>
<input type="date" name="date_projet" value="<?= htmlspecialchars((string) ($projet['date_projet'] ?? date('Y-m-d'))) ?>">
</label>
<label class="crm-projet-field">
<span>Montant estimé (€)</span>
<input type="number" step="0.01" name="montant" value="<?= htmlspecialchars((string) ($projet['montant'] ?? '')) ?>">
</label>
<label class="crm-projet-field">
<span>Statut</span>
<select name="statut">
<option value="Proposition" <?= $statut === 'Proposition' ? 'selected' : '' ?>>Proposition</option>
<option value="En cours" <?= $statut === 'En cours' ? 'selected' : '' ?>>En cours</option>
<option value="Terminé" <?= $statut === 'Terminé' ? 'selected' : '' ?>>Terminé</option>
</select>
</label>
<label class="crm-projet-field crm-projet-field-full">
<span>Description</span>
<textarea name="description" rows="5"><?= htmlspecialchars((string) ($projet['description'] ?? '')) ?></textarea>
</label>
</div>
<div class="crm-projet-form-actions">
<button type="submit" class="button">Enregistrer le projet</button>
</div>
</form>
</section>
<script>
window.crmClientsList = <?php echo json_encode(array_map(function($c) {
return [
'id' => $c['id'],
'nom' => $c['nom'],
'prenom' => $c['prenom'] ?? '',
'entreprise' => $c['entreprise'] ?? ''
];
}, $clients ?? [])); ?>;
</script>
<script src="assets/js/projets.js"></script>
<section class="crm-projet-panel crm-projet-panel-full crm-projet-reports-panel">
<h2>Compte-rendus</h2>
<div class="crm-projet-report-composer">
<form method="post" action="index.php?page=projets&action=save-report" class="crm-projet-rich-form crm-projet-report-form" id="crm-projet-report-form">
<input type="hidden" name="projet_id" value="<?= htmlspecialchars((string) $projet['id']) ?>">
<input type="hidden" name="report_id" id="crm-report-id" value="">
<div class="crm-projet-report-form-header">
<div>
<h3 id="crm-report-form-title">Ajouter un compte-rendu</h3>
<p class="crm-projet-page-subtitle" id="crm-report-form-subtitle">Ajoutez autant de comptes-rendus que nécessaire. Ils s'affichent ensuite sous forme de cards dans l'historique.</p>
</div>
<button type="button" class="button crm-projet-report-cancel" id="crm-report-cancel" style="display:none;">Annuler la modification</button>
</div>
<label class="crm-projet-field crm-projet-field-full">
<span>Contenu</span>
<textarea name="contenu" rows="10" class="crm-projet-rich-editor" id="crm-report-editor"></textarea>
</label>
<div class="crm-projet-form-actions">
<button type="submit" class="button" id="crm-report-submit">Ajouter ce compte-rendu</button>
</div>
</form>
</div>
<div class="crm-projet-section-block">
<h3>Historique des comptes-rendu</h3>
<?php if (!empty($comptesRendus)): ?>
<div class="crm-projet-report-list">
<?php foreach ($comptesRendus as $compteRendu): ?>
<article class="crm-projet-report-item" id="crm-report-card-<?= htmlspecialchars((string) $compteRendu['id']) ?>">
<div class="crm-projet-report-meta">
<span>Ajouté le <?= htmlspecialchars($formatDate($compteRendu['created_at'] ?? null)) ?></span>
<div class="crm-projet-report-meta-actions">
<span>#<?= htmlspecialchars((string) $compteRendu['id']) ?></span>
<button
type="button"
class="button crm-btn-preview-report"
data-preview-target="crm-report-preview-<?= htmlspecialchars((string) $compteRendu['id']) ?>"
>Aperçu</button>
<button
type="button"
class="button crm-btn-edit-report"
data-report-id="<?= htmlspecialchars((string) $compteRendu['id']) ?>"
data-report-preview="crm-report-preview-<?= htmlspecialchars((string) $compteRendu['id']) ?>"
data-report-content="<?= htmlspecialchars(base64_encode((string) $compteRendu['contenu']), ENT_QUOTES) ?>"
>Modifier</button>
</div>
</div>
<div class="crm-projet-rich-content crm-projet-report-preview" id="crm-report-preview-<?= htmlspecialchars((string) $compteRendu['id']) ?>" hidden><?= $compteRendu['contenu'] ?></div>
</article>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="crm-projet-empty">Aucun compte-rendu n'a encore été ajouté pour ce projet.</div>
<?php endif; ?>
</div>
</section>
<!-- Datasheet/retroplanning removed -->
</div>
<script src="assets/vendor/tinymce/tinymce.min.js"></script>
<!-- Tabulator loader: runtime-detect engine support and load safely, otherwise fallback -->
<script>
(function(){
function supportsRestParams(){
try { new Function('...a', 'return a.length;'); return true; } catch(e) { return false; }
}
var localCss = 'assets/vendor/tabulator/tabulator.min.css';
var localJs = 'assets/vendor/tabulator/tabulator.min.js';
var cdnCss = 'https://unpkg.com/tabulator-tables@5.5.0/dist/css/tabulator.min.css';
var cdnJs = 'https://unpkg.com/tabulator-tables@5.5.0/dist/js/tabulator.min.js';
// Always load a CSS file (prefer local when available)
(function(){
var link = document.createElement('link'); link.rel = 'stylesheet';
// prefer local if exists (best-effort check via fetch)
fetch(localCss, { method: 'HEAD' }).then(function(res){ if (res.ok) link.href = localCss; else link.href = cdnCss; }).catch(function(){ link.href = cdnCss; }).finally(function(){ document.head.appendChild(link); });
})();
if (!supportsRestParams()) {
// Environment too old for Tabulator ES build — set flag so fallback will initialize
window.__tabulator_not_supported = true;
return;
}
// Dynamically load Tabulator (try local first, fallback to CDN on error)
var script = document.createElement('script');
script.src = localJs;
script.async = false;
script.onload = function(){ /* loaded */ };
script.onerror = function(){
var cdn = document.createElement('script'); cdn.src = cdnJs; cdn.async = false; document.head.appendChild(cdn);
};
document.head.appendChild(script);
})();
</script>
<!-- datasource scripts removed -->
<script>
(function () {
if (!window.tinymce) {
return;
}
window.tinymce.init({
selector: 'textarea.crm-projet-rich-editor',
menubar: false,
branding: false,
promotion: false,
resize: true,
height: 280,
plugins: 'lists link autoresize',
toolbar: 'bold italic underline | bullist numlist | link removeformat',
content_style: "body { font-family: 'Albert Sans', Arial, sans-serif; font-size: 14px; color: #232946; }"
});
document.querySelectorAll('.crm-projet-rich-form').forEach(function (form) {
form.addEventListener('submit', function () {
if (window.tinymce && typeof window.tinymce.triggerSave === 'function') {
window.tinymce.triggerSave();
}
});
});
const reportIdInput = document.getElementById('crm-report-id');
const reportCancel = document.getElementById('crm-report-cancel');
const reportTitle = document.getElementById('crm-report-form-title');
const reportSubtitle = document.getElementById('crm-report-form-subtitle');
const reportSubmit = document.getElementById('crm-report-submit');
const reportEditorId = 'crm-report-editor';
const autoEditReportId = <?= json_encode($autoEditReportId) ?>;
const projectId = <?= json_encode((int)$projet['id']) ?>;
function getReportEditor() {
return window.tinymce ? window.tinymce.get(reportEditorId) : null;
}
function decodeBase64Utf8(value) {
if (!value) {
return '';
}
try {
return decodeURIComponent(Array.prototype.map.call(window.atob(value), function (char) {
return '%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
} catch (error) {
try {
return window.atob(value);
} catch (decodeError) {
return '';
}
}
}
function resetReportForm() {
if (reportIdInput) {
reportIdInput.value = '';
}
if (reportTitle) {
reportTitle.textContent = 'Ajouter un compte-rendu';
}
if (reportSubtitle) {
reportSubtitle.textContent = 'Ajoutez autant de comptes-rendus que nécessaire. Ils s\'affichent ensuite sous forme de cards dans l\'historique.';
}
if (reportSubmit) {
reportSubmit.textContent = 'Ajouter ce compte-rendu';
}
if (reportCancel) {
reportCancel.style.display = 'none';
}
const reportEditor = getReportEditor();
if (reportEditor) {
reportEditor.setContent('');
}
}
document.querySelectorAll('.crm-btn-edit-report').forEach(function (button) {
button.addEventListener('click', function () {
const reportEditor = getReportEditor();
const encoded = this.getAttribute('data-report-content') || '';
const content = decodeBase64Utf8(encoded);
const previewId = this.getAttribute('data-report-preview') || '';
if (reportIdInput) {
reportIdInput.value = this.getAttribute('data-report-id') || '';
}
if (reportTitle) {
reportTitle.textContent = 'Modifier un compte-rendu';
}
if (reportSubtitle) {
reportSubtitle.textContent = 'Vous modifiez une card existante de l\'historique.';
}
if (reportSubmit) {
reportSubmit.textContent = 'Mettre à jour ce compte-rendu';
}
if (reportCancel) {
reportCancel.style.display = 'inline-flex';
}
if (reportEditor) {
reportEditor.setContent(content);
reportEditor.focus();
}
if (previewId) {
const preview = document.getElementById(previewId);
if (preview) {
preview.hidden = false;
}
const previewButton = document.querySelector('.crm-btn-preview-report[data-preview-target="' + previewId + '"]');
if (previewButton) {
previewButton.textContent = 'Masquer';
}
}
const reportForm = document.getElementById('crm-projet-report-form');
if (reportForm) {
reportForm.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
});
document.querySelectorAll('.crm-btn-preview-report').forEach(function (button) {
button.addEventListener('click', function () {
const previewId = this.getAttribute('data-preview-target') || '';
const preview = previewId ? document.getElementById(previewId) : null;
if (!preview) {
return;
}
const isHidden = preview.hasAttribute('hidden');
if (isHidden) {
preview.removeAttribute('hidden');
this.textContent = 'Masquer';
} else {
preview.setAttribute('hidden', 'hidden');
this.textContent = 'Aperçu';
}
});
});
if (reportCancel) {
reportCancel.addEventListener('click', resetReportForm);
}
if (autoEditReportId) {
const autoButton = document.querySelector('.crm-btn-edit-report[data-report-id="' + String(autoEditReportId) + '"]');
if (autoButton) {
autoButton.click();
}
}
// Retroplanning UI and scripts removed per request
})();
</script>
</div>