cinqcinqdev-seo 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/module.d.mts +17 -1
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +19 -11
  4. package/dist/runtime/assets/admin-tw.css +1 -1
  5. package/dist/runtime/components/admin/ContentCalendar.d.vue.ts +3 -0
  6. package/dist/runtime/components/admin/ContentCalendar.vue +196 -0
  7. package/dist/runtime/components/admin/ContentCalendar.vue.d.ts +3 -0
  8. package/dist/runtime/components/admin/DashboardLayout.d.vue.ts +13 -0
  9. package/dist/runtime/components/admin/DashboardLayout.vue +12 -0
  10. package/dist/runtime/components/admin/DashboardLayout.vue.d.ts +13 -0
  11. package/dist/runtime/components/admin/NavItem.d.vue.ts +3 -0
  12. package/dist/runtime/components/admin/NavItem.vue +46 -0
  13. package/dist/runtime/components/admin/NavItem.vue.d.ts +3 -0
  14. package/dist/runtime/components/admin/Navbar.vue +1 -1
  15. package/dist/runtime/components/admin/Sidebar.d.vue.ts +3 -0
  16. package/dist/runtime/components/admin/Sidebar.vue +115 -0
  17. package/dist/runtime/components/admin/Sidebar.vue.d.ts +3 -0
  18. package/dist/runtime/composables/useAdminBranding.d.ts +4 -0
  19. package/dist/runtime/composables/useAdminBranding.js +22 -0
  20. package/dist/runtime/composables/useAdminContent.d.ts +9 -0
  21. package/dist/runtime/composables/useAdminContent.js +46 -0
  22. package/dist/runtime/composables/useAdminPersonas.d.ts +8 -0
  23. package/dist/runtime/composables/useAdminPersonas.js +31 -0
  24. package/dist/runtime/composables/useAdminProducts.d.ts +6 -0
  25. package/dist/runtime/composables/useAdminProducts.js +24 -0
  26. package/dist/runtime/composables/useAdminSections.js +1 -0
  27. package/dist/runtime/composables/useAdminStrategies.d.ts +10 -0
  28. package/dist/runtime/composables/useAdminStrategies.js +57 -0
  29. package/dist/runtime/composables/useAdminTodos.d.ts +10 -0
  30. package/dist/runtime/composables/useAdminTodos.js +55 -0
  31. package/dist/runtime/middleware/admin-auth.js +2 -0
  32. package/dist/runtime/pages/admin/account.vue +1 -0
  33. package/dist/runtime/pages/admin/branding.d.vue.ts +3 -0
  34. package/dist/runtime/pages/admin/branding.vue +276 -0
  35. package/dist/runtime/pages/admin/branding.vue.d.ts +3 -0
  36. package/dist/runtime/pages/admin/calendar.d.vue.ts +3 -0
  37. package/dist/runtime/pages/admin/calendar.vue +169 -0
  38. package/dist/runtime/pages/admin/calendar.vue.d.ts +3 -0
  39. package/dist/runtime/pages/admin/content-library.d.vue.ts +3 -0
  40. package/dist/runtime/pages/admin/content-library.vue +359 -0
  41. package/dist/runtime/pages/admin/content-library.vue.d.ts +3 -0
  42. package/dist/runtime/pages/admin/editor/[id].vue +5 -2
  43. package/dist/runtime/pages/admin/index.vue +81 -22
  44. package/dist/runtime/pages/admin/pages/[type].vue +1 -0
  45. package/dist/runtime/pages/admin/personas/[id].d.vue.ts +3 -0
  46. package/dist/runtime/pages/admin/personas/[id].vue +170 -0
  47. package/dist/runtime/pages/admin/personas/[id].vue.d.ts +3 -0
  48. package/dist/runtime/pages/admin/personas/index.d.vue.ts +3 -0
  49. package/dist/runtime/pages/admin/personas/index.vue +266 -0
  50. package/dist/runtime/pages/admin/personas/index.vue.d.ts +3 -0
  51. package/dist/runtime/pages/admin/products.d.vue.ts +3 -0
  52. package/dist/runtime/pages/admin/products.vue +300 -0
  53. package/dist/runtime/pages/admin/products.vue.d.ts +3 -0
  54. package/dist/runtime/pages/admin/strategies/[id].d.vue.ts +3 -0
  55. package/dist/runtime/pages/admin/strategies/[id].vue +237 -0
  56. package/dist/runtime/pages/admin/strategies/[id].vue.d.ts +3 -0
  57. package/dist/runtime/pages/admin/strategies/index.d.vue.ts +3 -0
  58. package/dist/runtime/pages/admin/strategies/index.vue +232 -0
  59. package/dist/runtime/pages/admin/strategies/index.vue.d.ts +3 -0
  60. package/dist/runtime/pages/admin/todos.d.vue.ts +3 -0
  61. package/dist/runtime/pages/admin/todos.vue +251 -0
  62. package/dist/runtime/pages/admin/todos.vue.d.ts +3 -0
  63. package/dist/runtime/plugins/admin-font.client.d.ts +0 -5
  64. package/dist/runtime/plugins/admin-font.client.js +1 -0
  65. package/dist/runtime/stores/adminUser.d.ts +29 -1
  66. package/dist/runtime/stores/adminUser.js +4 -2
  67. package/dist/types.d.mts +1 -1
  68. package/package.json +6 -3
@@ -0,0 +1,276 @@
1
+ <script setup>
2
+ import { ref, onMounted, useHead } from "#imports";
3
+ import { useAdminBranding } from "#imports";
4
+ useHead({ title: "Brand Management - Admin" });
5
+ const { fetchBranding, saveBranding } = useAdminBranding();
6
+ const viewMode = ref("docs");
7
+ const activeTab = ref("visual");
8
+ const saving = ref(false);
9
+ const loading = ref(true);
10
+ const brandingData = ref({
11
+ brand_name: "",
12
+ tagline: "",
13
+ logo_primary_url: "",
14
+ logo_secondary_url: "",
15
+ logo_icon_url: "",
16
+ logo_white_url: "",
17
+ color_primary: "#3B82F6",
18
+ color_secondary: "#60A5FA",
19
+ color_accent: "#2563EB",
20
+ color_text: "#1F2937",
21
+ color_background: "#FFFFFF",
22
+ font_heading: "Inter",
23
+ font_body: "Inter",
24
+ font_accent: "",
25
+ website_url: "",
26
+ brand_assets_url: "",
27
+ mission: "",
28
+ vision: "",
29
+ brand_story: "",
30
+ unique_value_proposition: "",
31
+ elevator_pitch: "",
32
+ voice_description: "",
33
+ target_audience: "",
34
+ brand_personality: ""
35
+ });
36
+ onMounted(async () => {
37
+ loading.value = true;
38
+ try {
39
+ const data = await fetchBranding();
40
+ if (data) brandingData.value = { ...brandingData.value, ...data };
41
+ } catch {
42
+ } finally {
43
+ loading.value = false;
44
+ }
45
+ });
46
+ const saveBrandingData = async () => {
47
+ saving.value = true;
48
+ try {
49
+ await saveBranding(brandingData.value);
50
+ alert("Branding enregistre avec succes!");
51
+ } catch {
52
+ alert("Erreur lors de l'enregistrement");
53
+ } finally {
54
+ saving.value = false;
55
+ }
56
+ };
57
+ </script>
58
+
59
+ <template>
60
+ <AdminDashboardLayout>
61
+ <div class="max-w-7xl mx-auto px-6 md:px-10 py-10">
62
+ <div class="flex items-center justify-between mb-8">
63
+ <div>
64
+ <h1 class="text-4xl font-bold text-slate-900">Brand Management</h1>
65
+ <p class="text-slate-500 mt-1">Gerez l'identite visuelle et la voix de votre marque</p>
66
+ </div>
67
+ <div class="flex gap-3">
68
+ <div class="flex gap-2 bg-white p-1.5 rounded-xl border border-slate-200">
69
+ <button @click="viewMode = 'edit'" :class="[viewMode === 'edit' ? 'bg-blue-600 text-white' : 'text-slate-600 hover:bg-slate-50']" class="px-4 py-2 rounded-lg font-semibold text-sm transition-all flex items-center gap-2">
70
+ <Icon name="mdi:pencil" /> Edit
71
+ </button>
72
+ <button @click="viewMode = 'docs'" :class="[viewMode === 'docs' ? 'bg-blue-600 text-white' : 'text-slate-600 hover:bg-slate-50']" class="px-4 py-2 rounded-lg font-semibold text-sm transition-all flex items-center gap-2">
73
+ <Icon name="mdi:file-document" /> Docs
74
+ </button>
75
+ </div>
76
+ <button v-if="viewMode === 'edit'" @click="saveBrandingData" :disabled="saving" class="px-6 py-3 bg-blue-600 text-white rounded-xl font-semibold hover:bg-blue-700 shadow-lg disabled:opacity-50 flex items-center gap-2">
77
+ <Icon name="mdi:content-save" />
78
+ {{ saving ? "Enregistrement..." : "Enregistrer" }}
79
+ </button>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Docs View -->
84
+ <div v-if="viewMode === 'docs'" class="space-y-8">
85
+ <div class="bg-gradient-to-br from-blue-600 to-blue-800 rounded-3xl p-8 text-white shadow-2xl">
86
+ <div class="flex items-start justify-between mb-6">
87
+ <div>
88
+ <h2 class="text-3xl font-bold mb-2">{{ brandingData.brand_name || "Votre Marque" }}</h2>
89
+ <p class="text-blue-100 text-lg">{{ brandingData.tagline || "Votre tagline" }}</p>
90
+ </div>
91
+ <img v-if="brandingData.logo_primary_url" :src="brandingData.logo_primary_url" alt="Logo" class="h-16 w-auto bg-white p-2 rounded-xl" />
92
+ </div>
93
+ <div class="grid grid-cols-2 gap-6">
94
+ <div>
95
+ <h3 class="text-sm font-semibold text-blue-100 mb-2">Mission</h3>
96
+ <p class="text-white">{{ brandingData.mission || "Non definie" }}</p>
97
+ </div>
98
+ <div>
99
+ <h3 class="text-sm font-semibold text-blue-100 mb-2">Vision</h3>
100
+ <p class="text-white">{{ brandingData.vision || "Non definie" }}</p>
101
+ </div>
102
+ </div>
103
+ </div>
104
+
105
+ <div class="bg-white rounded-3xl p-8 border border-slate-200">
106
+ <h2 class="text-2xl font-bold text-slate-900 mb-6 flex items-center gap-2">
107
+ <Icon name="mdi:palette" class="text-blue-600" /> Palette de Couleurs
108
+ </h2>
109
+ <div class="grid grid-cols-5 gap-4">
110
+ <div v-for="[key, label] in [['color_primary', 'Primaire'], ['color_secondary', 'Secondaire'], ['color_accent', 'Accent'], ['color_text', 'Texte'], ['color_background', 'Fond']]" :key="key" class="text-center">
111
+ <div :style="{ backgroundColor: brandingData[key] }" class="w-full h-24 rounded-xl border-2 border-slate-200 shadow-sm"></div>
112
+ <p class="text-xs font-semibold text-slate-700 mt-2">{{ label }}</p>
113
+ <p class="text-xs text-slate-500 font-mono">{{ brandingData[key] }}</p>
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ <div class="bg-white rounded-3xl p-8 border border-slate-200">
119
+ <h2 class="text-2xl font-bold text-slate-900 mb-6 flex items-center gap-2">
120
+ <Icon name="mdi:microphone" class="text-blue-600" /> Voix & Messaging
121
+ </h2>
122
+ <div class="space-y-4">
123
+ <div v-if="brandingData.unique_value_proposition">
124
+ <h3 class="text-lg font-semibold text-slate-700 mb-1">Proposition de Valeur</h3>
125
+ <p class="text-slate-600">{{ brandingData.unique_value_proposition }}</p>
126
+ </div>
127
+ <div v-if="brandingData.elevator_pitch">
128
+ <h3 class="text-lg font-semibold text-slate-700 mb-1">Elevator Pitch</h3>
129
+ <p class="text-slate-600">{{ brandingData.elevator_pitch }}</p>
130
+ </div>
131
+ <div v-if="brandingData.voice_description">
132
+ <h3 class="text-lg font-semibold text-slate-700 mb-1">Voix de Marque</h3>
133
+ <p class="text-slate-600">{{ brandingData.voice_description }}</p>
134
+ </div>
135
+ </div>
136
+ </div>
137
+
138
+ <div class="bg-white rounded-3xl p-8 border border-slate-200">
139
+ <h2 class="text-2xl font-bold text-slate-900 mb-6 flex items-center gap-2">
140
+ <Icon name="mdi:link" class="text-blue-600" /> Ressources
141
+ </h2>
142
+ <div class="grid grid-cols-2 gap-4">
143
+ <a v-if="brandingData.website_url" :href="brandingData.website_url" target="_blank" class="flex items-center gap-3 p-4 bg-blue-50 border-2 border-blue-200 rounded-xl hover:bg-blue-100 transition-colors">
144
+ <Icon name="mdi:web" class="text-blue-600 text-xl" />
145
+ <div>
146
+ <p class="font-semibold text-slate-900">Site Web</p>
147
+ <p class="text-xs text-slate-600 truncate">{{ brandingData.website_url }}</p>
148
+ </div>
149
+ </a>
150
+ <a v-if="brandingData.brand_assets_url" :href="brandingData.brand_assets_url" target="_blank" class="flex items-center gap-3 p-4 bg-blue-50 border-2 border-blue-200 rounded-xl hover:bg-blue-100 transition-colors">
151
+ <Icon name="mdi:folder-image" class="text-blue-600 text-xl" />
152
+ <div>
153
+ <p class="font-semibold text-slate-900">Assets de Marque</p>
154
+ <p class="text-xs text-slate-600 truncate">{{ brandingData.brand_assets_url }}</p>
155
+ </div>
156
+ </a>
157
+ </div>
158
+ </div>
159
+ </div>
160
+
161
+ <!-- Edit View -->
162
+ <div v-if="viewMode === 'edit'">
163
+ <div class="flex gap-2 bg-white p-2 rounded-2xl border border-slate-200 inline-flex mb-8">
164
+ <button @click="activeTab = 'visual'" :class="[activeTab === 'visual' ? 'bg-blue-600 text-white shadow-lg' : 'text-slate-600 hover:bg-slate-50']" class="px-5 py-2.5 rounded-xl font-semibold text-sm transition-all flex items-center gap-2">
165
+ <Icon name="mdi:palette" /> Identite Visuelle
166
+ </button>
167
+ <button @click="activeTab = 'voice'" :class="[activeTab === 'voice' ? 'bg-blue-600 text-white shadow-lg' : 'text-slate-600 hover:bg-slate-50']" class="px-5 py-2.5 rounded-xl font-semibold text-sm transition-all flex items-center gap-2">
168
+ <Icon name="mdi:microphone" /> Voix de Marque
169
+ </button>
170
+ </div>
171
+
172
+ <div v-if="activeTab === 'visual'" class="space-y-8">
173
+ <div class="bg-white rounded-3xl p-8 border border-slate-200">
174
+ <h2 class="text-xl font-bold text-slate-900 mb-6">Informations de Base</h2>
175
+ <div class="grid grid-cols-2 gap-6">
176
+ <div>
177
+ <label class="block text-sm font-semibold text-slate-700 mb-2">Nom de la Marque</label>
178
+ <input v-model="brandingData.brand_name" type="text" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-blue-500 focus:outline-none" />
179
+ </div>
180
+ <div>
181
+ <label class="block text-sm font-semibold text-slate-700 mb-2">Tagline</label>
182
+ <input v-model="brandingData.tagline" type="text" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-blue-500 focus:outline-none" />
183
+ </div>
184
+ </div>
185
+ </div>
186
+
187
+ <div class="bg-white rounded-3xl p-8 border border-slate-200">
188
+ <h2 class="text-xl font-bold text-slate-900 mb-6">Logos (URLs)</h2>
189
+ <div class="grid grid-cols-2 gap-6">
190
+ <div v-for="[key, label] in [['logo_primary_url', 'Logo Principal'], ['logo_secondary_url', 'Logo Secondaire'], ['logo_icon_url', 'Icone'], ['logo_white_url', 'Logo Blanc']]" :key="key">
191
+ <label class="block text-sm font-semibold text-slate-700 mb-2">{{ label }}</label>
192
+ <div class="flex gap-2">
193
+ <img v-if="brandingData[key]" :src="brandingData[key]" alt="" class="w-12 h-12 object-contain rounded-lg border border-slate-200" />
194
+ <input v-model="brandingData[key]" type="url" placeholder="https://..." class="flex-1 px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-blue-500 focus:outline-none" />
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </div>
199
+
200
+ <div class="bg-white rounded-3xl p-8 border border-slate-200">
201
+ <h2 class="text-xl font-bold text-slate-900 mb-6">Palette de Couleurs</h2>
202
+ <div class="grid grid-cols-2 md:grid-cols-5 gap-6">
203
+ <div v-for="[key, label] in [['color_primary', 'Principale'], ['color_secondary', 'Secondaire'], ['color_accent', 'Accent'], ['color_text', 'Texte'], ['color_background', 'Fond']]" :key="key">
204
+ <label class="block text-sm font-semibold text-slate-700 mb-2">{{ label }}</label>
205
+ <div class="flex gap-2">
206
+ <input v-model="brandingData[key]" type="color" class="w-12 h-12 border-2 border-slate-200 rounded-xl cursor-pointer flex-shrink-0" />
207
+ <input v-model="brandingData[key]" type="text" class="flex-1 px-3 py-2 border-2 border-slate-200 rounded-xl text-sm" />
208
+ </div>
209
+ </div>
210
+ </div>
211
+ </div>
212
+
213
+ <div class="bg-white rounded-3xl p-8 border border-slate-200">
214
+ <h2 class="text-xl font-bold text-slate-900 mb-6">Typographie</h2>
215
+ <div class="grid grid-cols-3 gap-6">
216
+ <div v-for="[key, label] in [['font_heading', 'Police Titres'], ['font_body', 'Police Corps'], ['font_accent', 'Police Accent']]" :key="key">
217
+ <label class="block text-sm font-semibold text-slate-700 mb-2">{{ label }}</label>
218
+ <input v-model="brandingData[key]" type="text" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-blue-500 focus:outline-none" />
219
+ </div>
220
+ </div>
221
+ </div>
222
+
223
+ <div class="bg-white rounded-3xl p-8 border border-slate-200">
224
+ <h2 class="text-xl font-bold text-slate-900 mb-6">Liens</h2>
225
+ <div class="grid grid-cols-2 gap-6">
226
+ <div>
227
+ <label class="block text-sm font-semibold text-slate-700 mb-2">Site Web</label>
228
+ <input v-model="brandingData.website_url" type="url" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl" placeholder="https://..." />
229
+ </div>
230
+ <div>
231
+ <label class="block text-sm font-semibold text-slate-700 mb-2">Assets de Marque</label>
232
+ <input v-model="brandingData.brand_assets_url" type="url" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl" placeholder="https://..." />
233
+ </div>
234
+ </div>
235
+ </div>
236
+ </div>
237
+
238
+ <div v-if="activeTab === 'voice'" class="space-y-8">
239
+ <div class="bg-white rounded-3xl p-8 border border-slate-200">
240
+ <h2 class="text-xl font-bold text-slate-900 mb-6">Identite de Base</h2>
241
+ <div class="space-y-6">
242
+ <div v-for="[key, label, ph] in [['mission', 'Mission', 'Pourquoi votre entreprise existe...'], ['vision', 'Vision', 'Ou vous voulez aller...'], ['brand_story', 'Histoire de la Marque', 'Racontez votre histoire...']]" :key="key">
243
+ <label class="block text-sm font-semibold text-slate-700 mb-2">{{ label }}</label>
244
+ <textarea v-model="brandingData[key]" rows="3" :placeholder="ph" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-blue-500 focus:outline-none"></textarea>
245
+ </div>
246
+ </div>
247
+ </div>
248
+
249
+ <div class="bg-white rounded-3xl p-8 border border-slate-200">
250
+ <h2 class="text-xl font-bold text-slate-900 mb-6">Proposition de Valeur</h2>
251
+ <div class="space-y-6">
252
+ <div v-for="[key, label, ph] in [['unique_value_proposition', 'UVP', 'Ce qui vous differencie...'], ['elevator_pitch', 'Elevator Pitch', 'Votre pitch en 30 secondes...'], ['voice_description', 'Voix de Marque', 'Comment votre marque sonne...']]" :key="key">
253
+ <label class="block text-sm font-semibold text-slate-700 mb-2">{{ label }}</label>
254
+ <textarea v-model="brandingData[key]" rows="3" :placeholder="ph" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-blue-500 focus:outline-none"></textarea>
255
+ </div>
256
+ </div>
257
+ </div>
258
+
259
+ <div class="bg-white rounded-3xl p-8 border border-slate-200">
260
+ <h2 class="text-xl font-bold text-slate-900 mb-6">Positionnement</h2>
261
+ <div class="grid grid-cols-2 gap-6">
262
+ <div>
263
+ <label class="block text-sm font-semibold text-slate-700 mb-2">Audience Cible</label>
264
+ <textarea v-model="brandingData.target_audience" rows="4" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-blue-500 focus:outline-none" placeholder="Qui sont vos clients ideaux..."></textarea>
265
+ </div>
266
+ <div>
267
+ <label class="block text-sm font-semibold text-slate-700 mb-2">Personnalite de Marque</label>
268
+ <textarea v-model="brandingData.brand_personality" rows="4" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:border-blue-500 focus:outline-none" placeholder="Si votre marque etait une personne..."></textarea>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ </div>
275
+ </AdminDashboardLayout>
276
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: any;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: any;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,169 @@
1
+ <script setup>
2
+ import { ref, onMounted, useHead } from "#imports";
3
+ import { useAdminContent, useAdminPersonas } from "#imports";
4
+ useHead({ title: "Calendrier Editorial - Admin" });
5
+ const { fetchContent, createContent, updateContent } = useAdminContent();
6
+ const { fetchPersonas } = useAdminPersonas();
7
+ const allContents = ref([]);
8
+ const personas = ref([]);
9
+ const showModal = ref(false);
10
+ const saving = ref(false);
11
+ const editingContent = ref(null);
12
+ const instagramTypes = ["Story", "Reel", "Carousel", "Post"];
13
+ const defaultForm = () => ({
14
+ title: "",
15
+ platform: "LinkedIn",
16
+ content_type: "Post Social",
17
+ content: "",
18
+ persona_id: "",
19
+ persona_name: "",
20
+ status: "draft",
21
+ scheduled_date: "",
22
+ instagram_type: "",
23
+ content_url: ""
24
+ });
25
+ const formData = ref(defaultForm());
26
+ const addContentToDay = (date) => {
27
+ editingContent.value = null;
28
+ formData.value = { ...defaultForm(), scheduled_date: date.toISOString().slice(0, 16) };
29
+ showModal.value = true;
30
+ };
31
+ const openEditModal = (content) => {
32
+ editingContent.value = content;
33
+ formData.value = { ...content, scheduled_date: content.scheduled_date ? new Date(content.scheduled_date).toISOString().slice(0, 16) : "" };
34
+ showModal.value = true;
35
+ };
36
+ const closeModal = () => {
37
+ showModal.value = false;
38
+ editingContent.value = null;
39
+ formData.value = defaultForm();
40
+ };
41
+ const updatePersonaName = () => {
42
+ const p = personas.value.find((p2) => p2.id === formData.value.persona_id);
43
+ formData.value.persona_name = p ? p.name : "";
44
+ };
45
+ const saveContent = async () => {
46
+ saving.value = true;
47
+ try {
48
+ const data = { ...formData.value };
49
+ if (data.scheduled_date) data.scheduled_date = new Date(data.scheduled_date).toISOString();
50
+ if (editingContent.value) {
51
+ await updateContent(editingContent.value.id, data);
52
+ } else {
53
+ await createContent(data);
54
+ }
55
+ allContents.value = await fetchContent();
56
+ closeModal();
57
+ } catch (e) {
58
+ console.error(e);
59
+ alert("Erreur lors de l'enregistrement");
60
+ } finally {
61
+ saving.value = false;
62
+ }
63
+ };
64
+ onMounted(async () => {
65
+ try {
66
+ const [c, p] = await Promise.all([fetchContent(), fetchPersonas()]);
67
+ allContents.value = c || [];
68
+ personas.value = p || [];
69
+ } catch {
70
+ }
71
+ });
72
+ </script>
73
+
74
+ <template>
75
+ <AdminDashboardLayout>
76
+ <div class="max-w-7xl mx-auto px-6 md:px-10 py-10">
77
+ <div class="flex items-center justify-between mb-8">
78
+ <div>
79
+ <h1 class="text-4xl font-bold text-slate-900 mb-1">Calendrier Editorial</h1>
80
+ <p class="text-slate-500">Planifiez et gerez vos contenus</p>
81
+ </div>
82
+ <button @click="showModal = true" class="px-6 py-3 bg-blue-600 text-white rounded-2xl text-sm font-black uppercase hover:bg-blue-700 transition-all shadow-lg shadow-blue-200 flex items-center gap-2">
83
+ <Icon name="mdi:plus-circle" class="text-xl" />
84
+ Nouveau Contenu
85
+ </button>
86
+ </div>
87
+
88
+ <AdminContentCalendar
89
+ :contents="allContents"
90
+ @add-content="addContentToDay"
91
+ @edit-content="openEditModal"
92
+ />
93
+
94
+ <Teleport to="body">
95
+ <div v-if="showModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4" @click.self="closeModal">
96
+ <div class="bg-white rounded-[2rem] shadow-2xl max-w-3xl w-full max-h-[90vh] overflow-y-auto">
97
+ <div class="p-8">
98
+ <div class="flex justify-between items-center mb-6">
99
+ <h2 class="text-2xl font-black text-slate-900">{{ editingContent ? "Modifier le Contenu" : "Nouveau Contenu" }}</h2>
100
+ <button @click="closeModal" class="w-10 h-10 bg-slate-100 hover:bg-slate-200 rounded-xl flex items-center justify-center transition-all">
101
+ <Icon name="mdi:close" class="text-xl" />
102
+ </button>
103
+ </div>
104
+ <form @submit.prevent="saveContent" class="space-y-6">
105
+ <div class="space-y-2">
106
+ <label class="text-[10px] font-black uppercase text-slate-400 ml-2">Titre *</label>
107
+ <input v-model="formData.title" type="text" required class="w-full bg-slate-50 border-none rounded-2xl px-6 py-4 font-bold text-slate-900 placeholder:text-slate-400" placeholder="Titre du contenu" />
108
+ </div>
109
+ <div class="grid grid-cols-2 gap-6">
110
+ <div class="space-y-2">
111
+ <label class="text-[10px] font-black uppercase text-slate-400 ml-2">Plateforme</label>
112
+ <select v-model="formData.platform" class="w-full bg-blue-50 border-none rounded-2xl px-6 py-4 font-bold text-blue-900 appearance-none">
113
+ <option v-for="p in ['LinkedIn', 'Instagram', 'Facebook', 'Twitter', 'Email', 'Blog', 'YouTube', 'Website']" :key="p" :value="p">{{ p }}</option>
114
+ </select>
115
+ </div>
116
+ <div class="space-y-2">
117
+ <label class="text-[10px] font-black uppercase text-slate-400 ml-2">Type</label>
118
+ <select v-model="formData.content_type" class="w-full bg-emerald-50 border-none rounded-2xl px-6 py-4 font-bold text-emerald-900 appearance-none">
119
+ <option v-for="t in ['Post Social', 'Email', 'Article Blog', 'Script Video', 'Carousel', 'Newsletter']" :key="t" :value="t">{{ t }}</option>
120
+ </select>
121
+ </div>
122
+ </div>
123
+ <div v-if="formData.platform === 'Instagram'" class="space-y-2">
124
+ <label class="text-[10px] font-black uppercase text-slate-400 ml-2">Type Instagram</label>
125
+ <div class="grid grid-cols-4 gap-3">
126
+ <button type="button" v-for="t in instagramTypes" :key="t" @click="formData.instagram_type = t"
127
+ :class="[formData.instagram_type === t ? 'bg-pink-600 text-white shadow-lg' : 'bg-pink-50 text-pink-600 hover:bg-pink-100']"
128
+ class="px-4 py-3 rounded-xl text-xs font-black uppercase transition-all">{{ t }}</button>
129
+ </div>
130
+ </div>
131
+ <div class="space-y-2">
132
+ <label class="text-[10px] font-black uppercase text-slate-400 ml-2">Date de publication *</label>
133
+ <input v-model="formData.scheduled_date" type="datetime-local" required class="w-full bg-purple-50 border-2 border-purple-200 rounded-2xl px-6 py-4 font-bold text-purple-900" />
134
+ </div>
135
+ <div class="space-y-2">
136
+ <label class="text-[10px] font-black uppercase text-slate-400 ml-2">Contenu *</label>
137
+ <textarea v-model="formData.content" required rows="5" class="w-full bg-slate-50 border-none rounded-2xl px-6 py-4 font-medium text-slate-900 placeholder:text-slate-400 resize-none" placeholder="Contenu..."></textarea>
138
+ </div>
139
+ <div class="grid grid-cols-2 gap-6">
140
+ <div class="space-y-2">
141
+ <label class="text-[10px] font-black uppercase text-slate-400 ml-2">Persona</label>
142
+ <select v-model="formData.persona_id" @change="updatePersonaName" class="w-full bg-slate-50 border-none rounded-2xl px-6 py-4 font-bold text-slate-600 appearance-none">
143
+ <option value="">Generique / Tous</option>
144
+ <option v-for="p in personas" :key="p.id" :value="p.id">{{ p.name }}</option>
145
+ </select>
146
+ </div>
147
+ <div class="space-y-2">
148
+ <label class="text-[10px] font-black uppercase text-slate-400 ml-2">Statut</label>
149
+ <div class="flex gap-2 mt-2">
150
+ <button type="button" v-for="s in [{ v: 'draft', l: 'Brouillon' }, { v: 'approved', l: 'Planifie' }, { v: 'published', l: 'Publie' }]" :key="s.v" @click="formData.status = s.v"
151
+ :class="[formData.status === s.v ? 'bg-blue-500 text-white shadow-lg' : 'bg-slate-50 text-slate-400']"
152
+ class="px-4 py-3 rounded-xl text-[10px] font-black uppercase flex-1 transition-all">{{ s.l }}</button>
153
+ </div>
154
+ </div>
155
+ </div>
156
+ <div class="flex justify-end gap-4 pt-4 border-t border-slate-200">
157
+ <button type="button" @click="closeModal" class="px-8 py-3 bg-slate-100 text-slate-700 rounded-xl font-bold hover:bg-slate-200 transition-all">Annuler</button>
158
+ <button type="submit" :disabled="saving" class="px-8 py-3 bg-blue-600 text-white rounded-xl font-bold hover:bg-blue-700 transition-all shadow-lg shadow-blue-200 disabled:opacity-50">
159
+ {{ saving ? "Enregistrement..." : editingContent ? "Mettre a jour" : "Creer" }}
160
+ </button>
161
+ </div>
162
+ </form>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </Teleport>
167
+ </div>
168
+ </AdminDashboardLayout>
169
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: any;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: any;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;