Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

gidaban79's avatar

Save order in each column

Hello guys.

First look on my code.

    <div class="container-fluid vh-100">
        <div class="row mb-2">
            <div class="col">
                <button @click="openModalCard" class="btn btn-outline-success btn-sm">Add Card</button>
                <modal v-if="isModalCardOpen" @button-click="onModalCardButtonClick"></modal>
                <hr>
            </div>
        </div>
        <div class="row">
            <div v-for="(status, index) in statuses" :key="index" class="col">
                <div class="rounded mb-3">
                    <h6 v-if="!isEditing(index)">{{ status.title }}</h6>
                    <input v-else v-model="statuses[index].title" class="form-control mb-2">
                    <div class="d-flex justify-content-between">
                        <button v-if="!isEditing(index)" @click="editStatus(index)" class="btn btn-outline-primary btn-sm">Edit Card</button>
                        <button v-else @click="saveStatus(index,status.id)" class="btn btn-outline-success btn-sm">Save</button>
                        <button @click="addTaskModal(status.id, index)" class="btn btn-sm btn-warning btn-sm">Add task</button>
                    </div>
                    <hr>
                </div>
                <div class="p-0">
                    <draggable
                        v-model="status.kanban_tasks"
                        tag="div"
                        v-bind="taskDragOptions"
                        @end="handleTaskMoved"
                        :disabled="!enabled"
                    >
                        <div v-for="(task, taskIndex) in status.kanban_tasks" :key="task.id" class="card p-1 mb-2">
                            <div class="p-1">
                                <div v-if="!isEditingTask(index, taskIndex)">
                                    <h6>{{ task.task_name }}</h6>
                                    <p class="text-muted small">{{ task.task_description }}</p>
                                </div>
                                <div v-else>
                                    <input v-model="task.task_name" class="form-control mb-1">
                                    <textarea v-model="task.task_description" class="form-control"></textarea>
                                </div>
                                <div class="d-flex justify-content-between mt-2">
                                    <button v-if="!isEditingTask(index, taskIndex)" @click="editTask(index, taskIndex)" class="btn btn-outline-primary btn-sm">Edit</button>
                                    <button v-else @click="saveTask(index, taskIndex)" class="btn btn-outline-success btn-sm">Save</button>
                                    <button class="btn btn-sm btn-danger" @click="deleteTask(task.id)">Delete</button>
                                </div>
                            </div>
                        </div>

                    </draggable>
                    <task-modal v-if="isModalTaskOpen" :id_card="idCard" :index_card="indexCard" @add-task="onModalTaskButtonClick" :key="status.id"></task-modal>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
import modal from "./AddCardModalComponent.vue";
import taskModal from "./AddTaskModalComponent.vue";
import draggable from "vuedraggable";

export default {
    components: {
        draggable,
        modal,
        taskModal
    },
    data: function () {
        return {
            statuses: [],
            urlTask: '/repair/API/kanban/task',
            urlCard: '/repair/API/kanban/card',
            syncUrl: '/repair/API/kanban/task/sync',
            isModalCardOpen: false,
            isModalTaskOpen: false,
            editingIndex: null,
            editingTaskIndex: null,
            idCard: null,
            indexCard: null,
            enabled: true,
        };
    },
    computed: {
        taskDragOptions() {
            return {
                animation: 200,
                group: "task-list",
                dragClass: "status-drag"
            };
        }
    },
    mounted() {
        this.fetchCards()
    },
    methods: {
        fetchCards() {
            axios.get(this.urlCard)
                .then(response => {
                    this.statuses = response.data.map(status => ({
                        ...status,
                        kanban_tasks: status.kanban_tasks.map(task => ({
                            ...task,
                            isEditing: false
                        }))
                    }));
                })
                .catch(err => {
                    console.warn(err);
                });
        },
        handleTaskMoved(evt) {
            axios.put(this.syncUrl, {
                columns: this.statuses
            }).catch(err => {
                console.log(err.response);
            });
        },
        openModalCard() {
            this.isModalCardOpen = true;
        },
        openModalTask() {
            this.isModalTaskOpen = true;
        },
        closeModalTask() {
            this.isModalTaskOpen = false;
        },
        onModalCardButtonClick(action) {
            if (action.action === 'confirm') {
                axios.post(this.urlCard + '/store', {
                    title: action.card_name
                }).then(response => {
                    this.fetchCards();
                }).catch(err => {
                    console.warn(err);
                })
            } else if (action.action === 'cancel') {
                this.closeCardModal();
            }

            this.closeCardModal();
        },
        closeCardModal() {
            this.isModalCardOpen = false;
        },
        isEditing(index) {
            return this.editingIndex === index;
        },
        isEditingTask(statusIndex, taskIndex) {
            return this.statuses[statusIndex].kanban_tasks[taskIndex].isEditing;
        },

        editStatus(index) {
            this.editingIndex = index;
        },
        editTask(statusIndex, taskIndex) {
            this.enabled = false;
            this.statuses[statusIndex].kanban_tasks[taskIndex].isEditing = true;
        },

        saveStatus(index, id) {
            this.editingIndex = null;
            axios.put(this.urlCard + '/update/' + id, {
                title: this.statuses[index].title
            }).then(response => {
                if (response) {
                    this.editingIndex = null;
                    this.fetchCards();
                } else {
                    alert('Clusterfuck')
                }

            }).catch(err => {
                console.warn(err)
            })
        },
        saveTask(statusIndex, taskIndex) {
            const task = this.statuses[statusIndex].kanban_tasks[taskIndex];

            axios.put(this.urlTask + '/update/' + task.id, {
                task_name: task.task_name,
                task_description: task.task_description
            })
                .then(response => {
                    task.isEditing = false;
                    this.enabled = false;
                    this.fetchCards();
                })
                .catch(err => {
                    console.warn(err);
                });
        },

        addTaskModal(id, index) {
            this.isModalTaskOpen = true;
            this.idCard = id;
            this.indexCard = index;
        },
        onModalTaskButtonClick(action) {
            if (action.action === 'confirm') {
                axios.post(this.urlTask,
                    {
                        task_name: action.task_name,
                        task_description: action.task_description,
                        id: action.id,
                    }).then(response => {
                    this.fetchCards()
                }).catch(err => {
                    console.warn(err);
                })
            } else if (action.action === 'cancel') {
                this.closeModalTask();
            }

            this.closeModalTask();
        },
        deleteTask(id) {
            axios.delete(this.urlTask + '/destroy/' + id)
                .then(response => {
                    this.fetchCards();
                })
                .catch(err => {
                    console.warn(err)
                })
        }
    }
};
</script>

I am lil bit stuck, As you can see i am use draggable for moving cards, Now i want to save order for each card in db. Any hints how i can handle it ?

Many thanks

0 likes
0 replies

Please or to participate in this conversation.