feature: folder

This commit is contained in:
Khaiyb949 2024-06-20 14:49:46 +07:00
parent 58b4004dcc
commit ad64dfbcd4
13 changed files with 600 additions and 11 deletions

View File

@ -7522,11 +7522,12 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"license": "MIT",
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -9100,10 +9101,11 @@
"dev": true "dev": true
}, },
"node_modules/ejs": { "node_modules/ejs": {
"version": "3.1.9", "version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"dev": true, "dev": true,
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"jake": "^10.8.5" "jake": "^10.8.5"
}, },
@ -10047,9 +10049,10 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"license": "MIT",
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@ -11163,6 +11166,7 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"license": "MIT",
"engines": { "engines": {
"node": ">=0.12.0" "node": ">=0.12.0"
} }
@ -17800,6 +17804,7 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
}, },

94
src-ui/paperless.conf Normal file
View File

@ -0,0 +1,94 @@
# Have a look at the docs for documentation.
# https://docs.paperless-ngx.com/configuration/
# Debug. Only enable this for development.
#PAPERLESS_DEBUG=false
# Required services
PAPERLESS_REDIS=redis://localhost:6379
# PAPERLESS_DBHOST=localhost
#PAPERLESS_DBPORT=5432
#PAPERLESS_DBNAME=paperless
#PAPERLESS_DBUSER=paperless
#PAPERLESS_DBPASS=paperless
#PAPERLESS_DBSSLMODE=prefer
# Paths and folders
#PAPERLESS_CONSUMPTION_DIR=../consume
#PAPERLESS_DATA_DIR=../data
#PAPERLESS_TRASH_DIR=
#PAPERLESS_MEDIA_ROOT=../media
#PAPERLESS_STATICDIR=../static
#PAPERLESS_FILENAME_FORMAT=
#PAPERLESS_FILENAME_FORMAT_REMOVE_NONE=
# Security and hosting
#PAPERLESS_SECRET_KEY=change-me
#PAPERLESS_URL=https://example.com
#PAPERLESS_CSRF_TRUSTED_ORIGINS=https://example.com # can be set using PAPERLESS_URL
#PAPERLESS_ALLOWED_HOSTS=example.com,www.example.com # can be set using PAPERLESS_URL
#PAPERLESS_CORS_ALLOWED_HOSTS=https://localhost:8080,https://example.com # can be set using PAPERLESS_URL
#PAPERLESS_FORCE_SCRIPT_NAME=
#PAPERLESS_STATIC_URL=/static/
#PAPERLESS_AUTO_LOGIN_USERNAME=
#PAPERLESS_COOKIE_PREFIX=
#PAPERLESS_ENABLE_HTTP_REMOTE_USER=false
# OCR settings
#PAPERLESS_OCR_LANGUAGE=eng
#PAPERLESS_OCR_MODE=skip
#PAPERLESS_OCR_SKIP_ARCHIVE_FILE=never
#PAPERLESS_OCR_OUTPUT_TYPE=pdfa
#PAPERLESS_OCR_PAGES=1
#PAPERLESS_OCR_IMAGE_DPI=300
#PAPERLESS_OCR_CLEAN=clean
#PAPERLESS_OCR_DESKEW=true
#PAPERLESS_OCR_ROTATE_PAGES=true
#PAPERLESS_OCR_ROTATE_PAGES_THRESHOLD=12.0
#PAPERLESS_OCR_USER_ARGS={}
#PAPERLESS_CONVERT_MEMORY_LIMIT=0
#PAPERLESS_CONVERT_TMPDIR=/var/tmp/paperless
# Software tweaks
#PAPERLESS_TASK_WORKERS=1
#PAPERLESS_THREADS_PER_WORKER=1
#PAPERLESS_TIME_ZONE=UTC
#PAPERLESS_CONSUMER_POLLING=10
#PAPERLESS_CONSUMER_DELETE_DUPLICATES=false
#PAPERLESS_CONSUMER_RECURSIVE=false
#PAPERLESS_CONSUMER_IGNORE_PATTERNS=[".DS_STORE/*", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini"]
#PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=false
#PAPERLESS_CONSUMER_ENABLE_BARCODES=false
#PAPERLESS_CONSUMER_BARCODE_STRING=PATCHT
#PAPERLESS_CONSUMER_BARCODE_UPSCALE=0.0
#PAPERLESS_CONSUMER_BARCODE_DPI=300
#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE=false
#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING={"TAG:(.*)": "\\g<1>"}
#PAPERLESS_CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED=false
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_SUBDIR_NAME=double-sided
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_TIFF_SUPPORT=false
#PAPERLESS_PRE_CONSUME_SCRIPT=/path/to/an/arbitrary/script.sh
#PAPERLESS_POST_CONSUME_SCRIPT=/path/to/an/arbitrary/script.sh
#PAPERLESS_FILENAME_DATE_ORDER=YMD
#PAPERLESS_FILENAME_PARSE_TRANSFORMS=[]
#PAPERLESS_NUMBER_OF_SUGGESTED_DATES=5
#PAPERLESS_THUMBNAIL_FONT_NAME=
#PAPERLESS_IGNORE_DATES=
#PAPERLESS_ENABLE_UPDATE_CHECK=
# Tika settings
#PAPERLESS_TIKA_ENABLED=false
#PAPERLESS_TIKA_ENDPOINT=http://localhost:9998
#PAPERLESS_TIKA_GOTENBERG_ENDPOINT=http://localhost:3000
# Binaries
#PAPERLESS_CONVERT_BINARY=/usr/bin/convert
#PAPERLESS_GS_BINARY=/usr/bin/gs

View File

@ -27,6 +27,7 @@ import { MailComponent } from './components/manage/mail/mail.component'
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component' import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
import { CustomFieldsComponent } from './components/manage/custom-fields/custom-fields.component' import { CustomFieldsComponent } from './components/manage/custom-fields/custom-fields.component'
import { ConfigComponent } from './components/admin/config/config.component' import { ConfigComponent } from './components/admin/config/config.component'
import { ViewallForderComponent } from './components/folder-management/viewall-forder/viewall-forder.component'
export const routes: Routes = [ export const routes: Routes = [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' }, { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
@ -115,6 +116,17 @@ export const routes: Routes = [
}, },
}, },
}, },
{
path: 'Viewallforder',
component: ViewallForderComponent,
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.Viewallforder,
},
},
},
{ {
path: 'documenttypes', path: 'documenttypes',
component: DocumentTypeListComponent, component: DocumentTypeListComponent,

View File

@ -172,6 +172,14 @@
<span i18n>Manage</span> <span i18n>Manage</span>
</h6> </h6>
<ul class="nav flex-column mb-2"> <ul class="nav flex-column mb-2">
<li class="nav-item app-link"
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Viewallforder }">
<a class="nav-link" routerLink="Viewallforder" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="viewallforder" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="Personal"></i-bs><span>&nbsp;<ng-container i18n>Personal document</ng-container></span>
</a>
</li>
<li class="nav-item app-link" <li class="nav-item app-link"
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"> *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
<a class="nav-link" routerLink="correspondents" routerLinkActive="active" (click)="closeMenu()" <a class="nav-link" routerLink="correspondents" routerLinkActive="active" (click)="closeMenu()"

View File

@ -0,0 +1,156 @@
<div class="viewforder-header">
<div class="search-new">
<form class="myform" action="">
<button><i class="fa-solid fa-magnifying-glass"></i></button>
<input type="text" placeholder="Search">
</form>
<div class="add-viewforder">
<button class="addforder"><i class="fa-solid fa-folder-plus"></i> Tạo thư mục mới</button>
</div>
</div>
<div class="titel">
<p>Show</p>
<select name="Show" id="showSelect">
<option value="16" >16</option>
<option value="36">36</option>
<option value="66">66</option>
</select>
<script>
document.addEventListener('DOMContentLoaded', function() {
const defaultOption = document.querySelector('#showSelect option[selected]');
if (defaultOption) {
defaultOption.textContent = 'Show ' + defaultOption.value;
}
});
</script>
<h1 style="margin-left: 10px;">Forder</h1>
</div>
<div class="conten">
<div *ngFor="let folder of folders" class="product-forder">
<i class="fa-solid fa-folder"></i>
<p>{{ folder.name }}</p>
<i class="fa-solid fa-ellipsis-vertical" onclick="toggleDropdown(this)"></i>
<div class="submenu">
<ul>
<li><a href="#">Delete</a></li>
<li><a href="#">Reaname</a></li>
</ul>
</div>
</div>
<script>
document.addEventListener('click', function(event) {
const submenus = document.querySelectorAll('.submenu');
const dropdownIcons = document.querySelectorAll('.fa-ellipsis-vertical');
for (let i = 0; i < submenus.length; i++) {
const submenu = submenus[i];
const dropdownIcon = dropdownIcons[i];
const isClickInside = dropdownIcon.contains(event.target);
if (!isClickInside) {
submenu.classList.remove('active');
}
}
});
function toggleDropdown(icon) {
const submenu = icon.nextElementSibling;
submenu.classList.toggle('active');
event.stopPropagation();
}
</script>
</div>
<div style="margin-top: 20px;" class="titel">
<p>Show</p>
<select name="Show" id="showSelect">
<option value="16" >16</option>
<option value="36">36</option>
<option value="66">66</option>
</select>
<script>
document.addEventListener('DOMContentLoaded', function() {
const defaultOption = document.querySelector('#showSelect option[selected]');
if (defaultOption) {
defaultOption.textContent = 'Show ' + defaultOption.value;
}
});
</script>
<h2 style="margin-left: 10px;">Documents</h2>
</div>
<div class="conten-tailieu">
<div style="display: none;" class="conten-tailieu-submenu">
<ul>
<li><a href="#"><i class="fa-solid fa-folder"></i></a></li>
<li><a href="#"><i class="fa-solid fa-trash"></i></a></li>
</ul>
</div>
<table id="myTable">
<thead>
<tr>
<th>Document name</th>
<th>Last modified <i class="fa-solid fa-caret-down"></i><i class="fa-solid fa-arrow-right-long"></i></th>
<th>File size</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let document of documents">
<td>{{ document.original_filename }}</td>
<td>{{ document.modified | date:'medium' }}</td>
<td>{{ calculateFileSize(document.archive_checksum) }}</td>
</tr>
</tbody>
</table>
<script>
document.addEventListener('DOMContentLoaded', function() {
const table = document.getElementById('myTable');
let selectedRows = [];
table.addEventListener('click', function(event) {
const target = event.target.closest('tr'); // Find closest parent <tr>
if (!target) return;
const isCtrlPressed = event.ctrlKey || event.metaKey; // Check if Ctrl or Cmd key is pressed
if (isCtrlPressed) {
if (selectedRows.includes(target)) {
// Deselect row
selectedRows = selectedRows.filter(row => row !== target);
target.classList.remove('selected');
} else {
// Select row
selectedRows.push(target);
target.classList.add('selected');
}
} else {
// Clear all selections
selectedRows.forEach(row => row.classList.remove('selected'));
selectedRows = [target];
target.classList.add('selected');
}
// Show or hide .conten-tailieu-submenu based on selection
const submenu = document.querySelector('.conten-tailieu-submenu');
if (selectedRows.length > 0) {
submenu.style.display = 'block';
} else {
submenu.style.display = 'none';
}
});
// Hide submenu if user clicks outside
document.addEventListener('click', function(event) {
if (!event.target.closest('.conten-tailieu-submenu') && !event.target.closest('table')) {
// Clear all selections and hide submenu
selectedRows.forEach(row => row.classList.remove('selected'));
selectedRows = [];
const submenu = document.querySelector('.conten-tailieu-submenu');
submenu.style.display = 'none';
}
});
});
</script>
</div>
</div>

View File

@ -0,0 +1,224 @@
.viewforder-header {
width: 100%;
}
.search-new {
width: 100%;
height: 100px;
margin: 5px;
display: flex;
align-items: center;
.myform {
margin-top: 10px;
height: 100%;
display: flex;
align-items: center;
button {
padding: 10px 15px;
cursor: pointer;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
&:active {
cursor: progress;
}
}
input {
padding: 10px 10px;
width: 400px;
margin-left: -6px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
}
}
.add-viewforder {
margin-top: 10px;
height: 100%;
display: flex;
align-items: center;
}
.viewforder-header .addforder {
padding: 10px 10px;
margin-left: 50px;
color: aliceblue;
background-color: rgb(236, 72, 72);
border: none;
border-radius: 5px;
cursor: pointer;
&:active {
cursor: progress;
}
}
.fa-folder-plus {
padding: 0 5px;
}
.conten {
width: 100%;
height: auto;
margin-top: 20px;
display: flex;
gap: 1em;
flex-wrap: wrap;
margin-left: 10px;
position: relative;
cursor: pointer;
.product-forder {
width: 200px;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 10px;
padding: 0 10px;
height: 50px;
cursor: pointer;
background-color: rgba(128, 128, 128, 0.585);
p {
margin-left: -40%;
}
&:active {
cursor: progress;
}
}
.fa-ellipsis-vertical {
cursor: wait;
padding: 3px 3px;
}
.submenu {
position: absolute;
width: 100px;
height: auto;
margin-left: 190px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
overflow: hidden;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, transform 0.3s;
transform: translateY(-100%);
ul li {
list-style-type: none;
padding: 5px 5px;
border: 1px solid gray;
background-color: rgba(0, 0, 0, 0.804);
a {
color: aliceblue;
text-decoration: none;
&:hover {
border-bottom: 1px solid white;
}
}
}
&.active {
opacity: 1;
visibility: visible;
transform: translateY(0%);
}
.product-forder & {
transform-origin: top;
}
.product-forder ul {
transform-origin: bottom;
}
}
}
.titel {
display: flex;
align-items: center;
}
#showSelect {
padding: 5px 5px;
}
.conten-tailieu{
width: 100%;
margin-top: 10px;
height: auto;
background-color: rgb(204, 202, 202);
}
.conten-tailieu {
width: 100%;
table {
width: 100%;
border-collapse: collapse;
th, td {
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
position: relative;
}
td:first-child {
width: 50%;
}
td:nth-child(2),
td:nth-child(3) {
text-align: right;
}
th:nth-child(2),
th:nth-child(3) {
text-align: center;
}
tbody th:nth-child(2),
tbody th:nth-child(3),
tbody td:nth-child(2),
tbody td:nth-child(3) {
text-align: center;
}
}
}
.selected {
background-color: lightblue;
}
.fa-arrow-right-long {
display: inline-block;
transform: rotate(90deg);
padding: 0 15px;
}
.conten-tailieu-submenu{
position: fixed;
display: none;
z-index: 10000;
right: 0;
top: 40%;
width: 40px;
height: auto;
background-color: white;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 1px 0px 1px 1px black;
}
.conten-tailieu-submenu ul li{
list-style-type: none;
}
.conten-tailieu-submenu .fa-solid{
padding: 5px 12px;
font-size: 18px;
}

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ViewallForderComponent } from './viewall-forder.component';
describe('ViewallForderComponent', () => {
let component: ViewallForderComponent;
let fixture: ComponentFixture<ViewallForderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ViewallForderComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ViewallForderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,26 @@
import { Component } from '@angular/core';
import { FoldersService } from 'src/app/services/rest/folders.service';
@Component({
selector: 'pngx-viewall-forder',
standalone: true,
imports: [],
templateUrl: './viewall-forder.component.html',
styleUrl: './viewall-forder.component.scss'
})
export class ViewallForderComponent {
folders: any[] = [];
documents: any[] = [];
constructor(private foldersService: FoldersService) { }
ngOnInit(): void {
this.foldersService.getFoldersAndDocuments().subscribe(data => {
this.folders = data.folders;
this.documents = data.documents;
});
}
calculateFileSize(checksum: string): string {
return checksum.length.toString() + ' bytes';
}
}

View File

@ -0,0 +1,10 @@
import { MatchingModel } from './matching-model'
export interface folders extends MatchingModel {
type?: string
parent_warehouse?: number
path?: string
}

View File

@ -14,6 +14,7 @@ export enum PermissionType {
Tag = '%s_tag', Tag = '%s_tag',
Warehouse = '%s_warehouse', Warehouse = '%s_warehouse',
Correspondent = '%s_correspondent', Correspondent = '%s_correspondent',
Viewallforder = '%s_viewallforder',
DocumentType = '%s_documenttype', DocumentType = '%s_documenttype',
StoragePath = '%s_storagepath', StoragePath = '%s_storagepath',
SavedView = '%s_savedview', SavedView = '%s_savedview',

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { FoldersService } from './folders.service';
describe('FoldersService', () => {
let service: FoldersService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(FoldersService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Warehouse } from 'src/app/data/warehouse'
import { AbstractNameFilterService } from './abstract-name-filter-service';
@Injectable({
providedIn: 'root'
})
export class FoldersService extends AbstractNameFilterService<folders> {
constructor(http: HttpClient) {
super(http, 'folders')
}
}

View File

@ -1,4 +1,3 @@
import { DOCUMENT } from '@angular/common'
import { HttpClient } from '@angular/common/http' import { HttpClient } from '@angular/common/http'
import { import {
EventEmitter, EventEmitter,
@ -22,6 +21,7 @@ import { User } from '../data/user'
import { PermissionsService } from './permissions.service' import { PermissionsService } from './permissions.service'
import { ToastService } from './toast.service' import { ToastService } from './toast.service'
import { SavedView } from '../data/saved-view' import { SavedView } from '../data/saved-view'
import { DOCUMENT } from '@angular/common'
export interface LanguageOption { export interface LanguageOption {
code: string code: string