Merge branch 'development'

This commit is contained in:
Methapon2001 2023-12-15 10:10:00 +07:00
commit 51120ba49b
No known key found for this signature in database
GPG key ID: 849924FEF46BD132
10 changed files with 383 additions and 10578 deletions

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,10 @@ import { storeToRefs } from 'pinia'
import { useSearchDataStore } from '@/stores/searched-data'
import { useFileInfoStore } from '@/stores/file-info-data'
import useStorage from '@/stores/storage'
import type { StorageFile } from '@/stores/storage'
import DialogDelete from './DialogDelete.vue'
import FileFormWrapper from './FileFormWrapper.vue'
import DialogDelete from '@/components/DialogDelete.vue'
import FileFormWrapper from '@/components/FileFormWrapper.vue'
import FileItemAction from '@/components/FileItemAction.vue'
import FileIcon from '@/components/FileIcon.vue'
@ -33,7 +34,7 @@ const keywordList = ref<string[]>([])
const categoryList = ref<string[]>([])
const selectKeyword = ref<string[]>([])
const selectCategory = ref<string[]>([])
const filterFoundFile = ref<any>()
const filterFoundFile = ref<StorageFile[]>()
const deleteFormType = ref<'deleteFile'>('deleteFile')
const dialogDeleteState = ref<boolean>(false)
@ -161,6 +162,7 @@ onMounted(() => {
</div>
<div v-if="props.viewMode === 'view_list' && foundFile.length > 0">
<span class="text-body text-grey">จำนวน {{ filterFoundFile?.length }} รายการ</span>
<div class="grid q-mt-md">
<div v-for="(value, index) in filterFoundFile" :key="value.title">
<div
@ -201,6 +203,7 @@ onMounted(() => {
category: value.category,
},
value.pathname,
value.fileName,
)
"
@delete="() => triggerFileDelete(value.pathname)"
@ -224,13 +227,13 @@ onMounted(() => {
<q-table
flat
bordered
row-key="name"
class="cursor"
:rows="filterFoundFile"
:columns="columns"
row-key="name"
:pagination="{
rowsPerPage: 0,
}"
class="cursor"
>
<template v-slot:body-cell-name="nameData">
<q-td style="width: 50%" @click="() => getFileInfo(nameData.row)">
@ -271,11 +274,12 @@ onMounted(() => {
color="positive"
dense
icon="o_edit"
@click="
@click.stop="
() =>
fileFormComponent?.triggerFileEdit(
actionData.row,
actionData.row.pathname,
actionData.row.fileName,
)
"
id="listViewFileEdit"

View file

@ -355,6 +355,7 @@ const onRowClick = ((_, row) => {
fileFormComponent?.triggerFileEdit(
data.row,
data.row.pathname,
data.row.filename,
)
"
id="listViewFileEdit"
@ -366,6 +367,7 @@ const onRowClick = ((_, row) => {
color="negative"
icon="mdi-trash-can-outline"
@click.stop="() => triggerFileDelete(data.row.pathname)"
data-testid="listViewFileDelete"
/>
</div>
</q-td>

View file

@ -2,14 +2,38 @@ import type { ObjectDirective } from 'vue'
export const clickOutside = {
beforeMount(element, binding) {
element.setDragging = () => {
!element.hasAttribute('data-dragging') &&
element.setAttribute('data-dragging', true)
}
element.clearDragging = () => {
element.removeAttribute('data-dragging')
}
element.clickOutsideEvent = (e: MouseEvent) => {
if (!(element === e.target || element.contains(e.target))) {
if (
!(
element === e.target ||
element.contains(e.target) ||
element.hasAttribute('data-dragging')
)
) {
binding.value(e)
}
}
document.addEventListener('click', element.clickOutsideEvent)
document.addEventListener('touchstart', element.clearDragging)
document.addEventListener('touchmove', element.setDragging)
document.addEventListener('touchend', element.clickOutsideEvent)
document.addEventListener('mousedown', element.clearDragging)
document.addEventListener('mousemove', element.setDragging)
document.addEventListener('mouseup', element.clickOutsideEvent)
},
unmounted(element) {
document.removeEventListener('click', element.clickOutsideEvent)
document.removeEventListener('touchstart', element.clearDragging)
document.removeEventListener('touchmove', element.setDragging)
document.removeEventListener('touchend', element.clickOutsideEvent)
document.removeEventListener('mousedown', element.clearDragging)
document.removeEventListener('mousemove', element.setDragging)
document.removeEventListener('mouseup', element.clickOutsideEvent)
},
} satisfies ObjectDirective

View file

@ -1,4 +1,3 @@
divdivdivdivdivdiv
<script setup lang="ts">
import { storeToRefs } from 'pinia'
@ -22,10 +21,12 @@ const props = defineProps<{
AND: {
field: string
value: string
exact: boolean
}[]
OR: {
field: string
value: string
exact: boolean
}[]
}
}>()
@ -35,6 +36,7 @@ function addAdvSearchData() {
op: 'AND',
field: 'title',
value: '',
exact: false,
})
}
function delAdvSearchData(index: number) {
@ -47,6 +49,7 @@ function clearAdvSearchData() {
op: 'AND',
field: 'title',
value: '',
exact: false,
},
]
advSearchDataField.value = {
@ -71,7 +74,7 @@ function clearAdvSearchData() {
</div>
<div v-if="isAdvSearchCall === true">
<div class="column bg-white q-pa-sm">
<div class="col bg-white q-pa-sm">
<div class="row items-center justify-between q-pb-md">
<span class="text-primary text-weight-medium q-pl-md q-pt-xs"
><q-icon name="mdi-tools" class="q-pr-sm" size="sm" />
@ -87,112 +90,123 @@ function clearAdvSearchData() {
/>
</div>
<div class="column q-px-lg">
<div
class="row q-px-lg items-center justify-start q-pb-md q-col-gutter-md"
v-for="(item, index) in advSearchDataRow"
:key="index"
>
<div
class="row items-center q-pb-xs q-col-gutter-md"
v-for="(item, index) in advSearchDataRow"
:key="index"
class="col-md-1 col-1 row content-center"
style="width: 45px; height: 45px"
>
<div class="row content-center" style="width: 45px; height: 45px">
<q-btn
dense
color="teal-5"
icon="mdi-plus"
v-if="index === advSearchDataRow.length - 1"
@click="addAdvSearchData"
id="addAdvSearchData"
/>
</div>
<div class="col-4 col-md-2">
<q-select
id="advSearchOp"
dense
outlined
emit-value
map-options
v-model="item.op"
:options="optionsOp"
/>
</div>
<div class="col-grow col-md-3">
<q-select
id="advSearchField"
dense
outlined
emit-value
map-options
v-model="item.field"
:options="optionsField"
/>
</div>
<div class="col-grow">
<q-input
id="advSearchValue"
dense
outlined
v-model="item.value"
placeholder="เอกสาร"
@keydown.enter.prevent="submitSearch()"
><template v-slot:append>
<q-icon
v-if="item.value"
name="close"
@click="() => (item.value = '')"
class="cursor-pointer"
/>
</template>
</q-input>
</div>
<div class="row content-center" style="width: 45px; height: 45px">
<q-btn
dense
flat
icon="mdi-trash-can-outline"
v-if="advSearchDataRow.length > 1"
color="red"
@click="() => delAdvSearchData(index)"
id="delAdvSearchData"
/>
</div>
<q-btn
dense
color="teal-5"
icon="mdi-plus"
v-if="index === advSearchDataRow.length - 1"
@click="addAdvSearchData"
id="addAdvSearchData"
/>
</div>
<div class="col-4 col-md-2">
<q-select
id="advSearchOp"
dense
outlined
emit-value
map-options
v-model="item.op"
:options="optionsOp"
/>
</div>
<div class="col-grow col-md-3">
<q-select
id="advSearchField"
dense
outlined
emit-value
map-options
v-model="item.field"
:options="optionsField"
/>
</div>
<div class="col-md-grow col-grow">
<q-input
id="advSearchValue"
dense
outlined
v-model="item.value"
placeholder="เอกสาร"
@keydown.enter.prevent="submitSearch()"
><template v-slot:append>
<q-icon
v-if="item.value"
name="close"
@click="() => (item.value = '')"
class="cursor-pointer"
/>
</template>
</q-input>
</div>
<div class="col-md-1 q-mr-xl">
<q-checkbox
id="specificBox"
style="width: 200%"
v-model="item.exact"
label="ค้นหาตรงตัว"
color="grey"
keep-color
/>
</div>
<div class="row content-center">
<q-btn
dense
flat
icon="mdi-trash-can-outline"
v-if="advSearchDataRow.length > 1"
color="red"
@click="() => delAdvSearchData(index)"
id="delAdvSearchData"
/>
</div>
</div>
<q-separator class="q-mb-md q-mt-sm" />
<q-separator class="q-mb-md q-mt-sm" />
<div class="row q-col-gutter-md q-pb-md">
<div class="col-12 col-md-5">
<q-select
outlined
dense
v-model="advSearchDataField.keyword"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add-unique"
><template v-slot:prepend
><span class="text-subtitle2">คำสำค:</span></template
></q-select
>
</div>
<div class="col-12 col-md-grow">
<q-input
id="advSearchDes"
dense
outlined
@keydown.enter.prevent="submitSearch()"
v-model="advSearchDataField.description"
><template v-slot:prepend
><span class="text-subtitle2">รายละเอยด:</span></template
><template v-slot:append>
<q-icon
v-if="advSearchDataField.description"
name="close"
@click="() => (advSearchDataField.description = '')"
class="cursor-pointer"
/> </template
></q-input>
</div>
<div class="row q-col-gutter-md q-pb-md q-pt-sm">
<div class="col-12 col-md-5">
<q-select
outlined
dense
v-model="advSearchDataField.keyword"
use-input
use-chips
multiple
hide-dropdown-icon
input-debounce="0"
new-value-mode="add-unique"
><template v-slot:prepend
><span class="text-subtitle2">คำสำค:</span></template
></q-select
>
</div>
<div class="col-12 col-md-grow">
<q-input
id="advSearchDes"
dense
outlined
@keydown.enter.prevent="submitSearch()"
v-model="advSearchDataField.description"
><template v-slot:prepend
><span class="text-subtitle2">รายละเอยด:</span></template
><template v-slot:append>
<q-icon
v-if="advSearchDataField.description"
name="close"
@click="() => (advSearchDataField.description = '')"
class="cursor-pointer"
/> </template
></q-input>
</div>
</div>
</div>

View file

@ -16,6 +16,7 @@ const loaderStore = useLoader()
const { isFilePreview } = storeToRefs(useFileInfoStore())
const {
foundFile,
isExact,
isSearch,
isAdvSearchCall,
isActFoundFile,
@ -31,8 +32,8 @@ const optionsField = [
{ label: 'เนื้อหาในไฟล์ (content)', value: 'attachment.content' },
]
const submitSearchData = ref<{
AND: { field: string; value: string }[]
OR: { field: string; value: string }[]
AND: { field: string; value: string; exact: boolean }[]
OR: { field: string; value: string; exact: boolean }[]
}>({
AND: [],
OR: [],
@ -40,7 +41,6 @@ const submitSearchData = ref<{
const props = defineProps<{
mode: 'admin' | 'user'
}>()
const socket = io(import.meta.env.VITE_API_HOST)
socket.on('FileUpdate', (data: StorageFile) =>
@ -68,36 +68,47 @@ async function submitSearch() {
submitSearchData.value.OR.push({
field: option.value,
value: searchData.value.value,
exact: true,
})
})
submitSearchData.value.OR.push({
field: 'fileName',
value: searchData.value.value,
exact: true,
})
submitSearchData.value.OR.push({
field: 'fileType',
value: mime.getType(searchData.value.value) || '',
exact: true,
})
} else {
submitSearchData.value.OR.push({
field: searchData.value.field,
value: searchData.value.value,
exact: isExact.value,
})
if (isAdvSearchCall.value) {
let advField = advSearchDataField.value
let advRow = advSearchDataRow.value
advRow.forEach((d: { field: string; value: string; op: string }) => {
if (d.field && d.value.trim() !== '') {
const op = d.op === 'AND' ? 'AND' : 'OR'
submitSearchData.value[op].push({ field: d.field, value: d.value })
}
})
advRow.forEach(
(d: { field: string; value: string; op: string; exact: boolean }) => {
if (d.field && d.value.trim() !== '') {
const op = d.op === 'AND' ? 'AND' : 'OR'
submitSearchData.value[op].push({
field: d.field,
value: d.value,
exact: d.exact,
})
}
},
)
if (advField.keyword.length > 0) {
for (let i = 0; i < advField.keyword.length; i++) {
submitSearchData.value.AND.push({
field: 'keyword',
value: advField.keyword[i],
exact: true,
})
}
}
@ -105,6 +116,7 @@ async function submitSearch() {
submitSearchData.value.AND.push({
field: 'description',
value: advField.description,
exact: true,
})
}
}
@ -116,6 +128,7 @@ async function submitSearch() {
`${import.meta.env.VITE_API_ENDPOINT}/search`,
submitSearchData.value,
)
getFoundFile(res.data)
isSearch.value = true
} catch (error) {
@ -133,7 +146,7 @@ watch(
submitSearch()
setTimeout(() => {
isActFoundFile.value = false
}, 300)
}, 1000)
}
},
)
@ -206,11 +219,20 @@ watch(
</template>
</q-input>
</div>
<div>
<q-checkbox
id="specificBox"
v-model="isExact"
label="ค้นหาตรงตัว"
color="grey"
keep-color
/>
</div>
</div>
<div class="column">
<div class="col">
<div class="row items-center justify-between q-gutter-y-md q-pt-sm">
<div class="column col-grow">
<div class="col-grow">
<advanced-search
:submitSearch="submitSearch"
:submit-search-data="submitSearchData"
@ -221,6 +243,7 @@ watch(
style="width: 150px"
color="primary"
label="ค้นหา"
class="q-mt-sm"
icon="mdi-magnify"
@click="submitSearch"
id="submitSearch"

View file

@ -11,6 +11,7 @@ export interface AdvancedSearch {
op: 'AND' | 'OR'
field: 'title' | 'keyword'
value: string
exact: boolean
}
export interface AdvancedSearchFields {
@ -22,6 +23,7 @@ export const useSearchDataStore = defineStore('searched', () => {
const foundFile = ref<StorageFile[]>([])
const isAdvSearchCall = ref<boolean>(false)
const isSearch = ref<Boolean>(false)
const isExact = ref<boolean>(false)
const isActFoundFile = ref<Boolean>(false)
const searchData = ref<Search>({
field: 'title',
@ -32,6 +34,7 @@ export const useSearchDataStore = defineStore('searched', () => {
op: 'AND',
field: 'title',
value: '',
exact: false,
},
])
const advSearchDataField = ref<AdvancedSearchFields>({
@ -46,6 +49,7 @@ export const useSearchDataStore = defineStore('searched', () => {
return {
foundFile,
isSearch,
isExact,
isAdvSearchCall,
isActFoundFile,
searchData,

View file

@ -1,5 +1,5 @@
import { test, expect, Page } from '@playwright/test'
import { nanoid } from 'nanoid'
test.describe.configure({ mode: 'serial' })
let page: Page
@ -21,23 +21,26 @@ test('Login', async ({}) => {
})
test('listViewMode', async () => {
await page.click("(//i[@role='img'])[2]")
await page.click("//i[text()='view_list']")
})
test('Create Cabinet', async () => {
await page.click("//span[text()='สร้างตู้เก็บเอกสาร']")
await page.fill("(//input[@placeholder='กรอกชื่อ'])[2]", 'oat-test')
await page.click("(//span[text()='บันทึก'])[3]")
await page.fill("//input[@placeholder='กรอกชื่อ']", 'oat-test')
await page.click("//button[@type='submit']")
})
test('Go into Cabinet', async () => {
await page.click('//td[contains(text(),"oat-test")]')
})
var drawername = nanoid(7)
test('Create Drawer', async () => {
await page.click("//span[text()='สร้างลิ้นชัก']")
await page.fill("(//input[@placeholder='กรอกชื่อ'])[1]", 'oat-drawer2')
await page.click("(//button[@type='submit'])[3]")
await page.fill("(//input[@class='q-field__native q-placeholder'])[2]", drawername)
await page.click("(//button[@type='submit'])")
})
test('Go into Drawer', async () => {
@ -46,18 +49,18 @@ test('Go into Drawer', async () => {
test('Create Folder', async () => {
await page.click("//span[text()='สร้างแฟ้ม']")
await page.fill("(//input[@placeholder='กรอกชื่อ'])[1]", 'oat-folder2')
await page.click("(//span[text()='บันทึก'])[2]")
await page.fill("(//input[@class='q-field__native q-placeholder'])[2]", 'oat-folder')
await page.click("(//button[@type='submit'])")
})
test('Go into Folder', async () => {
await page.click("//table[@class='q-table']")
await page.click("//table[@class='q-table']/tbody[1]/tr[1]/td[1]")
})
test('Create subFolder', async () => {
await page.click("//span[text()='สร้างแฟ้มย่อย']")
await page.fill("(//input[@placeholder='กรอกชื่อ'])[1]","oat-subfolder2")
await page.click("(//span[text()='บันทึก'])[2]")
await page.fill("(//input[@class='q-field__native q-placeholder'])[2]","oat-subfolder")
await page.click("//span[text()='บันทึก']")
})
test('Go into SubFolder', async () => {
@ -67,15 +70,15 @@ test('Go into SubFolder', async () => {
test('Upload File', async () => {
await page.click("//span[text()='สร้างเอกสาร']")
await page.locator("//input[@type='file']").setInputFiles('tests/test.docx')
await page.fill("//input[@placeholder='กรอกชื่อเรื่อง']",'oattest2')
await page.click("(//button[@type='submit'])[2]")
await page.fill("//input[@placeholder='กรอกชื่อเรื่อง']",'oattestfile')
await page.click("(//button[@type='submit'])[1]")
await page.waitForTimeout(3000)
await page.click("//i[text()='refresh']")
await page.click("button#getFolder")
await page.waitForTimeout(1000)
})
test('Go into File', async () => {
await page.click("//table[@class='q-table']/tbody[1]/tr[1]/td[1]")
await page.click('//td[contains(text(),"test.docx")]')
})
test('Download File', async () => {

View file

@ -0,0 +1,168 @@
import { test, expect, Page } from '@playwright/test'
import { nanoid } from 'nanoid'
import { Directive } from 'vue'
test.describe.configure({ mode: 'serial' })
let page: Page
test.beforeAll(async ({ browser }) => {
page = await browser.newPage()
})
test.afterAll(async () => {
await page.close()
})
test('Login', async ({}) => {
await page.goto('http://localhost:3010/admin')
await expect(page).toHaveTitle('Sign in to EDM')
await page.fill("input[name='username']", 'admin')
await page.fill("input[name='password']", 'P@ssw0rd')
await page.click("input[name='login']")
})
test('listViewMode', async () => {
await page.click("//i[text()='view_list']")
})
test('Create Cabinet', async () => {
await page.click("//span[text()='สร้างตู้เก็บเอกสาร']")
await page.fill("//input[@placeholder='กรอกชื่อ']", 'oat-test')
await page.click("//button[@type='submit']")
})
test('Go into Cabinet', async () => {
await page.click('//td[contains(text(),"oat-test")]')
})
test('Create Drawer', async () => {
await page.click("//span[text()='สร้างลิ้นชัก']")
await page.fill("(//input[@class='q-field__native q-placeholder'])[2]", 'oat-drawer')
await page.click("(//button[@type='submit'])")
})
test('Go into Drawer', async () => {
await page.click("//table[@class='q-table']/tbody[1]/tr[1]/td[1]")
})
test('Create Folder', async () => {
await page.click("//span[text()='สร้างแฟ้ม']")
await page.fill("(//input[@class='q-field__native q-placeholder'])[2]", 'oat-folder')
await page.click("(//button[@type='submit'])")
})
test('Go into Folder', async () => {
await page.click("//table[@class='q-table']/tbody[1]/tr[1]/td[1]")
})
test('Create subFolder', async () => {
await page.click("//span[text()='สร้างแฟ้มย่อย']")
await page.fill("(//input[@class='q-field__native q-placeholder'])[2]","oat-subfolder")
await page.click("//span[text()='บันทึก']")
})
test('Go into SubFolder', async () => {
await page.click("//table[@class='q-table']")
})
test('Upload File in SubFolder Level', async () => {
await page.click("//span[text()='สร้างเอกสาร']")
await page.locator("//input[@type='file']").setInputFiles('tests/test.docx')
await page.fill("//input[@placeholder='กรอกชื่อเรื่อง']",'oattestfile')
await page.click("(//button[@type='submit'])[1]")
await page.waitForTimeout(2500)
await page.click("button#getFolder")
await page.waitForTimeout(1000)
})
test('Go into File', async () => {
await page.click('//td[contains(text(),"test.docx")]')
})
test('Download File', async () => {
await page.click("//span[text()='ดาวน์โหลด']")
await page.waitForTimeout(2000)
})
test('Go Back to SubFolder', async () => {
await page.click("//i[text()='arrow_back']")
})
test('Delele file on SubFolder', async () => {
await page.click("//button[@data-testid='listViewFileDelete']")
await page.click("//span[text()='ลบ']")
})
test('Go back to Folder Level', async () => {
await page.click("//i[text()='arrow_back']")
})
test('Delete SubFolder', async () => {
await page.click("//button[@data-testid='oat-subfolder']")
await page.click("//span[text()='ลบ']")
})
test('Upload File in Folder Level', async () => {
await page.click("//span[text()='สร้างเอกสาร']")
await page.locator("//input[@type='file']").setInputFiles('tests/searchtest.txt')
await page.fill("//input[@placeholder='กรอกชื่อเรื่อง']",'testtext')
await page.click("(//button[@type='submit'])[1]")
await page.waitForTimeout(2500)
await page.click("button#getFolder")
await page.waitForTimeout(1000)
})
test('Go into Text File', async () => {
await page.click('//td[contains(text(),"searchtest.txt")]')
})
test('Download File Text', async () => {
await page.click("//span[text()='ดาวน์โหลด']")
await page.waitForTimeout(2000)
})
test('Go back to Folder Level Again', async () => {
await page.click("//i[text()='arrow_back']")
})
test('Delele file on Folder', async () => {
await page.click("//button[@data-testid='listViewFileDelete']")
await page.click("//span[text()='ลบ']")
})
test('Go back to Drawer Level', async () => {
await page.click("//i[text()='arrow_back']")
})
test('Delete Folder', async () => {
await page.click("//button[@data-testid='oat-folder']")
await page.click("//span[text()='ลบ']")
})
test('Go back to Cabinet Level', async () => {
await page.click("//i[text()='arrow_back']")
})
test('Delete Drawer', async () => {
await page.click("//button[@data-testid='oat-drawer']")
await page.click("//span[text()='ลบ']")
})
test('Go back to Main Level', async () => {
await page.click("//i[text()='arrow_back']")
})
test('Delete Cabinet', async () => {
await page.click("//button[@data-testid='oat-test']")
await page.click("//span[text()='ลบ']")
await page.waitForTimeout(1000)
})
// test('Delete Cabinet', async () => {
// await page.click("//button[@data-testid='oat-test']")
// await page.click("//span[text()='ลบ']")
// await page.waitForTimeout(2000)
// })

View file

@ -0,0 +1,2 @@
หาเจอป่าว รายรับ รายจ่าย สวัสดี กินข้าว หิว อิ่ม ราชการ นอน ตื่น กระโดด วิ่ง โทรศัพท์ คอมพิวเตอร์ เอกสาร แฟ้ม โฟลเดอร์ ประตู ตู้เก็บของ ตู้เสื้อผ้า
ปลั้กไฟ ตู้เย็น ปากกา ดินสอ ยางลบ กบเหลา