ProfileManagement.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. <template>
  2. <div class="profile-management">
  3. <div class="d-flex justify-content-between align-items-center mb-4">
  4. <h2>Profiilin hallinta</h2>
  5. </div>
  6. <div class="row">
  7. <div class="col-md-8">
  8. <!-- Profile Form -->
  9. <div class="card">
  10. <div class="card-header">
  11. <h5 class="mb-0">Perustiedot</h5>
  12. </div>
  13. <div class="card-body">
  14. <form @submit.prevent="saveProfile">
  15. <div class="row">
  16. <div class="col-md-6 mb-3">
  17. <label for="username" class="form-label">Käyttäjänimi</label>
  18. <input
  19. type="text"
  20. class="form-control"
  21. id="username"
  22. v-model="formData.username"
  23. required
  24. disabled
  25. >
  26. <small class="form-text text-muted">Käyttäjänimeä ei voi muuttaa</small>
  27. </div>
  28. <div class="col-md-6 mb-3">
  29. <label for="email" class="form-label">Sähköposti</label>
  30. <input
  31. type="email"
  32. class="form-control"
  33. id="email"
  34. v-model="formData.email"
  35. required
  36. >
  37. </div>
  38. </div>
  39. <div class="row">
  40. <div class="col-md-6 mb-3">
  41. <label for="firstName" class="form-label">Etunimi</label>
  42. <input
  43. type="text"
  44. class="form-control"
  45. id="firstName"
  46. v-model="formData.first_name"
  47. required
  48. >
  49. </div>
  50. <div class="col-md-6 mb-3">
  51. <label for="lastName" class="form-label">Sukunimi</label>
  52. <input
  53. type="text"
  54. class="form-control"
  55. id="lastName"
  56. v-model="formData.last_name"
  57. required
  58. >
  59. </div>
  60. </div>
  61. <div class="mb-3">
  62. <label for="role" class="form-label">Rooli</label>
  63. <input
  64. type="text"
  65. class="form-control"
  66. id="role"
  67. :value="getRoleLabel(formData.role)"
  68. disabled
  69. >
  70. <small class="form-text text-muted">Roolin voi muuttaa vain ylläpitäjä</small>
  71. </div>
  72. <div class="d-flex justify-content-between">
  73. <div>
  74. <h6>Tilin tiedot</h6>
  75. <p class="text-muted">
  76. <strong>Luotu:</strong> {{ formatDate(formData.created_at) }}<br>
  77. <strong>Päivitetty:</strong> {{ formatDate(formData.updated_at) }}<br>
  78. <strong>Viimeksi kirjautunut:</strong> {{ formatDate(formData.last_login) }}
  79. </p>
  80. </div>
  81. <div>
  82. <button type="submit" class="btn btn-primary" :disabled="loading">
  83. <span v-if="loading" class="spinner-border spinner-border-sm me-2"></span>
  84. Tallenna muutokset
  85. </button>
  86. </div>
  87. </div>
  88. </form>
  89. </div>
  90. </div>
  91. </div>
  92. <div class="col-md-4">
  93. <!-- Password Change -->
  94. <div class="card">
  95. <div class="card-header">
  96. <h5 class="mb-0">Salasanan vaihto</h5>
  97. </div>
  98. <div class="card-body">
  99. <form @submit.prevent="changePassword">
  100. <div class="mb-3">
  101. <label for="currentPassword" class="form-label">Nykyinen salasana</label>
  102. <input
  103. type="password"
  104. class="form-control"
  105. id="currentPassword"
  106. v-model="passwordForm.current_password"
  107. required
  108. >
  109. </div>
  110. <div class="mb-3">
  111. <label for="newPassword" class="form-label">Uusi salasana</label>
  112. <input
  113. type="password"
  114. class="form-control"
  115. id="newPassword"
  116. v-model="passwordForm.password"
  117. required
  118. minlength="8"
  119. >
  120. <small class="form-text text-muted">Vähintään 8 merkkiä</small>
  121. </div>
  122. <div class="mb-3">
  123. <label for="confirmPassword" class="form-label">Vahvista uusi salasana</label>
  124. <input
  125. type="password"
  126. class="form-control"
  127. id="confirmPassword"
  128. v-model="passwordForm.confirm_password"
  129. required
  130. >
  131. </div>
  132. <button type="submit" class="btn btn-warning w-100" :disabled="passwordLoading">
  133. <span v-if="passwordLoading" class="spinner-border spinner-border-sm me-2"></span>
  134. Vaihda salasana
  135. </button>
  136. </form>
  137. </div>
  138. </div>
  139. <!-- Account Status -->
  140. <div class="card mt-3">
  141. <div class="card-header">
  142. <h5 class="mb-0">Tilin tila</h5>
  143. </div>
  144. <div class="card-body">
  145. <div class="d-flex align-items-center">
  146. <span :class="getStatusBadgeClass(formData.is_active)" class="me-2">
  147. {{ formData.is_active ? 'Aktiivinen' : 'Passiivinen' }}
  148. </span>
  149. <small class="text-muted" v-if="!formData.is_active">
  150. Tili on passiivinen. Ota yhteyttä ylläpitäjään.
  151. </small>
  152. </div>
  153. </div>
  154. </div>
  155. </div>
  156. </div>
  157. </div>
  158. </template>
  159. <script>
  160. import api from '../api/axios'
  161. export default {
  162. name: 'ProfileManagement',
  163. props: {
  164. currentUser: {
  165. type: Object,
  166. default: null
  167. }
  168. },
  169. data() {
  170. return {
  171. loading: false,
  172. passwordLoading: false,
  173. formData: {
  174. id: null,
  175. username: '',
  176. email: '',
  177. first_name: '',
  178. last_name: '',
  179. role: '',
  180. is_active: false,
  181. created_at: null,
  182. updated_at: null,
  183. last_login: null
  184. },
  185. passwordForm: {
  186. current_password: '',
  187. password: '',
  188. confirm_password: ''
  189. }
  190. }
  191. },
  192. async mounted() {
  193. await this.loadProfile()
  194. },
  195. methods: {
  196. async loadProfile() {
  197. this.loading = true
  198. try {
  199. // Use currentUser prop instead of making API call
  200. if (!this.currentUser || !this.currentUser.id) {
  201. this.$emit('error', 'Käyttäjätietoja ei löytynyt')
  202. return
  203. }
  204. // Load full user profile
  205. const profileResponse = await api.get(`/users.php?id=${this.currentUser.id}`)
  206. this.formData = profileResponse.data
  207. } catch (error) {
  208. console.error('Error loading profile:', error)
  209. this.$emit('error', 'Profiilin lataaminen epäonnistui')
  210. } finally {
  211. this.loading = false
  212. }
  213. },
  214. async saveProfile() {
  215. this.loading = true
  216. try {
  217. await api.put('/users', this.formData)
  218. this.$emit('success', 'Profiili päivitetty onnistuneesti')
  219. await this.loadProfile()
  220. } catch (error) {
  221. console.error('Error saving profile:', error)
  222. const message = error.response?.data?.message || 'Profiilin tallentaminen epäonnistui'
  223. this.$emit('error', message)
  224. } finally {
  225. this.loading = false
  226. }
  227. },
  228. async changePassword() {
  229. if (this.passwordForm.password !== this.passwordForm.confirm_password) {
  230. this.$emit('error', 'Salasanat eivät täsmää')
  231. return
  232. }
  233. if (this.passwordForm.password.length < 8) {
  234. this.$emit('error', 'Salasanan tulee olla vähintään 8 merkkiä pitkä')
  235. return
  236. }
  237. this.passwordLoading = true
  238. try {
  239. await api.put('/users', {
  240. ...this.formData,
  241. password: this.passwordForm.password,
  242. current_password: this.passwordForm.current_password
  243. })
  244. this.$emit('success', 'Salasana vaihdettu onnistuneesti')
  245. // Clear password form
  246. this.passwordForm = {
  247. current_password: '',
  248. password: '',
  249. confirm_password: ''
  250. }
  251. } catch (error) {
  252. console.error('Error changing password:', error)
  253. const message = error.response?.data?.message || 'Salasanan vaihtaminen epäonnistui'
  254. this.$emit('error', message)
  255. } finally {
  256. this.passwordLoading = false
  257. }
  258. },
  259. getRoleLabel(role) {
  260. return role === 'admin' ? 'Ylläpitäjä' : 'Käyttäjä'
  261. },
  262. getStatusBadgeClass(isActive) {
  263. return isActive ? 'badge bg-success' : 'badge bg-secondary'
  264. },
  265. formatDate(dateString) {
  266. if (!dateString) return 'Ei koskaan'
  267. return new Date(dateString).toLocaleString('fi-FI')
  268. }
  269. }
  270. }
  271. </script>
  272. <style scoped>
  273. .profile-management {
  274. padding: 20px;
  275. color: #333 !important;
  276. }
  277. .card {
  278. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  279. background-color: #ffffff;
  280. color: #333;
  281. }
  282. .card-header {
  283. background-color: #f8f9fa;
  284. border-bottom: 1px solid #dee2e6;
  285. color: #495057;
  286. }
  287. .card-body {
  288. color: #333;
  289. }
  290. .form-label {
  291. color: #495057;
  292. font-weight: 500;
  293. }
  294. .form-control {
  295. color: #495057;
  296. background-color: #ffffff;
  297. border-color: #ced4da;
  298. }
  299. .form-control:disabled {
  300. background-color: #e9ecef;
  301. color: #6c757d;
  302. }
  303. .form-text {
  304. font-size: 0.875em;
  305. color: #6c757d !important;
  306. }
  307. .btn {
  308. color: #ffffff;
  309. }
  310. .btn-primary {
  311. background-color: #007bff;
  312. border-color: #007bff;
  313. }
  314. .btn-primary:hover {
  315. background-color: #0056b3;
  316. border-color: #004085;
  317. }
  318. .btn-secondary {
  319. background-color: #6c757d;
  320. border-color: #6c757d;
  321. }
  322. .btn-secondary:hover {
  323. background-color: #545b62;
  324. border-color: #545b62;
  325. }
  326. .btn:disabled {
  327. opacity: 0.65;
  328. cursor: not-allowed;
  329. }
  330. h2, h5 {
  331. color: white !important;
  332. }
  333. .text-muted {
  334. color: #6c757d !important;
  335. }
  336. .alert {
  337. color: #333;
  338. }
  339. .alert-success {
  340. background-color: #d4edda;
  341. border-color: #c3e6cb;
  342. color: #155724;
  343. }
  344. .alert-danger {
  345. background-color: #f8d7da;
  346. border-color: #f5c6cb;
  347. color: #721c24;
  348. }
  349. </style>