11<script setup lang="ts">
2- import { ref , computed } from ' vue' ;
3- import { useWorkspaceStore } from ' @/stores/workspace' ;
4- import { useCardsStore } from ' @/stores/cards' ;
2+ import { ref , computed , toRef } from ' vue' ;
53import type { CardGql } from ' @/graphql/types' ;
64import { parseCard } from ' @/lib/card' ;
75import { useInlineEdit } from ' @/composables/useInlineEdit' ;
6+ import { useCardActions } from ' @/composables/useCardActions' ;
87import CardNote from ' ./CardNote.vue' ;
98import CardLink from ' ./CardLink.vue' ;
109import CardChecklist from ' ./CardChecklist.vue' ;
1110import CreateCardDialog from ' @/components/card/CreateCardDialog.vue' ;
12- import { toast } from ' vue-sonner' ;
13- import { getGraphQLErrorMessage } from ' @/lib/graphql-error' ;
1411
1512const props = withDefaults (
1613 defineProps <{
1714 card: CardGql ;
1815 columnId? : string ;
1916 /** Беклог: карточка без колонки, только просмотр/редакт/удаление */
2017 isBacklog? : boolean ;
18+ /** Отключить drag-курсор (для Tags view и т.п.) */
19+ static? : boolean ;
2120 }>(),
22- { isBacklog: false }
21+ { isBacklog: false , static: false }
2322);
2423
25- const workspaceStore = useWorkspaceStore ();
26- const cardsStore = useCardsStore ();
24+ const emit = defineEmits <{
25+ (e : ' updated' , card : CardGql ): void ;
26+ (e : ' deleted' , cardId : string ): void ;
27+ }>();
28+
2729const showEditDialog = ref (false );
2830const titleContainerRef = ref <HTMLElement | null >(null );
2931
32+ // Используем composable для всех операций с карточкой
33+ const cardActions = useCardActions (toRef (props , ' card' ), {
34+ onUpdated : (card ) => emit (' updated' , card ),
35+ onDeleted : (cardId ) => emit (' deleted' , cardId ),
36+ });
37+
3038const { isEditing : isEditingTitle, editTitle, inputRef : titleInputRef, startEdit : startEditTitle, saveEdit : saveEditTitle, cancelEdit : cancelEditTitle } = useInlineEdit (
3139 titleContainerRef ,
3240 () => props .card .title ?? ' ' ,
3341 async (newTitle ) => {
34- const payload = props .card .payload ;
35- if (isHubCard .value ) await cardsStore .updateCard (props .card .id , { title: newTitle || undefined , payload });
36- else await workspaceStore .updateCard (props .card .id , { title: newTitle || undefined , payload });
42+ await cardActions .updateCard ({ title: newTitle || undefined , payload: props .card .payload });
3743 }
3844);
3945
40- const isHubCard = computed (() => props .isBacklog && props .card .workspaceId == null );
41-
4246const parsed = computed (() => {
4347 try {
4448 return parseCard (props .card );
@@ -66,79 +70,9 @@ const linkUrl = computed(() => {
6670 return (parsed .value .payload as { url? : string }).url ?? null ;
6771});
6872
69- async function toggleDone() {
70- try {
71- const payload = { done: ! props .card .done };
72- if (isHubCard .value ) await cardsStore .updateCard (props .card .id , payload );
73- else await workspaceStore .updateCard (props .card .id , payload );
74- } catch (e ) {
75- toast .error (getGraphQLErrorMessage (e ));
76- }
77- }
78-
79- async function toggleChecklistItem(index : number ) {
80- if (props .card .type !== ' CHECKLIST' ) return ;
81- try {
82- const raw = typeof props .card .payload === ' string' ? JSON .parse (props .card .payload || ' {}' ) : props .card .payload ;
83- const items = Array .isArray ((raw as { items? : unknown [] }).items ) ? (raw as { items: { id: string ; text: string ; done: boolean ; order: number }[] }).items : [];
84- const next = items .map ((it , i ) => (i === index ? { ... it , done: ! it .done } : it ));
85- const payload = { payload: JSON .stringify ({ items: next }) };
86- if (isHubCard .value ) await cardsStore .updateCard (props .card .id , payload );
87- else await workspaceStore .updateCard (props .card .id , payload );
88- } catch (e ) {
89- toast .error (getGraphQLErrorMessage (e ));
90- }
91- }
92-
93- async function doDelete() {
94- try {
95- if (isHubCard .value ) {
96- await cardsStore .deleteCard (props .card .id );
97- toast (' Карточка удалена' , {
98- action: {
99- label: ' Отменить' ,
100- onClick : () => {
101- cardsStore .undoDeleteCard ().catch ((e ) => {
102- toast .error (getGraphQLErrorMessage (e ));
103- });
104- },
105- },
106- duration: 5000 ,
107- });
108- } else {
109- await workspaceStore .deleteCard (props .card .id );
110- toast (' Карточка удалена' , {
111- action: {
112- label: ' Отменить' ,
113- onClick : () => {
114- workspaceStore .undoDeleteCard ().catch ((e ) => {
115- toast .error (getGraphQLErrorMessage (e ));
116- });
117- },
118- },
119- duration: 5000 ,
120- });
121- }
122- } catch (e ) {
123- toast .error (getGraphQLErrorMessage (e ));
124- }
125- }
126-
12773function handleDeleteFromDialog() {
128- if (! confirm (' Удалить карточку?' )) return ;
129- doDelete ();
13074 showEditDialog .value = false ;
131- }
132-
133- async function updateSummary(newSummary : string ) {
134- try {
135- const raw = typeof props .card .payload === ' string' ? JSON .parse (props .card .payload || ' {}' ) : props .card .payload ;
136- const payload = JSON .stringify ({ ... raw , summary: newSummary .trim () || undefined });
137- if (isHubCard .value ) await cardsStore .updateCard (props .card .id , { payload });
138- else await workspaceStore .updateCard (props .card .id , { payload });
139- } catch (e ) {
140- toast .error (getGraphQLErrorMessage (e ));
141- }
75+ cardActions .deleteCard ();
14276}
14377 </script >
14478
@@ -157,13 +91,13 @@ async function updateSummary(newSummary: string) {
15791 class =" card"
15892 :class =" [
15993 { 'opacity-60': card.done },
160- columnId || (isBacklog && card.workspaceId != null) ? 'cursor-grab active:cursor-grabbing' : 'cursor-default',
94+ !static && ( columnId || (isBacklog && card.workspaceId != null) ) ? 'cursor-grab active:cursor-grabbing' : 'cursor-default',
16195 ]"
16296 >
16397 <div class =" flex items-center gap-2 p-3 pb-0" >
16498 <button
16599 type =" button"
166- @click =" toggleDone"
100+ @click =" cardActions. toggleDone() "
167101 class =" w-4 h-4 rounded flex-center border transition-colors shrink-0"
168102 :class =" card.done ? 'bg-success border-success text-on-primary' : 'border-border hover:border-success'"
169103 >
@@ -203,24 +137,24 @@ async function updateSummary(newSummary: string) {
203137 :done =" card.done"
204138 :payload =" parsed.payload"
205139 :editable-summary =" true"
206- @update-summary =" updateSummary"
140+ @update-summary =" cardActions. updateSummary"
207141 />
208142 <CardLink
209143 v-else-if =" parsed.type === 'link'"
210144 :title =" card.title"
211145 :done =" card.done"
212146 :payload =" parsed.payload"
213147 :editable-summary =" true"
214- @update-summary =" updateSummary"
148+ @update-summary =" cardActions. updateSummary"
215149 />
216150 <CardChecklist
217151 v-else-if =" parsed.type === 'checklist'"
218152 :title =" card.title"
219153 :done =" card.done"
220154 :payload =" parsed.payload"
221155 :editable-summary =" true"
222- @toggle-item =" toggleChecklistItem"
223- @update-summary =" updateSummary"
156+ @toggle-item =" cardActions. toggleChecklistItem"
157+ @update-summary =" cardActions. updateSummary"
224158 />
225159 </template >
226160
@@ -248,6 +182,7 @@ async function updateSummary(newSummary: string) {
248182 :is-backlog =" isBacklog"
249183 @close =" showEditDialog = false"
250184 @delete =" handleDeleteFromDialog"
185+ @updated =" (c) => emit('updated', c)"
251186 />
252187 </div >
253188</template >
0 commit comments