diff --git a/frontend_management/.env.example b/frontend_management/.env.example
index d5e0ccc9..dbb3e3f7 100644
--- a/frontend_management/.env.example
+++ b/frontend_management/.env.example
@@ -1,5 +1,3 @@
# API Configuration
-API_BASE_URL=http://localhost:3001/api
+NUXT_PUBLIC_API_BASE_URL=http://localhost:3001/api
-# Application
-NODE_ENV=development
diff --git a/frontend_management/.gitignore b/frontend_management/.gitignore
index c05a50a2..df2ba243 100644
--- a/frontend_management/.gitignore
+++ b/frontend_management/.gitignore
@@ -23,4 +23,13 @@ logs
.env.*
!.env.example
deploy.ps1
-*.tar
\ No newline at end of file
+*.tar
+
+# Playwright
+tests
+tests/.auth/
+/test-results/
+/playwright-report/
+/blob-report/
+/playwright/.cache/
+playwright.config.ts
\ No newline at end of file
diff --git a/frontend_management/components/course/AnnouncementsTab.vue b/frontend_management/components/course/AnnouncementsTab.vue
index 84b49a14..1efe7f2b 100644
--- a/frontend_management/components/course/AnnouncementsTab.vue
+++ b/frontend_management/components/course/AnnouncementsTab.vue
@@ -342,11 +342,18 @@ const save = async () => {
saving.value = true;
try {
+ // Convert local datetime to ISO string to preserve timezone
+ const payload = { ...form.value };
+ if (payload.published_at) {
+ const localDate = new Date(payload.published_at.replace(' ', 'T'));
+ payload.published_at = localDate.toISOString();
+ }
+
if (editing.value) {
- await instructorService.updateAnnouncement(props.courseId, editing.value.id, form.value);
+ await instructorService.updateAnnouncement(props.courseId, editing.value.id, payload);
$q.notify({ type: 'positive', message: 'บันทึกประกาศสำเร็จ', position: 'top' });
} else {
- const created = await instructorService.createAnnouncement(props.courseId, form.value);
+ const created = await instructorService.createAnnouncement(props.courseId, payload);
// Upload pending files
for (const file of pendingFiles.value) {
diff --git a/frontend_management/components/course/InstructorsTab.vue b/frontend_management/components/course/InstructorsTab.vue
index 323a5a72..4f47af73 100644
--- a/frontend_management/components/course/InstructorsTab.vue
+++ b/frontend_management/components/course/InstructorsTab.vue
@@ -32,7 +32,7 @@
{{ instructor.user.email }}
-
+
@@ -139,6 +139,10 @@ const isPrimaryInstructor = computed(() => {
return myRecord?.is_primary === true;
});
+const currentUserId = computed(() => {
+ return authStore.user?.id ? parseInt(authStore.user.id) : null;
+});
+
// Methods
const fetchInstructors = async () => {
loading.value = true;
diff --git a/frontend_management/package-lock.json b/frontend_management/package-lock.json
index 665d28ad..e113ea0c 100644
--- a/frontend_management/package-lock.json
+++ b/frontend_management/package-lock.json
@@ -18,6 +18,7 @@
},
"devDependencies": {
"@nuxtjs/tailwindcss": "^6.14.0",
+ "@playwright/test": "^1.58.2",
"@types/node": "^25.0.3",
"nuxt-quasar-ui": "^3.0.0"
}
@@ -2773,6 +2774,22 @@
"node": ">=14"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.58.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
+ "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.58.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@polka/url": {
"version": "1.0.0-next.29",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
@@ -8783,6 +8800,53 @@
"pathe": "^2.0.3"
}
},
+ "node_modules/playwright": {
+ "version": "1.58.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
+ "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.58.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.58.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
+ "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/portfinder": {
"version": "1.0.38",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.38.tgz",
diff --git a/frontend_management/package.json b/frontend_management/package.json
index 0cd042dd..9ca64017 100644
--- a/frontend_management/package.json
+++ b/frontend_management/package.json
@@ -7,7 +7,11 @@
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
- "postinstall": "nuxt prepare"
+ "postinstall": "nuxt prepare",
+ "test": "playwright test",
+ "test:ui": "playwright test --ui",
+ "test:headed": "playwright test --headed",
+ "test:report": "playwright show-report"
},
"dependencies": {
"@pinia/nuxt": "^0.11.3",
@@ -21,7 +25,8 @@
},
"devDependencies": {
"@nuxtjs/tailwindcss": "^6.14.0",
+ "@playwright/test": "^1.58.2",
"@types/node": "^25.0.3",
"nuxt-quasar-ui": "^3.0.0"
}
-}
+}
\ No newline at end of file
diff --git a/frontend_management/pages/admin/index.vue b/frontend_management/pages/admin/index.vue
index cf1121ed..e4e6b968 100644
--- a/frontend_management/pages/admin/index.vue
+++ b/frontend_management/pages/admin/index.vue
@@ -182,6 +182,7 @@