Skip to content

Commit fa2e7f5

Browse files
committed
Add initial implementation of Task management features
1 parent 88fe870 commit fa2e7f5

File tree

4 files changed

+483
-74
lines changed

4 files changed

+483
-74
lines changed
Lines changed: 293 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,319 @@
1-
<div
2-
class="w-full h-full flex flex-col"
3-
x-load-css="[@js(\Filament\Support\Facades\FilamentAsset::getStyleHref('flowforge', package: 'relaticle/flowforge'))]"
4-
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('flowforge', 'relaticle/flowforge') }}"
5-
>
6-
<!-- Board Content -->
7-
<div class="flex flex-row h-full w-full overflow-x-auto overflow-y-hidden py-4 px-2 gap-4">
8-
@foreach($columns as $columnId => $column)
9-
<div
10-
class="flex flex-col h-full min-w-64 w-64 bg-gray-100 dark:bg-gray-800 rounded-xl shadow-sm p-2"
11-
x-data="{
12-
columnId: '{{ $columnId }}',
13-
items: @js($column['items']),
14-
isOver: false
15-
}"
16-
x-on:dragover.prevent="isOver = true; $event.dataTransfer.dropEffect = 'move';"
17-
x-on:dragleave.prevent="isOver = false"
18-
x-on:drop.prevent="
1+
<div>
2+
<div
3+
class="w-full h-full flex flex-col"
4+
x-load-css="[@js(\Filament\Support\Facades\FilamentAsset::getStyleHref('flowforge', package: 'relaticle/flowforge'))]"
5+
x-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('flowforge', 'relaticle/flowforge') }}"
6+
x-data="{
7+
showCreateModal: false,
8+
showEditModal: false,
9+
currentColumn: null,
10+
currentCard: null,
11+
formData: {},
12+
13+
openCreateModal(columnId) {
14+
this.currentColumn = columnId;
15+
this.formData = {
16+
{{ $config['statusField'] }}: columnId
17+
};
18+
$dispatch('open-modal', { id: 'create-card-modal' })
19+
},
20+
21+
submitCreateForm() {
22+
console.log(this.formData);
23+
$wire.createCard(this.formData).then(cardId => {
24+
if (cardId) {
25+
this.showCreateModal = false;
26+
this.formData = {};
27+
}
28+
});
29+
},
30+
31+
openEditModal(card, columnId) {
32+
this.currentCard = card;
33+
this.currentColumn = columnId;
34+
35+
// Initialize form data with current card values
36+
this.formData = {...card};
37+
this.formData['{{ $config['statusField'] }}'] = columnId;
38+
39+
$dispatch('open-modal', { id: 'edit-card-modal' })
40+
},
41+
42+
submitEditForm() {
43+
$wire.updateCard(this.currentCard.id, this.formData).then(result => {
44+
if (result) {
45+
this.showEditModal = false;
46+
this.formData = {};
47+
}
48+
});
49+
},
50+
51+
deleteCard() {
52+
if (confirm('Are you sure you want to delete this card? This action cannot be undone.')) {
53+
$wire.deleteCard(this.currentCard.id).then(result => {
54+
if (result) {
55+
this.showEditModal = false;
56+
this.formData = {};
57+
}
58+
});
59+
}
60+
}
61+
}"
62+
>
63+
<div>barev
64+
<div x-text="showCreateModal"></div>
65+
</div>
66+
<!-- Board Content -->
67+
<div class="flex flex-row h-full w-full overflow-x-auto overflow-y-hidden py-4 px-2 gap-4">
68+
@foreach($columns as $columnId => $column)
69+
<div
70+
class="flex flex-col h-full min-w-64 w-64 bg-gray-100 dark:bg-gray-800 rounded-xl shadow-sm p-2"
71+
x-data="{
72+
columnId: '{{ $columnId }}',
73+
items: @js($column['items']),
74+
isOver: false
75+
}"
76+
x-on:dragover.prevent="isOver = true; $event.dataTransfer.dropEffect = 'move';"
77+
x-on:dragleave.prevent="isOver = false"
78+
x-on:drop.prevent="
1979
isOver = false;
2080
const data = JSON.parse($event.dataTransfer.getData('text/plain'));
2181
$wire.updateStatus(data.id, columnId);
2282
"
23-
:class="{ 'border-2 border-primary-500 dark:border-primary-400': isOver }"
24-
>
25-
<!-- Column Header -->
26-
<div class="flex items-center justify-between p-2 mb-2 font-medium">
27-
<h3 class="text-gray-900 dark:text-white">{{ $column['name'] }}</h3>
28-
</div>
83+
:class="{ 'border-2 border-primary-500 dark:border-primary-400': isOver }"
84+
>
85+
<!-- Column Header -->
86+
<div class="flex items-center justify-between p-2 mb-2 font-medium">
87+
<h3 class="text-gray-900 dark:text-white">{{ $column['name'] }}</h3>
88+
<button
89+
type="button"
90+
class="text-primary-600 hover:text-primary-500 dark:text-primary-400 dark:hover:text-primary-300"
91+
x-on:click="openCreateModal('{{ $columnId }}')"
92+
>
93+
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
94+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" clip-rule="evenodd" />
95+
</svg>
96+
</button>
97+
</div>
2998

30-
<!-- Column Content -->
31-
<div class="flex-1 overflow-y-auto overflow-x-hidden p-1">
32-
<template x-for="(card, index) in items" :key="card.id">
33-
<div
34-
class="bg-white dark:bg-gray-700 rounded-md shadow-sm mb-2 p-3 cursor-grab select-none transition-all duration-200 hover:shadow-md hover:-translate-y-[2px]"
35-
:class="{
99+
<!-- Column Content -->
100+
<div class="flex-1 overflow-y-auto overflow-x-hidden p-1">
101+
<template x-for="(card, index) in items" :key="card.id">
102+
<div
103+
class="bg-white dark:bg-gray-700 rounded-md shadow-sm mb-2 p-3 cursor-pointer select-none transition-all duration-200 hover:shadow-md hover:-translate-y-[2px]"
104+
:class="{
36105
'border-l-4 border-red-500': card.priority === 'high',
37106
'border-l-4 border-yellow-500': card.priority === 'medium',
38107
'border-l-4 border-green-500': card.priority === 'low',
39108
'border-l-4 border-gray-300 dark:border-gray-600': !card.priority
40109
}"
41-
draggable="true"
42-
x-on:dragstart="
110+
draggable="true"
111+
x-on:dragstart="
43112
$event.dataTransfer.setData('text/plain', JSON.stringify({
44113
id: card.id,
45114
sourceColumn: columnId
46115
}));
47116
$event.target.classList.add('opacity-50');
48117
"
49-
x-on:dragend="$event.target.classList.remove('opacity-50')"
50-
>
51-
<div class="text-sm font-medium text-gray-900 dark:text-white mb-1" x-text="card.title"></div>
52-
53-
<template x-if="card.description">
54-
<div class="text-xs text-gray-500 dark:text-gray-400 line-clamp-2" x-text="card.description"></div>
55-
</template>
56-
57-
<div class="flex flex-wrap gap-2 mt-2">
58-
<template x-for="(value, key) in card" :key="key">
59-
<template x-if="!['id', 'title', 'description'].includes(key) && value">
60-
<div
61-
class="inline-flex items-center text-xs px-2 py-0.5 rounded-full"
62-
:class="{
118+
x-on:dragend="$event.target.classList.remove('opacity-50')"
119+
x-on:click.stop="openEditModal(card, columnId)"
120+
>
121+
<div class="text-sm font-medium text-gray-900 dark:text-white mb-1" x-text="card.title"></div>
122+
123+
<template x-if="card.description">
124+
<div class="text-xs text-gray-500 dark:text-gray-400 line-clamp-2" x-text="card.description"></div>
125+
</template>
126+
127+
<div class="flex flex-wrap gap-2 mt-2">
128+
<template x-for="(value, key) in card" :key="key">
129+
<template x-if="!['id', 'title', 'description'].includes(key) && value">
130+
<div
131+
class="inline-flex items-center text-xs px-2 py-0.5 rounded-full"
132+
:class="{
63133
'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300': key === 'category',
64134
'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300': key === 'assignee',
65135
'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300': key === 'due_date',
66136
'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300': !['category', 'assignee', 'due_date'].includes(key)
67137
}"
68-
>
69-
<span x-text="value"></span>
70-
</div>
138+
>
139+
<span x-text="value"></span>
140+
</div>
141+
</template>
71142
</template>
72-
</template>
143+
</div>
73144
</div>
74-
</div>
75-
</template>
145+
</template>
146+
</div>
76147
</div>
77-
</div>
78-
@endforeach
148+
@endforeach
149+
</div>
150+
151+
<!-- Create Card Modal -->
152+
<x-filament::modal
153+
id="create-card-modal"
154+
:heading="__('Create New Card')"
155+
width="md"
156+
>
157+
<form x-on:submit="submitCreateForm">
158+
<!-- Title Field -->
159+
<label for="">Title</label>
160+
<x-filament::input.wrapper label="{{ __('Title') }}">
161+
<x-filament::input
162+
id="title"
163+
type="text"
164+
x-model="formData['{{ $config['titleAttribute'] }}']"
165+
required
166+
/>
167+
</x-filament::input.wrapper>
168+
169+
<!-- Description Field (if available) -->
170+
@if($config['descriptionAttribute'])
171+
<x-filament::input.wrapper label="{{ __('Description') }}" class="mt-4">
172+
<x-filament::input
173+
id="description"
174+
type="text"
175+
x-model="formData['{{ $config['descriptionAttribute'] }}']"
176+
multiline
177+
rows="3"
178+
/>
179+
</x-filament::input.wrapper>
180+
@endif
181+
182+
<!-- Status Field -->
183+
<x-filament::input.wrapper label="{{ __('Status') }}" class="mt-4">
184+
<x-filament::input.select
185+
id="status"
186+
x-model="formData['{{ $config['statusField'] }}']"
187+
>
188+
@foreach($config['statusValues'] as $value => $label)
189+
<option value="{{ $value }}">{{ $label }}</option>
190+
@endforeach
191+
</x-filament::input.select>
192+
</x-filament::input.wrapper>
193+
194+
<!-- Additional Card Attributes -->
195+
@foreach($config['cardAttributes'] as $attribute)
196+
<x-filament::input.wrapper label="{{ __(ucfirst(str_replace('_', ' ', $attribute))) }}" class="mt-4">
197+
<x-filament::input
198+
id="{{ $attribute }}"
199+
type="text"
200+
x-model="formData['{{ $attribute }}']"
201+
/>
202+
</x-filament::input.wrapper>
203+
@endforeach
204+
205+
<x-slot name="footer">
206+
<div class="flex justify-end gap-x-3">
207+
<x-filament::button
208+
type="button"
209+
color="gray"
210+
x-on:click="showCreateModal = false"
211+
>
212+
{{ __('Cancel') }}
213+
</x-filament::button>
214+
215+
<x-filament::button
216+
type="submit"
217+
x-on:click="submitCreateForm"
218+
>
219+
{{ __('Create') }}
220+
</x-filament::button>
221+
</div>
222+
</x-slot>
223+
</form>
224+
</x-filament::modal>
225+
226+
<!-- Edit Card Modal -->
227+
<x-filament::modal
228+
id="edit-card-modal"
229+
:heading="__('Edit Card')"
230+
width="md"
231+
>
232+
<form x-on:submit.prevent="submitEditForm">
233+
<!-- Title Field -->
234+
<x-filament::input.wrapper label="{{ __('Title') }}">
235+
<x-filament::input
236+
id="edit-title"
237+
type="text"
238+
x-model="formData['{{ $config['titleAttribute'] }}']"
239+
required
240+
/>
241+
</x-filament::input.wrapper>
242+
243+
<!-- Description Field (if available) -->
244+
@if($config['descriptionAttribute'])
245+
<x-filament::input.wrapper label="{{ __('Description') }}" class="mt-4">
246+
<x-filament::input
247+
id="edit-description"
248+
type="text"
249+
x-model="formData['{{ $config['descriptionAttribute'] }}']"
250+
multiline
251+
rows="3"
252+
/>
253+
</x-filament::input.wrapper>
254+
@endif
255+
256+
<!-- Status Field -->
257+
<x-filament::input.wrapper label="{{ __('Status') }}" class="mt-4">
258+
<x-filament::input.select
259+
id="edit-status"
260+
x-model="formData['{{ $config['statusField'] }}']"
261+
>
262+
@foreach($config['statusValues'] as $value => $label)
263+
<option value="{{ $value }}">{{ $label }}</option>
264+
@endforeach
265+
</x-filament::input.select>
266+
</x-filament::input.wrapper>
267+
268+
<!-- Additional Card Attributes -->
269+
@foreach($config['cardAttributes'] as $attribute)
270+
<x-filament::input.wrapper label="{{ __(ucfirst(str_replace('_', ' ', $attribute))) }}" class="mt-4">
271+
<x-filament::input
272+
id="edit-{{ $attribute }}"
273+
type="text"
274+
x-model="formData['{{ $attribute }}']"
275+
/>
276+
</x-filament::input.wrapper>
277+
@endforeach
278+
279+
<x-slot name="footer">
280+
<div class="flex justify-between">
281+
<x-filament::button
282+
type="button"
283+
color="danger"
284+
x-on:click="deleteCard()"
285+
>
286+
<x-filament::icon
287+
name="heroicon-m-trash"
288+
class="w-4 h-4 mr-1"
289+
/>
290+
{{ __('Delete') }}
291+
</x-filament::button>
292+
293+
<div class="flex items-center gap-x-3">
294+
<x-filament::button
295+
type="button"
296+
color="gray"
297+
x-on:click="showEditModal = false"
298+
>
299+
{{ __('Cancel') }}
300+
</x-filament::button>
301+
302+
<x-filament::button
303+
type="submit"
304+
x-on:click="submitEditForm"
305+
>
306+
{{ __('Update') }}
307+
</x-filament::button>
308+
</div>
309+
</div>
310+
</x-slot>
311+
</form>
312+
</x-filament::modal>
79313
</div>
314+
315+
<style>
316+
[x-cloak] { display: none !important; }
317+
</style>
318+
80319
</div>

0 commit comments

Comments
 (0)