OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)
This commit is contained in:
12
app/Domain/Logicmodelcanvas/Controllers/BoardDialog.php
Normal file
12
app/Domain/Logicmodelcanvas/Controllers/BoardDialog.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Controller / Board Dialog
|
||||
*/
|
||||
|
||||
namespace Leantime\Domain\Logicmodelcanvas\Controllers;
|
||||
|
||||
class BoardDialog extends \Leantime\Domain\Canvas\Controllers\BoardDialog
|
||||
{
|
||||
protected const CANVAS_NAME = 'logicmodel';
|
||||
}
|
||||
12
app/Domain/Logicmodelcanvas/Controllers/DelCanvas.php
Normal file
12
app/Domain/Logicmodelcanvas/Controllers/DelCanvas.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Controller / Delete Canvas
|
||||
*/
|
||||
|
||||
namespace Leantime\Domain\Logicmodelcanvas\Controllers;
|
||||
|
||||
class DelCanvas extends \Leantime\Domain\Canvas\Controllers\DelCanvas
|
||||
{
|
||||
protected const CANVAS_NAME = 'logicmodel';
|
||||
}
|
||||
12
app/Domain/Logicmodelcanvas/Controllers/DelCanvasItem.php
Normal file
12
app/Domain/Logicmodelcanvas/Controllers/DelCanvasItem.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Controller / Delete Canvas Item
|
||||
*/
|
||||
|
||||
namespace Leantime\Domain\Logicmodelcanvas\Controllers;
|
||||
|
||||
class DelCanvasItem extends \Leantime\Domain\Canvas\Controllers\DelCanvasItem
|
||||
{
|
||||
protected const CANVAS_NAME = 'logicmodel';
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Controller / Edit Canvas Comment
|
||||
*/
|
||||
|
||||
namespace Leantime\Domain\Logicmodelcanvas\Controllers;
|
||||
|
||||
class EditCanvasComment extends \Leantime\Domain\Canvas\Controllers\EditCanvasComment
|
||||
{
|
||||
protected const CANVAS_NAME = 'logicmodel';
|
||||
}
|
||||
71
app/Domain/Logicmodelcanvas/Controllers/EditCanvasItem.php
Normal file
71
app/Domain/Logicmodelcanvas/Controllers/EditCanvasItem.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Controller / Edit Canvas Item
|
||||
*/
|
||||
|
||||
namespace Leantime\Domain\Logicmodelcanvas\Controllers;
|
||||
|
||||
use Leantime\Core\Auth\Permissions\RequiresPermission;
|
||||
use Leantime\Domain\Blueprints\Permissions\BlueprintsPermissions;
|
||||
use Leantime\Domain\Blueprints\Services\Blueprints as BlueprintsService;
|
||||
|
||||
class EditCanvasItem extends \Leantime\Domain\Canvas\Controllers\EditCanvasItem
|
||||
{
|
||||
protected const CANVAS_NAME = 'logicmodel';
|
||||
|
||||
/**
|
||||
* Persist the Stage (box) and Priority (impact) selects on save.
|
||||
*
|
||||
* The shared Canvas save path (parent::post → service::updateCanvasItem) never
|
||||
* writes `box` and does not forward `impact`, so without this the Stage
|
||||
* dropdown would be a no-op and saving would reset Priority. Patch both
|
||||
* after the base save for existing items; new items already receive their
|
||||
* box on insert via the create path.
|
||||
*
|
||||
* The base post() already EDIT-authorizes the item against its real project; the extra
|
||||
* patch is routed through the same project-authorized service method for consistency.
|
||||
*
|
||||
* @param array $params Request parameters
|
||||
*/
|
||||
#[RequiresPermission(BlueprintsPermissions::EDIT, entityScoped: true)]
|
||||
public function post($params)
|
||||
{
|
||||
$response = parent::post($params);
|
||||
|
||||
$isExistingItemSave = isset($params['changeItem'])
|
||||
&& ! empty($params['itemId'])
|
||||
&& ! empty($params['description']);
|
||||
|
||||
if ($isExistingItemSave) {
|
||||
$patch = [];
|
||||
|
||||
if (! empty($params['box'])) {
|
||||
$patch['box'] = $params['box'];
|
||||
}
|
||||
|
||||
if (array_key_exists('impact', $params)) {
|
||||
$patch['impact'] = $params['impact'];
|
||||
}
|
||||
|
||||
// Authored-meaning fields. Both are stage-gated in the UI
|
||||
// (Impact only shows starting_picture; Outputs/Outcomes only
|
||||
// show why_this_matters) — the hidden inputs preserve any
|
||||
// pre-existing value so a stage change doesn't silently
|
||||
// erase authored text.
|
||||
if (array_key_exists('why_this_matters', $params)) {
|
||||
$patch['why_this_matters'] = (string) $params['why_this_matters'];
|
||||
}
|
||||
if (array_key_exists('starting_picture', $params)) {
|
||||
$patch['starting_picture'] = (string) $params['starting_picture'];
|
||||
}
|
||||
|
||||
if ($patch !== []) {
|
||||
app()->make(BlueprintsService::class)
|
||||
->patchCanvasItem((int) $params['itemId'], $patch, static::CANVAS_NAME.'canvas');
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
12
app/Domain/Logicmodelcanvas/Controllers/Export.php
Normal file
12
app/Domain/Logicmodelcanvas/Controllers/Export.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Controller / Export Canvas
|
||||
*/
|
||||
|
||||
namespace Leantime\Domain\Logicmodelcanvas\Controllers;
|
||||
|
||||
class Export extends \Leantime\Domain\Canvas\Controllers\Export
|
||||
{
|
||||
protected const CANVAS_NAME = 'logicmodel';
|
||||
}
|
||||
12
app/Domain/Logicmodelcanvas/Controllers/ShowCanvas.php
Normal file
12
app/Domain/Logicmodelcanvas/Controllers/ShowCanvas.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Controller / Show Canvas
|
||||
*/
|
||||
|
||||
namespace Leantime\Domain\Logicmodelcanvas\Controllers;
|
||||
|
||||
class ShowCanvas extends \Leantime\Domain\Canvas\Controllers\ShowCanvas
|
||||
{
|
||||
protected const CANVAS_NAME = 'logicmodel';
|
||||
}
|
||||
13
app/Domain/Logicmodelcanvas/Js/logicmodelCanvasController.js
Normal file
13
app/Domain/Logicmodelcanvas/Js/logicmodelCanvasController.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Logic Model board controller.
|
||||
*
|
||||
* The board renders every stage expanded so the full causal chain
|
||||
* (inputs → activities → outputs → outcomes → impact) is visible and each stage
|
||||
* is independently editable. Canvas link/modal/dropdown wiring is handled by
|
||||
* leantime.canvasController (initialised from the board template), so no
|
||||
* stage-focus behaviour is needed here. Kept as a thin, stable namespace for
|
||||
* plugin extension points (e.g. StrategyPro health/narrative overlays).
|
||||
*/
|
||||
leantime.logicmodelCanvasController = (function () {
|
||||
return {};
|
||||
})();
|
||||
189
app/Domain/Logicmodelcanvas/Repositories/Logicmodelcanvas.php
Normal file
189
app/Domain/Logicmodelcanvas/Repositories/Logicmodelcanvas.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Logic Model Canvas Repository
|
||||
*/
|
||||
|
||||
namespace Leantime\Domain\Logicmodelcanvas\Repositories;
|
||||
|
||||
use Leantime\Domain\Canvas\Repositories\Canvas;
|
||||
|
||||
class Logicmodelcanvas extends Canvas
|
||||
{
|
||||
/**
|
||||
* Canvas identifier used for DB type and URL routing.
|
||||
*/
|
||||
protected const CANVAS_NAME = 'logicmodel';
|
||||
|
||||
/**
|
||||
* Stage definitions for the five-stage causal chain.
|
||||
* Referenced by the StrategyPro plugin via Logicmodelcanvas::STAGES.
|
||||
*/
|
||||
public const STAGES = [
|
||||
1 => [
|
||||
'key' => 'inputs',
|
||||
'title' => 'box.logicmodel.inputs',
|
||||
'subtitle' => 'box.logicmodel.inputs_subtitle',
|
||||
'icon' => 'fa-arrow-right-to-bracket',
|
||||
'color' => '#4A85B5',
|
||||
'bg' => '#EDF3F8',
|
||||
],
|
||||
2 => [
|
||||
'key' => 'activities',
|
||||
'title' => 'box.logicmodel.activities',
|
||||
'subtitle' => 'box.logicmodel.activities_subtitle',
|
||||
'icon' => 'fa-gears',
|
||||
'color' => '#3E937A',
|
||||
'bg' => '#ECF6F2',
|
||||
],
|
||||
3 => [
|
||||
'key' => 'outputs',
|
||||
'title' => 'box.logicmodel.outputs',
|
||||
'subtitle' => 'box.logicmodel.outputs_subtitle',
|
||||
'icon' => 'fa-boxes-stacked',
|
||||
'color' => '#C09035',
|
||||
'bg' => '#FBF5EA',
|
||||
],
|
||||
4 => [
|
||||
'key' => 'outcomes',
|
||||
'title' => 'box.logicmodel.outcomes',
|
||||
'subtitle' => 'box.logicmodel.outcomes_subtitle',
|
||||
'icon' => 'fa-chart-line',
|
||||
'color' => '#8E6AAD',
|
||||
'bg' => '#F2EDF8',
|
||||
],
|
||||
5 => [
|
||||
'key' => 'impact',
|
||||
'title' => 'box.logicmodel.impact',
|
||||
'subtitle' => 'box.logicmodel.impact_subtitle',
|
||||
'icon' => 'fa-bullseye',
|
||||
'color' => '#2D7D5E',
|
||||
'bg' => '#EAF5F0',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Board framework templates.
|
||||
* Referenced by the StrategyPro plugin via Logicmodelcanvas::TEMPLATES.
|
||||
*/
|
||||
public const TEMPLATES = [
|
||||
'standard' => [
|
||||
'key' => 'standard',
|
||||
'title' => 'Standard Logic Model',
|
||||
'description' => 'Classic five-column logic model for program planning.',
|
||||
],
|
||||
'toc' => [
|
||||
'key' => 'toc',
|
||||
'title' => 'Theory of Change',
|
||||
'description' => 'Backwards-mapping from impact to inputs.',
|
||||
],
|
||||
'results' => [
|
||||
'key' => 'results',
|
||||
'title' => 'Results Framework',
|
||||
'description' => 'Focus on outputs, outcomes, and impact measurement.',
|
||||
],
|
||||
'pathway' => [
|
||||
'key' => 'pathway',
|
||||
'title' => 'Impact Pathway',
|
||||
'description' => 'Causal chain emphasising linkages between stages.',
|
||||
],
|
||||
'program' => [
|
||||
'key' => 'program',
|
||||
'title' => 'Program Logic',
|
||||
'description' => 'Program-level view for multi-project portfolios.',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Icon associated with canvas.
|
||||
*/
|
||||
protected string $icon = 'fa-diagram-project';
|
||||
|
||||
/**
|
||||
* Canvas element box types (one per stage).
|
||||
*
|
||||
* @var array<string, array{icon: string, title: string}>
|
||||
*/
|
||||
protected array $canvasTypes = [
|
||||
'lm_inputs' => ['icon' => 'fa-arrow-right-to-bracket', 'title' => 'box.logicmodel.inputs'],
|
||||
'lm_activities' => ['icon' => 'fa-gears', 'title' => 'box.logicmodel.activities'],
|
||||
'lm_outputs' => ['icon' => 'fa-boxes-stacked', 'title' => 'box.logicmodel.outputs'],
|
||||
'lm_outcomes' => ['icon' => 'fa-chart-line', 'title' => 'box.logicmodel.outcomes'],
|
||||
'lm_impact' => ['icon' => 'fa-bullseye', 'title' => 'box.logicmodel.impact'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Hypothesis status labels (same keys as Canvas base, custom titles).
|
||||
*
|
||||
* @var array<string, array{icon: string, color: string, title: string, dropdown: string, active: bool}>
|
||||
*/
|
||||
protected array $statusLabels = [
|
||||
'status_draft' => ['icon' => 'fa-circle-question', 'color' => 'blue', 'title' => 'logicmodel.status.draft', 'dropdown' => 'info', 'active' => true],
|
||||
'status_review' => ['icon' => 'fa-circle-exclamation', 'color' => 'orange', 'title' => 'logicmodel.status.review', 'dropdown' => 'warning', 'active' => true],
|
||||
'status_valid' => ['icon' => 'fa-circle-check', 'color' => 'green', 'title' => 'logicmodel.status.validated', 'dropdown' => 'success', 'active' => true],
|
||||
'status_hold' => ['icon' => 'fa-circle-pause', 'color' => 'red', 'title' => 'logicmodel.status.paused', 'dropdown' => 'danger', 'active' => true],
|
||||
'status_invalid' => ['icon' => 'fa-circle-xmark', 'color' => 'red', 'title' => 'logicmodel.status.invalid', 'dropdown' => 'danger', 'active' => true],
|
||||
];
|
||||
|
||||
/**
|
||||
* Relates labels (not used for logic model).
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected array $relatesLabels = [];
|
||||
|
||||
/**
|
||||
* Data labels for the canvas item dialog.
|
||||
*
|
||||
* @var array<int, array{title: string, field: string, active: bool}>
|
||||
*/
|
||||
protected array $dataLabels = [
|
||||
1 => ['title' => 'logicmodel.field.description', 'field' => 'conclusion', 'active' => true],
|
||||
2 => ['title' => 'logicmodel.field.evidence', 'field' => 'assumptions', 'active' => true],
|
||||
3 => ['title' => 'label.data', 'field' => 'data', 'active' => false],
|
||||
];
|
||||
|
||||
/**
|
||||
* Map box field values to stage keys.
|
||||
*/
|
||||
private const BOX_TO_STAGE = [
|
||||
'lm_inputs' => 'inputs',
|
||||
'lm_activities' => 'activities',
|
||||
'lm_outputs' => 'outputs',
|
||||
'lm_outcomes' => 'outcomes',
|
||||
'lm_impact' => 'impact',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get canvas items grouped by stage key.
|
||||
*
|
||||
* Returns an associative array keyed by stage name (inputs, activities, etc.)
|
||||
* with each value being an array of item rows.
|
||||
* Required by the StrategyPro plugin service.
|
||||
*
|
||||
* @param int $canvasId Canvas board ID
|
||||
* @return array<string, array<int, array<string, mixed>>>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function getItemsByStage(int $canvasId): array
|
||||
{
|
||||
$items = $this->getCanvasItemsById($canvasId);
|
||||
$grouped = [];
|
||||
|
||||
foreach (self::BOX_TO_STAGE as $box => $stageKey) {
|
||||
$grouped[$stageKey] = [];
|
||||
}
|
||||
|
||||
if (is_array($items)) {
|
||||
foreach ($items as $item) {
|
||||
$box = $item['box'] ?? '';
|
||||
if (isset(self::BOX_TO_STAGE[$box])) {
|
||||
$grouped[self::BOX_TO_STAGE[$box]][] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
@include('canvas::canvasComment', ['canvasName' => 'logicmodel'])
|
||||
405
app/Domain/Logicmodelcanvas/Templates/canvasDialog.blade.php
Normal file
405
app/Domain/Logicmodelcanvas/Templates/canvasDialog.blade.php
Normal file
@@ -0,0 +1,405 @@
|
||||
@php
|
||||
use Leantime\Domain\Logicmodelcanvas\Repositories\Logicmodelcanvas;
|
||||
|
||||
$canvasName = 'logicmodel';
|
||||
|
||||
$canvasItem = $tpl->get('canvasItem') ?? [];
|
||||
$canvasItem = array_merge([
|
||||
'id' => '', 'box' => '', 'description' => '', 'conclusion' => '', 'assumptions' => '',
|
||||
'data' => '', 'status' => '', 'relates' => '', 'impact' => '', 'milestoneId' => '',
|
||||
'author' => '', 'authorFirstname' => '', 'authorLastname' => '',
|
||||
'why_this_matters' => '', 'starting_picture' => '',
|
||||
], is_array($canvasItem) ? $canvasItem : []);
|
||||
|
||||
$canvasTypes = $tpl->get('canvasTypes');
|
||||
$hiddenStatusLabels = $tpl->get('statusLabels');
|
||||
$statusLabels = $statusLabels ?? $hiddenStatusLabels;
|
||||
$hiddenRelatesLabels = $tpl->get('relatesLabels');
|
||||
$relatesLabels = $relatesLabels ?? $hiddenRelatesLabels;
|
||||
$dataLabels = $tpl->get('dataLabels');
|
||||
|
||||
$id = ($canvasItem['id'] ?? '') !== '' ? $canvasItem['id'] : '';
|
||||
|
||||
// Resolve stage color for the pill
|
||||
$stages = Logicmodelcanvas::STAGES;
|
||||
$boxKey = $canvasItem['box'] ?? '';
|
||||
$stageColor = '#888';
|
||||
$stageBg = '#f0f0f0';
|
||||
foreach ($stages as $stage) {
|
||||
if ('lm_' . $stage['key'] === $boxKey) {
|
||||
$stageColor = $stage['color'];
|
||||
$stageBg = $stage['bg'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$currentImpact = (string) ($canvasItem['impact'] ?? '');
|
||||
@endphp
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
if (!window.jQuery) {
|
||||
location.href="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas?showModal={{ $canvasItem['id'] }}";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div style="width:1000px; padding-bottom:20px;">
|
||||
|
||||
{{-- Header: stage pill --}}
|
||||
<div style="display:flex; align-items:center; gap:12px; margin-bottom:8px;">
|
||||
<span style="display:inline-flex; align-items:center; gap:5px; padding:4px 14px; border-radius:20px; font-size:var(--font-size-s); font-weight:600; color:{{ $stageColor }}; background:{{ $stageBg }};">
|
||||
<i class="fas {{ $canvasTypes[$canvasItem['box']]['icon'] ?? 'fa-diagram-project' }}"></i>
|
||||
{{ isset($canvasTypes[$canvasItem['box']]) ? $tpl->__($canvasTypes[$canvasItem['box']]['title']) : '' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{!! $tpl->displayNotification() !!}
|
||||
|
||||
<form class="formModal" method="post" action="{{ BASE_URL }}/{{ $canvasName }}canvas/editCanvasItem/{{ $id }}">
|
||||
|
||||
<input type="hidden" value="{{ $tpl->get('currentCanvas') }}" name="canvasId" />
|
||||
<input type="hidden" value="{{ $id }}" name="itemId" id="itemId"/>
|
||||
<input type="hidden" name="milestoneId" value="{{ $canvasItem['milestoneId'] }}" />
|
||||
<input type="hidden" name="changeItem" value="1" />
|
||||
<input type="hidden" name="{{ $dataLabels[3]['field'] }}" value="" />
|
||||
|
||||
<div class="row">
|
||||
{{-- ═══ Left Column: Content ═══ --}}
|
||||
<div class="col-md-8">
|
||||
|
||||
{{-- Title --}}
|
||||
<x-global::forms.text-input name="description" variant="headline" style="width:99%;"
|
||||
value="{{ $tpl->escape($canvasItem['description']) }}"
|
||||
placeholder="{{ $tpl->__('input.placeholders.short_name') }}" /><br /><br />
|
||||
|
||||
@if ($dataLabels[1]['active'])
|
||||
<label>{{ $tpl->__($dataLabels[1]['title']) }}</label>
|
||||
<textarea style="width:100%" rows="5" cols="10" name="{{ $dataLabels[1]['field'] }}" class="modalTextArea tiptapSimple">{{ $canvasItem[$dataLabels[1]['field']] }}</textarea><br />
|
||||
@else
|
||||
<input type="hidden" name="{{ $dataLabels[1]['field'] }}" value="" />
|
||||
@endif
|
||||
|
||||
@if ($dataLabels[2]['active'])
|
||||
<label>{{ $tpl->__($dataLabels[2]['title']) }}</label>
|
||||
<textarea style="width:100%" rows="3" cols="10" name="{{ $dataLabels[2]['field'] }}" class="modalTextArea tiptapSimple">{{ $canvasItem[$dataLabels[2]['field']] }}</textarea><br />
|
||||
@else
|
||||
<input type="hidden" name="{{ $dataLabels[2]['field'] }}" value="" />
|
||||
@endif
|
||||
|
||||
{{-- Authored-meaning fields.
|
||||
`why_this_matters` — Outcome and Impact items (skipped on
|
||||
Inputs/Activities/Outputs — those are context, not meaning).
|
||||
`starting_picture` — Impact only.
|
||||
Labels ARE the prompt; placeholders teach by example.
|
||||
Impact renders NO suggest control — the friction is the
|
||||
feature. Outputs/Outcomes get a suggest button (Step 3). --}}
|
||||
@php
|
||||
$isImpact = $boxKey === 'lm_impact';
|
||||
$isOutcome = $boxKey === 'lm_outcomes';
|
||||
$isOutput = $boxKey === 'lm_outputs';
|
||||
$showWhy = $isImpact || $isOutcome || $isOutput;
|
||||
$showStart = $isImpact;
|
||||
@endphp
|
||||
|
||||
@if ($showWhy)
|
||||
<label for="whyThisMatters" style="display:block;margin-top:8px;">{{ $tpl->__('logicmodel.field.why_this_matters') }}</label>
|
||||
<textarea id="whyThisMatters" style="width:100%" rows="3" name="why_this_matters"
|
||||
maxlength="500"
|
||||
placeholder="{{ $tpl->__('logicmodel.field.why_this_matters.placeholder') }}"
|
||||
class="modalTextArea">{{ $canvasItem['why_this_matters'] }}</textarea>
|
||||
@if (! $isImpact)
|
||||
{{-- Suggest affordance for Outputs/Outcomes only. Impact
|
||||
deliberately has none — no draft, no pre-fill. --}}
|
||||
<div style="margin-top:4px;font-size:12px;color:var(--rd-text-3,#7a8790);">
|
||||
<button type="button"
|
||||
class="lm-suggest-why"
|
||||
data-source-title="{{ $tpl->escape($canvasItem['description'] ?? '') }}"
|
||||
data-source-body="{{ $tpl->escape($canvasItem['conclusion'] ?? '') }}"
|
||||
style="background:none;border:0;padding:0;color:var(--main-titles-color,#004666);cursor:pointer;font-size:12px;font-weight:600;">
|
||||
<i class="fa fa-lightbulb"></i> {{ $tpl->__('logicmodel.field.why_this_matters.suggest') }}
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
<br />
|
||||
@else
|
||||
<input type="hidden" name="why_this_matters" value="{{ $tpl->escape($canvasItem['why_this_matters'] ?? '') }}" />
|
||||
@endif
|
||||
|
||||
@if ($showStart)
|
||||
<label for="startingPicture" style="display:block;margin-top:8px;">{{ $tpl->__('logicmodel.field.starting_picture') }}</label>
|
||||
<textarea id="startingPicture" style="width:100%" rows="3" name="starting_picture"
|
||||
maxlength="500"
|
||||
placeholder="{{ $tpl->__('logicmodel.field.starting_picture.placeholder') }}"
|
||||
class="modalTextArea">{{ $canvasItem['starting_picture'] }}</textarea>
|
||||
<br />
|
||||
@else
|
||||
<input type="hidden" name="starting_picture" value="{{ $tpl->escape($canvasItem['starting_picture'] ?? '') }}" />
|
||||
@endif
|
||||
|
||||
{{-- Suggest handler — pure client-side draft that copies from
|
||||
the item's title + body. Never runs on Impact (no button
|
||||
rendered). LLM-backed suggestions can slot in behind the
|
||||
same UI later; the seed source stays authored text. --}}
|
||||
@if ($showWhy && ! $isImpact)
|
||||
<script>
|
||||
(function () {
|
||||
var btns = document.querySelectorAll('.lm-suggest-why');
|
||||
btns.forEach(function (btn) {
|
||||
// The dialog can be re-injected within a session — guard against
|
||||
// stacking a second listener on an already-bound button.
|
||||
if (btn.dataset.lmWhyBound) {
|
||||
return;
|
||||
}
|
||||
btn.dataset.lmWhyBound = '1';
|
||||
btn.addEventListener('click', function () {
|
||||
var title = (btn.getAttribute('data-source-title') || '').trim();
|
||||
var body = (btn.getAttribute('data-source-body') || '').trim();
|
||||
// Silence beats generic: skip if we don't have real source text.
|
||||
if (title.length < 4 && body.length < 12) return;
|
||||
var draft = body.length >= 20 ? body : title;
|
||||
var ta = document.getElementById('whyThisMatters');
|
||||
if (ta && ! ta.value.trim()) {
|
||||
ta.value = draft;
|
||||
ta.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@endif
|
||||
|
||||
{{-- Comments section moved outside the form to avoid nested forms --}}
|
||||
|
||||
</div>
|
||||
|
||||
{{-- ═══ Right Column: Details Panel ═══ --}}
|
||||
<div class="col-md-4">
|
||||
<div class="lm-details-panel">
|
||||
|
||||
<div class="lm-details-heading">{{ $tpl->__('label.details') }}</div>
|
||||
|
||||
{{-- Status --}}
|
||||
@if (! empty($statusLabels))
|
||||
<div class="lm-details-row">
|
||||
<span class="lm-details-label"><i class="fas fa-fw fa-circle-dot"></i> {{ $tpl->__('label.status') }}</span>
|
||||
<span class="lm-details-value">
|
||||
<select name="status" id="statusCanvas"></select>
|
||||
</span>
|
||||
</div>
|
||||
@else
|
||||
<input type="hidden" name="status" value="{{ $canvasItem['status'] ?? array_key_first($hiddenStatusLabels) }}" />
|
||||
@endif
|
||||
|
||||
{{-- Priority --}}
|
||||
<div class="lm-details-row">
|
||||
<span class="lm-details-label"><i class="fas fa-fw fa-flag"></i> {{ $tpl->__('logicmodel.priority.label') }}</span>
|
||||
<span class="lm-details-value">
|
||||
<select name="impact" id="priorityCanvas"></select>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{-- Stage --}}
|
||||
<div class="lm-details-row">
|
||||
<span class="lm-details-label"><i class="fas fa-fw fa-layer-group"></i> {{ $tpl->__('logicmodel.stage.label') }}</span>
|
||||
<span class="lm-details-value">
|
||||
<select name="box" id="stageCanvas"></select>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if (! empty($relatesLabels))
|
||||
<div class="lm-details-row">
|
||||
<span class="lm-details-label"><i class="fas fa-fw fa-link"></i> {{ $tpl->__('label.relates') }}</span>
|
||||
<span class="lm-details-value">
|
||||
<select name="relates" id="relatesCanvas"></select>
|
||||
</span>
|
||||
</div>
|
||||
@else
|
||||
<input type="hidden" name="relates" value="{{ $canvasItem['relates'] ?? array_key_first($hiddenRelatesLabels) }}" />
|
||||
@endif
|
||||
|
||||
{{-- Author (read-only) --}}
|
||||
@if ($id !== '' && ($canvasItem['author'] ?? '') !== '')
|
||||
<div class="lm-details-row">
|
||||
<span class="lm-details-label"><i class="fas fa-fw fa-user"></i> {{ $tpl->__('label.author') }}</span>
|
||||
<span class="lm-details-value lm-details-text">{{ $canvasItem['authorFirstname'] ?? '' }} {{ $canvasItem['authorLastname'] ?? '' }}</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
{{-- Plugin hook for additional right-panel content (e.g. project links) --}}
|
||||
@if ($id !== '')
|
||||
@dispatchEvent('canvas.dialog.afterDetails', [
|
||||
'canvasItem' => $canvasItem,
|
||||
'canvasName' => $canvasName,
|
||||
'canvasId' => (int) session('currentLOGICMODELCanvas'),
|
||||
])
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ═══ Bottom: Actions ═══ --}}
|
||||
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-top:16px; padding-top:16px; border-top:1px solid var(--main-border-color);">
|
||||
@if ($login::userIsAtLeast($roles::$editor))
|
||||
<x-global::forms.button tag="input" inputType="submit" :labelText="$tpl->__('buttons.save')" id="primaryCanvasSubmitButton" contentRole="primary"/>
|
||||
<x-global::forms.button inputType="submit" contentRole="secondary" value="closeModal" id="saveAndClose" onclick="leantime.canvasController.setCloseModal();">{!! $tpl->__('buttons.save_and_close') !!}</x-global::forms.button>
|
||||
@endif
|
||||
|
||||
@if ($id != '')
|
||||
<x-global::forms.button tag="a" link="{{ BASE_URL }}/{{ $canvasName }}canvas/delCanvasItem/{{ $id }}" class="{{ $canvasName }}CanvasModal delete" style="margin-left:auto;" state="danger" variant="outline"><i class="fa fa-trash-can"></i> {{ $tpl->__('links.delete') }}</x-global::forms.button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
{{-- Comments section rendered OUTSIDE the main form to avoid nested forms.
|
||||
The comments submodule has its own <form> which would break the outer form. --}}
|
||||
@if ($id !== '')
|
||||
<div style="margin-top:16px; padding-top:16px; border-top:1px solid var(--main-border-color);">
|
||||
<h4 class="widgettitle title-light"><span class="fa fa-comments"></span>{{ $tpl->__('subtitles.discussion') }}</h4>
|
||||
<input type="hidden" name="comment" value="1" />
|
||||
@include('comments::submodules.generalComment', ['formUrl' => '/' . $canvasName . 'canvas/editCanvasItem/' . $id])
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.lm-details-panel {
|
||||
border-left: 1px solid var(--main-border-color);
|
||||
padding-left: 20px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.lm-details-heading {
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--primary-font-color);
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 6px;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
}
|
||||
.lm-details-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 0;
|
||||
min-height: 40px;
|
||||
}
|
||||
.lm-details-label {
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: 500;
|
||||
color: var(--primary-font-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lm-details-label i {
|
||||
color: var(--secondary-font-color);
|
||||
margin-right: 4px;
|
||||
}
|
||||
.lm-details-value {
|
||||
text-align: right;
|
||||
}
|
||||
.lm-details-value .ss-main {
|
||||
min-width: 120px;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.lm-details-value .ss-main .ss-single-selected {
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
padding-right: 0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.lm-details-text {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--primary-font-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function(){
|
||||
|
||||
@if (! empty($statusLabels))
|
||||
@php $statusColorMap = ['blue' => '#1B75BB', 'orange' => '#fdab3d', 'green' => '#75BB1B', 'red' => '#BB1B25', 'grey' => '#c3ccd4']; @endphp
|
||||
new SlimSelect({
|
||||
select: '#statusCanvas',
|
||||
showSearch: false,
|
||||
valuesUseText: false,
|
||||
data: [
|
||||
@foreach ($statusLabels as $key => $data)
|
||||
@if ($data['active'])
|
||||
@php $sColor = $statusColorMap[$data['color']] ?? '#666'; @endphp
|
||||
{ innerHTML: '<i class="fas fa-fw {{ $data['icon'] }}" style="color:{{ $sColor }}"></i> {{ $tpl->__($data['title']) }}',
|
||||
text: "{{ $tpl->__($data['title']) }}", value: "{{ $key }}", selected: {{ $canvasItem['status'] == $key ? 'true' : 'false' }} },
|
||||
@endif
|
||||
@endforeach
|
||||
]
|
||||
});
|
||||
@endif
|
||||
|
||||
// Priority dropdown (matches to-do priority structure; stored in the impact column)
|
||||
new SlimSelect({
|
||||
select: '#priorityCanvas',
|
||||
showSearch: false,
|
||||
valuesUseText: false,
|
||||
data: [
|
||||
{ text: "{{ $tpl->__('logicmodel.priority.none') }}", value: "", selected: {{ $currentImpact === '' ? 'true' : 'false' }} },
|
||||
{ innerHTML: '<i class="fas fa-fw fa-thermometer-full" style="color:#C73E5C"></i> {{ $tpl->__('logicmodel.priority.critical') }}',
|
||||
text: "{{ $tpl->__('logicmodel.priority.critical') }}", value: "1", selected: {{ $currentImpact === '1' ? 'true' : 'false' }} },
|
||||
{ innerHTML: '<i class="fas fa-fw fa-thermometer-three-quarters" style="color:#E85A5A"></i> {{ $tpl->__('logicmodel.priority.high') }}',
|
||||
text: "{{ $tpl->__('logicmodel.priority.high') }}", value: "2", selected: {{ $currentImpact === '2' ? 'true' : 'false' }} },
|
||||
{ innerHTML: '<i class="fas fa-fw fa-thermometer-half" style="color:#F5A623"></i> {{ $tpl->__('logicmodel.priority.medium') }}',
|
||||
text: "{{ $tpl->__('logicmodel.priority.medium') }}", value: "3", selected: {{ $currentImpact === '3' ? 'true' : 'false' }} },
|
||||
{ innerHTML: '<i class="fas fa-fw fa-thermometer-quarter" style="color:#2ECC71"></i> {{ $tpl->__('logicmodel.priority.low') }}',
|
||||
text: "{{ $tpl->__('logicmodel.priority.low') }}", value: "4", selected: {{ $currentImpact === '4' ? 'true' : 'false' }} },
|
||||
]
|
||||
});
|
||||
|
||||
// Stage dropdown (drives the box column)
|
||||
new SlimSelect({
|
||||
select: '#stageCanvas',
|
||||
showSearch: false,
|
||||
valuesUseText: false,
|
||||
data: [
|
||||
@foreach ($stages as $num => $stage)
|
||||
@php $stageBoxKey = 'lm_' . $stage['key']; @endphp
|
||||
{ innerHTML: '<i class="fas fa-fw {{ $stage['icon'] }}" style="color:{{ $stage['color'] }}"></i> {{ $tpl->__($stage['title']) }}',
|
||||
text: "{{ $tpl->__($stage['title']) }}", value: "{{ $stageBoxKey }}", selected: {{ $boxKey === $stageBoxKey ? 'true' : 'false' }} },
|
||||
@endforeach
|
||||
]
|
||||
});
|
||||
|
||||
@if (! empty($relatesLabels))
|
||||
new SlimSelect({
|
||||
select: '#relatesCanvas',
|
||||
showSearch: false,
|
||||
valuesUseText: false,
|
||||
data: [
|
||||
@foreach ($relatesLabels as $key => $data)
|
||||
@if ($data['active'])
|
||||
{ innerHTML: '<i class="fas fa-fw {{ $data['icon'] }}"></i> {{ $tpl->__($data['title']) }}',
|
||||
text: "{{ $tpl->__($data['title']) }}", value: "{{ $key }}", selected: {{ $canvasItem['relates'] == $key ? 'true' : 'false' }} },
|
||||
@endif
|
||||
@endforeach
|
||||
]
|
||||
});
|
||||
@endif
|
||||
|
||||
if (window.leantime && window.leantime.tiptapController) {
|
||||
leantime.tiptapController.initSimpleEditor();
|
||||
}
|
||||
|
||||
@if (! $login::userIsAtLeast($roles::$editor))
|
||||
leantime.authController.makeInputReadonly("#global-modal-content");
|
||||
@endif
|
||||
|
||||
@if ($login::userHasRole([$roles::$commenter]))
|
||||
leantime.commentsController.enableCommenterForms();
|
||||
@endif
|
||||
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1 @@
|
||||
@include('canvas::delCanvas', ['canvasName' => 'logicmodel'])
|
||||
@@ -0,0 +1 @@
|
||||
@include('canvas::delCanvasItem', ['canvasName' => 'logicmodel'])
|
||||
271
app/Domain/Logicmodelcanvas/Templates/showCanvas.blade.php
Normal file
271
app/Domain/Logicmodelcanvas/Templates/showCanvas.blade.php
Normal file
@@ -0,0 +1,271 @@
|
||||
@extends($layout)
|
||||
@section('content')
|
||||
|
||||
@php
|
||||
use Leantime\Domain\Logicmodelcanvas\Repositories\Logicmodelcanvas;
|
||||
use Leantime\Domain\Comments\Repositories\Comments;
|
||||
|
||||
$canvasName = 'logicmodel';
|
||||
$allCanvas = $tpl->get('allCanvas');
|
||||
$canvasIcon = $tpl->get('canvasIcon');
|
||||
$canvasTypes = $tpl->get('canvasTypes');
|
||||
$statusLabels = $tpl->get('statusLabels');
|
||||
$relatesLabels = $tpl->get('relatesLabels');
|
||||
$dataLabels = $tpl->get('dataLabels');
|
||||
$disclaimer = $tpl->get('disclaimer');
|
||||
$canvasItems = $tpl->get('canvasItems');
|
||||
$currentCanvas = $tpl->get('currentCanvas');
|
||||
$users = $tpl->get('users');
|
||||
|
||||
$filter['status'] = $_GET['filter_status'] ?? (session('filter_status') ?? 'all');
|
||||
// Logic model board does not use relates filter — force to 'all'
|
||||
$filter['relates'] = 'all';
|
||||
|
||||
$canvasTitle = '';
|
||||
foreach ($allCanvas as $canvasRow) {
|
||||
if ($canvasRow['id'] == $currentCanvas) {
|
||||
$canvasTitle = $canvasRow['title'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$stages = Logicmodelcanvas::STAGES;
|
||||
@endphp
|
||||
|
||||
@include('global::components.stageflow.styles')
|
||||
|
||||
{{-- ── Page Header ───────────────────────────────────────────── --}}
|
||||
<div class="pageheader">
|
||||
<div class="pageicon"><span class="fas {{ $canvasIcon }}"></span></div>
|
||||
<div class="pagetitle">
|
||||
@if (count($allCanvas) > 0)
|
||||
<x-global::subjectSwitcher
|
||||
:parent="$tpl->__('headline.logicmodel.board')"
|
||||
:current="$canvasTitle">
|
||||
@if ($login::userIsAtLeast($roles::$editor))
|
||||
<li><a href="#/{{ $canvasName }}canvas/boardDialog">{!! $tpl->__('links.icon.create_new_board') !!}</a></li>
|
||||
@endif
|
||||
<li class="border"></li>
|
||||
@foreach ($allCanvas as $canvasRow)
|
||||
<li><a href="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas/{{ $canvasRow['id'] }}">{{ $tpl->escape($canvasRow['title']) }}</a></li>
|
||||
@endforeach
|
||||
</x-global::subjectSwitcher>
|
||||
@else
|
||||
<h1>{{ $tpl->__('headline.logicmodel.board') }}</h1>
|
||||
@endif
|
||||
</div>
|
||||
@if (count($allCanvas) > 0)
|
||||
<div class="pageheader-right">
|
||||
<span class="dropdown dropdownWrapper headerEditDropdown">
|
||||
<a href="javascript:void(0)" class="dropdown-toggle btn btn-transparent" data-toggle="dropdown"><i class="fa-solid fa-ellipsis-v"></i></a>
|
||||
<ul class="dropdown-menu editCanvasDropdown">
|
||||
@if ($login::userIsAtLeast($roles::$editor))
|
||||
<li><a href="#/{{ $canvasName }}canvas/boardDialog/{{ $currentCanvas }}" class="editCanvasLink">{!! $tpl->__('links.icon.edit') !!}</a></li>
|
||||
@endif
|
||||
<li><a href="{{ BASE_URL }}/{{ $canvasName }}canvas/export/{{ $currentCanvas }}" hx-boost="false">{!! $tpl->__('links.icon.export') !!}</a></li>
|
||||
<li><a href="javascript:window.print();">{!! $tpl->__('links.icon.print') !!}</a></li>
|
||||
@dispatchEvent('logicmodel.headerActions', ['canvasId' => $currentCanvas])
|
||||
@if ($login::userIsAtLeast($roles::$editor))
|
||||
<li><a href="#/{{ $canvasName }}canvas/delCanvas/{{ $currentCanvas }}" class="delete">{!! $tpl->__('links.icon.delete') !!}</a></li>
|
||||
@endif
|
||||
</ul>
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="maincontent">
|
||||
<div class="maincontentinner">
|
||||
|
||||
{!! $tpl->displayNotification() !!}
|
||||
|
||||
@if (count($allCanvas) > 0)
|
||||
{{-- Toolbar --}}
|
||||
<div style="display:flex; align-items:center; gap:8px; margin-bottom:8px;">
|
||||
{{-- Board creation lives in the header title dropdown; no duplicate button here. --}}
|
||||
@if (!empty($statusLabels))
|
||||
@php
|
||||
$statusColorMap = ['blue' => '#1B75BB', 'orange' => '#fdab3d', 'green' => '#75BB1B', 'red' => '#BB1B25', 'grey' => '#c3ccd4'];
|
||||
if ($filter['status'] != 'all' && !isset($statusLabels[$filter['status']])) { $filter['status'] = 'all'; }
|
||||
if ($filter['status'] == 'all') {
|
||||
$statusFilterLabel = '<i class="fas fa-filter"></i> ' . $tpl->__('status.all');
|
||||
} else {
|
||||
$sc = $statusColorMap[$statusLabels[$filter['status']]['color']] ?? '#666';
|
||||
$statusFilterLabel = '<i class="fas fa-fw ' . $statusLabels[$filter['status']]['icon'] . '" style="color:' . $sc . '"></i> ' . $statusLabels[$filter['status']]['title'];
|
||||
}
|
||||
@endphp
|
||||
{{-- viewDropDown right-aligns the menu (left:auto; right:0). On
|
||||
this board the button sits at the left edge of the toolbar, so
|
||||
a 240px menu extends back under the sidebar and gets clipped
|
||||
by .primaryContent's overflow-x:hidden. Left-align it locally. --}}
|
||||
<div class="btn-group viewDropDown">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown">{!! $statusFilterLabel !!}</button>
|
||||
<ul class="dropdown-menu" style="left:0; right:auto;">
|
||||
<li><a href="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas?filter_status=all" @if ($filter['status'] == 'all') class="active" @endif><i class="fas fa-globe"></i> {{ $tpl->__('status.all') }}</a></li>
|
||||
@foreach ($statusLabels as $key => $data)
|
||||
@php $iconColor = $statusColorMap[$data['color']] ?? '#666'; @endphp
|
||||
<li><a href="{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas?filter_status={{ $key }}" @if ($filter['status'] == $key) class="active" @endif><i class="fas fa-fw {{ $data['icon'] }}" style="color:{{ $iconColor }}"></i> {{ $data['title'] }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
{{-- Export & print live in the header 3-dot menu (and the plugin extends it
|
||||
via the logicmodel.headerActions hook), so no duplicate toolbar control here. --}}
|
||||
|
||||
@dispatchEvent('logicmodel.beforeStageFlow', ['canvasId' => $currentCanvas, 'canvasItems' => $canvasItems])
|
||||
|
||||
{{-- ── Five-Stage Flow ──────────────────────────────────── --}}
|
||||
<div class="sf-flow" id="logicModelBoard">
|
||||
@foreach ($stages as $num => $stage)
|
||||
@php
|
||||
$boxKey = 'lm_' . $stage['key'];
|
||||
$stageItems = array_filter($canvasItems, function ($item) use ($boxKey, $filter) {
|
||||
if ($item['box'] !== $boxKey) return false;
|
||||
if ($filter['status'] !== 'all' && $item['status'] !== $filter['status']) return false;
|
||||
if ($filter['relates'] !== 'all' && $item['relates'] !== $filter['relates']) return false;
|
||||
return true;
|
||||
});
|
||||
$itemCount = count($stageItems);
|
||||
@endphp
|
||||
|
||||
<x-global::stageflow.card
|
||||
:stageKey="$boxKey"
|
||||
:stageNum="$num"
|
||||
:color="$stage['color']"
|
||||
:bgColor="$stage['bg']"
|
||||
:icon="$stage['icon']"
|
||||
:title="$tpl->__($stage['title'])"
|
||||
:subtitle="$tpl->__($stage['subtitle'])"
|
||||
:active="true"
|
||||
:itemCount="$itemCount"
|
||||
:focusLabel="''"
|
||||
>
|
||||
<x-slot:headerExtra>
|
||||
@dispatchEvent('logicmodel.afterStageHeader', ['stageNum' => $num, 'stage' => $stage, 'canvasId' => $currentCanvas, 'stageItems' => $stageItems])
|
||||
</x-slot:headerExtra>
|
||||
|
||||
<x-slot:beforeBody>
|
||||
@dispatchEvent('logicmodel.beforeStageBody', ['stageNum' => $num, 'stage' => $stage, 'canvasId' => $currentCanvas])
|
||||
</x-slot:beforeBody>
|
||||
|
||||
@foreach ($stageItems as $row)
|
||||
@php
|
||||
$commentsRepo = app()->make(Comments::class);
|
||||
$nbcomments = $commentsRepo->countComments(moduleId: $row['id']);
|
||||
$statusColor = isset($statusLabels[$row['status']]) ? $statusLabels[$row['status']]['color'] : 'grey';
|
||||
@endphp
|
||||
|
||||
<x-global::stageflow.item
|
||||
:itemId="$row['id']"
|
||||
:title="$row['description']"
|
||||
:description="$row['conclusion'] != '' ? $tpl->convertRelativePaths($row['conclusion']) : ''"
|
||||
:editUrl="'#/' . $canvasName . 'canvas/editCanvasItem/' . $row['id']"
|
||||
:deleteUrl="'#/' . $canvasName . 'canvas/delCanvasItem/' . $row['id']"
|
||||
:commentUrl="'#/' . $canvasName . 'canvas/editCanvasComment/' . $row['id']"
|
||||
:commentCount="$nbcomments"
|
||||
:authorId="$row['author']"
|
||||
:authorName="trim(($row['authorFirstname'] ?? '') . ' ' . ($row['authorLastname'] ?? ''))"
|
||||
:dotColor="$statusColor"
|
||||
:canEdit="$login::userIsAtLeast($roles::$editor)"
|
||||
>
|
||||
@dispatchEvent('logicmodel.itemCardFooter', ['item' => $row, 'canvasId' => $currentCanvas])
|
||||
</x-global::stageflow.item>
|
||||
@endforeach
|
||||
|
||||
@if ($itemCount === 0)
|
||||
<div class="sf-empty">
|
||||
<i class="fa {{ $stage['icon'] }} sf-empty-icon" style="color: {{ $stage['color'] }};"></i>
|
||||
{{ $tpl->__('text.no_items_yet') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($login::userIsAtLeast($roles::$editor))
|
||||
<a class="sf-add" href="#/{{ $canvasName }}canvas/editCanvasItem?type={{ $boxKey }}">
|
||||
<i class="fa fa-plus"></i> {{ $tpl->__('logicmodel.add_' . $stage['key']) }}
|
||||
</a>
|
||||
@endif
|
||||
</x-global::stageflow.card>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@dispatchEvent('logicmodel.afterStageFlow', ['canvasId' => $currentCanvas, 'canvasItems' => $canvasItems])
|
||||
|
||||
<div class="clearfix"></div>
|
||||
@endif
|
||||
|
||||
{{-- ── No Board Yet ─────────────────────────────────────── --}}
|
||||
@if (count($allCanvas) == 0)
|
||||
<br /><br />
|
||||
<div class="center">
|
||||
<div class="svgContainer">
|
||||
{!! file_get_contents(ROOT . '/dist/images/svg/undraw_design_data_khdb.svg') !!}
|
||||
</div>
|
||||
<h3>{{ $tpl->__('headlines.logicmodel.analysis') }}</h3>
|
||||
<br />{!! $tpl->__('text.logicmodel.helper_content') !!}
|
||||
@if ($login::userIsAtLeast($roles::$editor))
|
||||
<br /><br />
|
||||
<x-global::forms.button tag="a" link="javascript:void(0)" class="addCanvasLink" contentRole="primary">
|
||||
{{ $tpl->__('links.icon.create_new_board') }}
|
||||
</x-global::forms.button>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (!empty($disclaimer) && count($allCanvas) > 0)
|
||||
<small class="center">{{ $disclaimer }}</small>
|
||||
@endif
|
||||
|
||||
{!! $tpl->viewFactory->make($tpl->getTemplatePath('canvas', 'modals'), $__data)->render() !!}
|
||||
|
||||
{{-- Plugin panel containers (filled by HTMX when plugin is active) --}}
|
||||
<div id="templateSelectorContainer"></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function () {
|
||||
|
||||
if (jQuery('#searchCanvas').length > 0) {
|
||||
new SlimSelect({ select: '#searchCanvas' });
|
||||
}
|
||||
|
||||
@if (isset($_GET['closeModal']))
|
||||
jQuery.nmTop().close();
|
||||
@endif
|
||||
|
||||
leantime.canvasController.setCanvasName('logicmodel');
|
||||
leantime.canvasController.initFilterBar();
|
||||
|
||||
@if ($login::userIsAtLeast($roles::$editor))
|
||||
leantime.canvasController.initCanvasLinks();
|
||||
leantime.canvasController.initUserDropdown();
|
||||
leantime.canvasController.initStatusDropdown();
|
||||
leantime.canvasController.initRelatesDropdown();
|
||||
@else
|
||||
leantime.authController.makeInputReadonly(".maincontentinner");
|
||||
@endif
|
||||
|
||||
@if (isset($_GET['showModal']))
|
||||
@php
|
||||
if ($_GET['showModal'] == '') {
|
||||
$modalUrl = '&type=' . array_key_first($canvasTypes);
|
||||
} else {
|
||||
$modalUrl = '/' . (int) $_GET['showModal'];
|
||||
}
|
||||
@endphp
|
||||
leantime.canvasController.openModalManually("{{ BASE_URL }}/{{ $canvasName }}canvas/editCanvasItem{{ $modalUrl }}");
|
||||
window.history.pushState({}, document.title, '{{ BASE_URL }}/{{ $canvasName }}canvas/showCanvas/');
|
||||
@endif
|
||||
|
||||
// Reload page when sector fixture is loaded (items were added server-side by the plugin)
|
||||
document.body.addEventListener('logicmodel.fixtureLoaded', function () {
|
||||
window.location.reload();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
Reference in New Issue
Block a user