1
+ <script >
2
+ import {onMount } from " svelte" ;
3
+ import {contactList } from " $lib/api/ContactApi.js" ;
4
+ import {alertError } from " $lib/alert.js" ;
5
+
6
+ const token = localStorage .getItem (' token' );
7
+ const search = $state ({
8
+ name: " " ,
9
+ email: " " ,
10
+ phone: " " ,
11
+ page: 1
12
+ })
13
+ let totalPage = $state (0 )
14
+ let pages = $derived .by (() => {
15
+ const data = [];
16
+ for (let i = 1 ; i <= totalPage; i++ ) {
17
+ data .push (i);
18
+ }
19
+ return data;
20
+ })
21
+ let contacts = $state ([])
22
+
23
+ async function handlePageChange (value ) {
24
+ search .page = value;
25
+ await fetchContacts ();
26
+ }
27
+
28
+ async function handleSearch (e ) {
29
+ e .preventDefault ();
30
+ search .page = 1 ;
31
+
32
+ await fetchContacts ();
33
+ }
34
+
35
+ async function fetchContacts () {
36
+ const response = await contactList (token, search)
37
+ const responseBody = await response .json ()
38
+ console .log (responseBody)
39
+
40
+ if (response .status === 200 ) {
41
+ contacts = responseBody .data ;
42
+ totalPage = responseBody .paging .total_page ;
43
+ } else {
44
+ await alertError (responseBody .errors );
45
+ }
46
+ }
47
+
48
+ function toggleSearchForm () {
49
+ const toggleButton = document .getElementById (' toggleSearchForm' );
50
+ const searchFormContent = document .getElementById (' searchFormContent' );
51
+ const toggleIcon = document .getElementById (' toggleSearchIcon' );
52
+
53
+ // Add transition for smooth animation
54
+ searchFormContent .style .transition = ' max-height 0.3s ease-in-out, opacity 0.3s ease-in-out, margin 0.3s ease-in-out' ;
55
+ searchFormContent .style .overflow = ' hidden' ;
56
+ searchFormContent .style .maxHeight = ' 0px' ;
57
+ searchFormContent .style .opacity = ' 0' ;
58
+ searchFormContent .style .marginTop = ' 0' ;
59
+
60
+ toggleButton .addEventListener (' click' , function () {
61
+ if (searchFormContent .style .maxHeight !== ' 0px' ) {
62
+ // Hide the form
63
+ searchFormContent .style .maxHeight = ' 0px' ;
64
+ searchFormContent .style .opacity = ' 0' ;
65
+ searchFormContent .style .marginTop = ' 0' ;
66
+ toggleIcon .classList .remove (' fa-chevron-up' );
67
+ toggleIcon .classList .add (' fa-chevron-down' );
68
+ } else {
69
+ // Show the form
70
+ searchFormContent .style .maxHeight = searchFormContent .scrollHeight + ' px' ;
71
+ searchFormContent .style .opacity = ' 1' ;
72
+ searchFormContent .style .marginTop = ' 1rem' ;
73
+ toggleIcon .classList .remove (' fa-chevron-down' );
74
+ toggleIcon .classList .add (' fa-chevron-up' );
75
+ }
76
+ });
77
+ }
78
+
79
+ onMount (async () => {
80
+ toggleSearchForm ();
81
+ await fetchContacts ();
82
+ })
83
+ </script >
84
+
85
+ <div class =" flex items-center mb-6" >
86
+ <i class =" fas fa-users text-blue-400 text-2xl mr-3" ></i >
87
+ <h1 class =" text-2xl font-bold text-white" >My Contacts</h1 >
88
+ </div >
89
+
90
+ <!-- Search form -->
91
+ <div class =" bg-gray-800 bg-opacity-80 rounded-xl shadow-custom border border-gray-700 p-6 mb-8 animate-fade-in" >
92
+ <div class =" flex items-center justify-between mb-4" >
93
+ <div class =" flex items-center" >
94
+ <i class =" fas fa-search text-blue-400 mr-3" ></i >
95
+ <h2 class =" text-xl font-semibold text-white" >Search Contacts</h2 >
96
+ </div >
97
+ <button type =" button" id =" toggleSearchForm"
98
+ class =" text-gray-300 hover:text-white hover:bg-gray-700 p-2 rounded-full focus:outline-none transition-all duration-200" >
99
+ <i class =" fas fa-chevron-down text-lg" id =" toggleSearchIcon" ></i >
100
+ </button >
101
+ </div >
102
+ <div id =" searchFormContent" class =" mt-4" >
103
+ <form onsubmit ={handleSearch }>
104
+ <div class =" grid grid-cols-1 md:grid-cols-3 gap-5" >
105
+ <div >
106
+ <label for =" search_name" class =" block text-gray-300 text-sm font-medium mb-2" >Name</label >
107
+ <div class =" relative" >
108
+ <div class =" absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none" >
109
+ <i class =" fas fa-user text-gray-500" ></i >
110
+ </div >
111
+ <input type =" text" id =" search_name" name =" search_name"
112
+ class =" w-full pl-10 pr-3 py-3 bg-gray-700 bg-opacity-50 border border-gray-600 text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200"
113
+ placeholder ="Search by name" bind:value ={search .name }>
114
+ </div >
115
+ </div >
116
+ <div >
117
+ <label for =" search_email" class =" block text-gray-300 text-sm font-medium mb-2" >Email</label >
118
+ <div class =" relative" >
119
+ <div class =" absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none" >
120
+ <i class =" fas fa-envelope text-gray-500" ></i >
121
+ </div >
122
+ <input type =" text" id =" search_email" name =" search_email"
123
+ class =" w-full pl-10 pr-3 py-3 bg-gray-700 bg-opacity-50 border border-gray-600 text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200"
124
+ placeholder ="Search by email" bind:value ={search .email }>
125
+ </div >
126
+ </div >
127
+ <div >
128
+ <label for =" search_phone" class =" block text-gray-300 text-sm font-medium mb-2" >Phone</label >
129
+ <div class =" relative" >
130
+ <div class =" absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none" >
131
+ <i class =" fas fa-phone text-gray-500" ></i >
132
+ </div >
133
+ <input type =" text" id =" search_phone" name =" search_phone"
134
+ class =" w-full pl-10 pr-3 py-3 bg-gray-700 bg-opacity-50 border border-gray-600 text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200"
135
+ placeholder ="Search by phone" bind:value ={search .phone }>
136
+ </div >
137
+ </div >
138
+ </div >
139
+ <div class =" mt-5 text-right" >
140
+ <button type =" submit"
141
+ class =" px-5 py-3 bg-gradient text-white rounded-lg hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-800 transition-all duration-200 font-medium shadow-lg transform hover:-translate-y-0.5" >
142
+ <i class =" fas fa-search mr-2" ></i > Search
143
+ </button >
144
+ </div >
145
+ </form >
146
+ </div >
147
+ </div >
148
+
149
+ <!-- Contact cards grid -->
150
+ <div class =" grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" >
151
+ <!-- Create New Contact Card -->
152
+ <div class =" bg-gray-800 bg-opacity-80 rounded-xl shadow-custom overflow-hidden border-2 border-dashed border-gray-700 card-hover animate-fade-in" >
153
+ <a href =" /dashboard/contacts/create" class =" block p-6 h-full" >
154
+ <div class =" flex flex-col items-center justify-center h-full text-center" >
155
+ <div class =" w-20 h-20 bg-gradient rounded-full flex items-center justify-center mb-5 shadow-lg transform transition-transform duration-300 hover:scale-110" >
156
+ <i class =" fas fa-user-plus text-3xl text-white" ></i >
157
+ </div >
158
+ <h2 class =" text-xl font-semibold text-white mb-3" >Create New Contact</h2 >
159
+ <p class =" text-gray-300" >Add a new contact to your list</p >
160
+ </div >
161
+ </a >
162
+ </div >
163
+
164
+ {#each contacts as contact (contact .id )}
165
+ <div class =" bg-gray-800 bg-opacity-80 rounded-xl shadow-custom border border-gray-700 overflow-hidden card-hover animate-fade-in" >
166
+ <div class =" p-6" >
167
+ <a href ="/dashboard/contacts/ {contact .id }"
168
+ class =" block cursor-pointer hover:bg-gray-700 rounded-lg transition-all duration-200 p-3" >
169
+ <div class =" flex items-center mb-3" >
170
+ <div class =" w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center mr-3 shadow-md" >
171
+ <i class =" fas fa-user text-white" ></i >
172
+ </div >
173
+ <h2 class =" text-xl font-semibold text-white hover:text-blue-300 transition-colors duration-200" >
174
+ {contact .first_name } {contact .last_name }
175
+ </h2 >
176
+ </div >
177
+ <div class =" space-y-3 text-gray-300 ml-2" >
178
+ <p class =" flex items-center" >
179
+ <i class =" fas fa-user-tag text-gray-500 w-6" ></i >
180
+ <span class =" font-medium w-24" >First Name:</span >
181
+ <span >{contact .first_name }</span >
182
+ </p >
183
+ <p class =" flex items-center" >
184
+ <i class =" fas fa-user-tag text-gray-500 w-6" ></i >
185
+ <span class =" font-medium w-24" >Last Name:</span >
186
+ <span >{contact .last_name }</span >
187
+ </p >
188
+ <p class =" flex items-center" >
189
+ <i class =" fas fa-envelope text-gray-500 w-6" ></i >
190
+ <span class =" font-medium w-24" >Email:</span >
191
+ <span >{contact .email }</span >
192
+ </p >
193
+ <p class =" flex items-center" >
194
+ <i class =" fas fa-phone text-gray-500 w-6" ></i >
195
+ <span class =" font-medium w-24" >Phone:</span >
196
+ <span >{contact .phone }</span >
197
+ </p >
198
+ </div >
199
+ </a >
200
+ <div class =" mt-4 flex justify-end space-x-3" >
201
+ <a href ="/dashboard/contacts/ {contact .id }/edit"
202
+ class =" px-4 py-2 bg-gradient text-white rounded-lg hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-800 transition-all duration-200 font-medium shadow-md flex items-center" >
203
+ <i class =" fas fa-edit mr-2" ></i > Edit
204
+ </a >
205
+ <button class =" px-4 py-2 bg-gradient-to-r from-red-600 to-red-500 text-white rounded-lg hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 focus:ring-offset-gray-800 transition-all duration-200 font-medium shadow-md flex items-center" >
206
+ <i class =" fas fa-trash-alt mr-2" ></i > Delete
207
+ </button >
208
+ </div >
209
+ </div >
210
+ </div >
211
+ {/each }
212
+
213
+ </div >
214
+
215
+ <!-- Pagination -->
216
+ <div class =" mt-10 flex justify-center" >
217
+ <nav class =" flex items-center space-x-3 bg-gray-800 bg-opacity-80 rounded-xl shadow-custom border border-gray-700 p-3 animate-fade-in" >
218
+ {#if search .page > 1 }
219
+ <a href ="#" onclick ={() => handlePageChange (search .page - 1 )}
220
+ class =" px-4 py-2 bg-gray-700 text-gray-300 rounded-lg hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-800 transition-all duration-200 flex items-center" >
221
+ <i class =" fas fa-chevron-left mr-2" ></i > Previous
222
+ </a >
223
+ {/if }
224
+ {#each pages as page (page )}
225
+ {#if page === search .page }
226
+ <a href ="#" onclick ={() => handlePageChange (page )}
227
+ class =" px-4 py-2 bg-gradient text-white rounded-lg hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-800 transition-all duration-200 font-medium shadow-md" >
228
+ {page }
229
+ </a >
230
+ {:else }
231
+ <a href ="#" onclick ={() => handlePageChange (page )}
232
+ class =" px-4 py-2 bg-gray-700 text-gray-300 rounded-lg hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-800 transition-all duration-200" >
233
+ {page }
234
+ </a >
235
+ {/if }
236
+ {/each }
237
+ {#if search .page < totalPage }
238
+ <a href ="#" onclick ={() => handlePageChange (search .page + 1 )}
239
+ class =" px-4 py-2 bg-gray-700 text-gray-300 rounded-lg hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-800 transition-all duration-200 flex items-center" >
240
+ Next <i class =" fas fa-chevron-right ml-2" ></i >
241
+ </a >
242
+ {/if }
243
+ </nav >
244
+ </div >
0 commit comments