Added Settings, Updated Schema & Documentation

This commit is contained in:
Aiman Fakhrullah Mantasan 2025-05-31 12:12:00 +08:00
parent 1c0afe2b8a
commit b157b8b47d
19 changed files with 3915 additions and 2815 deletions

View File

@ -1,204 +0,0 @@
# Changelog
All notable changes to the Electronic Document Management System (EDMS) project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.0] - 2024-01-15
### 🎨 **Major Design System Overhaul**
#### Added
- **New Standardized Components**
- `RsInput.vue`: Comprehensive input component with validation, sizing, and error states
- `RsSelect.vue`: Dropdown/select component with single/multiple selection support
- `RsTextarea.vue`: Textarea component with configurable resize and validation
- Design system documentation page at `/dms/design-system`
- Interactive component playground with live examples
- Component usage guidelines and best practices
- **Enhanced Design System Features**
- Consistent prop structure across all Rs components (`modelValue`, `label`, `error`, `disabled`, `size`)
- Universal dark mode support via CSS variables
- Accessibility features with proper ARIA attributes
- Standardized sizing system (sm, md, lg)
- Error state handling with validation messaging
- Real-time validation and form state management
#### Changed
- **Settings Architecture Simplification**
- Reduced from 10 to 5 core settings categories:
- 🔐 User & Access Management
- 📁 Document & Folder Settings
- 📝 Metadata & Tagging
- 📤 Upload & Storage Settings
- 📅 System Settings
- Removed categories: Search & Indexing, Integration Settings, Audit & Compliance, Workflow & Automation, UI & Branding
- Streamlined settings structure for better usability and maintenance
- **Component Standardization**
- `DMSAccessRequestDialog.vue`: Replaced manual button styling with `RsButton` components
- Settings page: Updated key form inputs to use `RsInput`, `RsSelect`, `RsTextarea`
- Standardized modal footer patterns across all dialogs
- Consistent form validation and error handling
- **Enhanced Component Registration**
- Added new Rs components to global component registry
- Updated export system in `pages/devtool/code-playground/index.js`
- Improved component discovery and usage
#### Improved
- **Form Consistency**
- Unified form styling across all components
- Consistent error handling and validation states
- Standardized label and placeholder patterns
- Improved accessibility with proper form associations
- **User Experience**
- Better visual consistency across the application
- Improved dark mode support and theme switching
- Enhanced form validation with real-time feedback
- Standardized interaction patterns
### 📖 **Documentation Updates**
#### Added
- **Comprehensive Design System Documentation**
- Interactive component playground at `/dms/design-system`
- Component API documentation with props and examples
- Usage guidelines and best practices
- Color system and typography scale documentation
- Accessibility standards and compliance information
- **Enhanced Technical Guide**
- Complete design system section with component specifications
- Form component usage examples and API reference
- Color system documentation with CSS variable reference
- Typography and spacing scale definitions
- Integration patterns and best practices
#### Updated
- **User Guide Enhancements**
- Added comprehensive Settings Administration section
- Documented simplified 5-category settings structure
- Detailed explanation of each settings category
- Best practices for settings management
- Troubleshooting guide for settings issues
- **README Overhaul**
- Complete project overview with features and technology stack
- Design system highlights and component examples
- Comprehensive setup and installation instructions
- Development guidelines and contribution standards
- Security features and support information
### 🔧 **Technical Improvements**
#### Enhanced
- **CSS Architecture**
- Improved CSS variable system for theming
- Standardized component styling patterns
- Better organization of style files
- Enhanced dark mode support
- **Form Handling**
- Consistent v-model patterns across components
- Improved validation and error state management
- Better form component composition
- Enhanced accessibility features
- **State Management**
- Improved settings state management
- Better validation and error handling
- Enhanced form state synchronization
### 🚨 **Breaking Changes**
- Settings API structure changed due to category reduction
- Some component prop names standardized (may affect custom implementations)
- Manual form styling replaced with Rs components (update required for custom forms)
### 💻 **Development Experience**
#### Improved
- **Component Development**
- Standardized component patterns for easier development
- Better TypeScript support for component props
- Improved component documentation and examples
- Enhanced development guidelines
- **Design Consistency**
- Clear design system guidelines
- Standardized component usage patterns
- Better visual consistency across the application
- Improved maintainability
#### Added
- **Development Tools**
- Interactive design system playground
- Component examples and usage guidelines
- Development best practices documentation
- Contributing guidelines for design system
### 📱 **User Interface**
#### Enhanced
- **Visual Consistency**
- Unified button styles and interactions
- Consistent form field appearance
- Standardized modal and dialog layouts
- Improved color scheme and theming
- **Accessibility**
- Better keyboard navigation support
- Improved screen reader compatibility
- Consistent focus states and indicators
- WCAG 2.1 compliance improvements
- **Responsive Design**
- Better mobile form interactions
- Improved tablet layout handling
- Consistent breakpoint management
- Enhanced touch interaction support
### 🔄 **Migration Guide**
#### For Developers
1. **Update Form Components**: Replace manual form styling with Rs components
2. **Settings Integration**: Update any settings-related code for new 5-category structure
3. **Component Usage**: Follow new Rs component patterns for consistency
4. **Styling Updates**: Remove manual Tailwind classes in favor of Rs components
#### For Users
1. **Settings Location**: Settings now organized in 5 streamlined categories
2. **Interface Changes**: Improved form interactions and validation
3. **New Features**: Access to design system documentation and examples
### 🎯 **Future Roadmap**
- Additional Rs components (date picker, multi-select, autocomplete)
- Enhanced animation system for micro-interactions
- Expanded accessibility features and testing
- Advanced form validation and error handling
- Component testing and documentation automation
---
## [1.0.0] - 2023-12-01
### Initial Release
- Electronic Document Management System with hierarchical organization
- User authentication and role-based access control
- Document upload, preview, and management capabilities
- Basic settings and configuration system
- Initial UI components and styling
---
**Legend:**
- 🎨 Design System
- 📖 Documentation
- 🔧 Technical
- 🚨 Breaking Changes
- 💻 Development Experience
- 📱 User Interface
- 🔄 Migration
- 🎯 Roadmap

253
README.md
View File

@ -1,256 +1,45 @@
# Electronic Document Management System (EDMS) # Nuxt 3 Minimal Starter
A modern, web-based document management system built with Nuxt.js 3 and Vue.js 3, featuring a comprehensive design system and hierarchical document organization. Look at the [nuxt 3 documentation](https://v3.nuxtjs.org) to learn more.
## 🚀 Features ## Setup
### Core Functionality Make sure to install the dependencies:
- **Hierarchical Organization**: Cabinet → Drawer → Folder → Subfolder structure
- **Advanced Document Management**: Upload, version control, metadata management
- **Role-Based Access Control**: Granular permissions with access request workflows
- **Multi-format Document Viewer**: Built-in viewer for PDF, images, Office documents
- **Advanced Search**: Full-text search with metadata and tag filtering
- **Responsive Design**: Works seamlessly across desktop, tablet, and mobile
### Design System
- **Standardized Components**: Complete "Rs" component library (RsButton, RsInput, RsSelect, etc.)
- **Consistent Styling**: Unified design patterns with dark/light mode support
- **Accessibility**: WCAG-compliant components with proper ARIA attributes
- **Interactive Documentation**: Design system playground at `/dms/design-system`
### Administration
- **Streamlined Settings**: 5 core configuration categories
- 🔐 User & Access Management
- 📁 Document & Folder Settings
- 📝 Metadata & Tagging
- 📤 Upload & Storage Settings
- 📅 System Settings
- **Import/Export**: Configuration backup and transfer capabilities
- **Real-time Validation**: Form validation with dependency checking
## 🛠️ Technology Stack
### Frontend
- **Nuxt.js 3**: Universal Vue.js framework with SSR/SPA support
- **Vue.js 3**: Progressive framework with Composition API
- **TailwindCSS**: Utility-first CSS framework with custom component system
- **Pinia**: Modern state management with persistence
- **FormKit**: Advanced form handling with custom theming
### Backend
- **Prisma ORM**: Type-safe database client with migrations
- **MySQL/PostgreSQL**: Relational database with comprehensive schema
- **File System Integration**: Secure file storage and management
- **JWT Authentication**: Token-based authentication with RBAC
### Development Tools
- **TypeScript**: Type safety and enhanced development experience
- **ESLint**: Code linting with Vue.js specific rules
- **Vite**: Lightning-fast build tool and HMR
## 📋 Setup
### Requirements
- Node.js 18+ and npm/yarn/pnpm
- MySQL 8+ or PostgreSQL 13+
- Modern web browser with JavaScript enabled
### Installation
1. **Clone Repository**
```bash ```bash
git clone https://github.com/your-repo/edms.git # yarn
cd edms
```
2. **Install Dependencies**
```bash
# Using npm
npm install
# Using yarn
yarn install yarn install
# Using pnpm # npm
pnpm install npm install
# pnpm
pnpm install --shamefully-hoist
``` ```
3. **Environment Configuration** ## Development Server
Create `.env` file:
```env
# Database
DATABASE_URL="mysql://username:password@localhost:3306/edms_db"
# Authentication Start the development server on http://localhost:3000
JWT_SECRET="your-jwt-secret-key-min-256-bits"
SESSION_SECRET="your-session-secret-key"
# File Storage
UPLOAD_PATH="/var/uploads/edms"
MAX_FILE_SIZE="104857600" # 100MB
ALLOWED_FILE_TYPES="pdf,doc,docx,xls,xlsx,ppt,pptx,txt,jpg,jpeg,png"
# Application
NUXT_SECRET_KEY="your-nuxt-app-secret"
BASE_URL="http://localhost:3000"
```
4. **Database Setup**
```bash
# Generate Prisma client
npx prisma generate
# Run migrations
npx prisma db push
# Seed data (optional)
npx prisma db seed
```
5. **Development Server**
```bash ```bash
npm run dev npm run dev
``` ```
Access the application at `http://localhost:3000` ## Production
## 🎨 Design System Build the application for production:
### Component Library
The EDMS uses a standardized component library with the "Rs" prefix:
#### Form Components
- **RsInput**: Standardized input fields with validation
- **RsSelect**: Dropdown/select with options support
- **RsTextarea**: Multi-line text input with resize controls
- **RsButton**: Buttons with multiple variants and sizes
#### UI Components
- **RsCard**: Container component with header/body/footer
- **RsModal**: Modal dialogs with standardized structure
- **RsTable**: Data tables with sorting and filtering
- **RsDropdown**: Dropdown menus and navigation
### Usage Examples
```vue
<!-- Input with validation -->
<rs-input
v-model="email"
label="Email Address"
type="email"
:required="true"
:error="emailError"
/>
<!-- Button with variants -->
<rs-button variant="primary" size="md" @click="save">
Save Changes
</rs-button>
<!-- Modal with standardized structure -->
<rs-modal :visible="showModal" @close="closeModal">
<template #header>Modal Title</template>
<template #body>Modal content</template>
<template #footer>
<rs-button variant="secondary" @click="closeModal">Cancel</rs-button>
<rs-button variant="primary" @click="confirm">Confirm</rs-button>
</template>
</rs-modal>
```
### Design Principles
- **🎯 Consistency**: Unified patterns across all components
- **🔧 Modularity**: Reusable and composable components
- **🌙 Dark Mode**: Universal theme support
- **📱 Responsive**: Mobile-first approach
- **♿ Accessibility**: WCAG-compliant with keyboard navigation
## 📖 Documentation
### Available Guides
- **[Technical Guide](docs/Technical_Guide.md)**: Comprehensive technical documentation
- **[User Guide](docs/User_Guide.md)**: End-user instructions and features
- **[Site Settings](docs/SITE_SETTINGS.md)**: Configuration and customization guide
### Interactive Documentation
- **Design System**: Visit `/dms/design-system` for component examples
- **API Documentation**: Available in development mode
- **Component Playground**: Test components with live examples
## 🔧 Development
### Project Structure
```
edms/
├── components/ # Vue components
│ ├── dms/ # DMS-specific components
│ │ ├── dialogs/ # Modal dialogs
│ │ ├── explorer/ # Document browser
│ │ ├── search/ # Search functionality
│ │ └── viewers/ # Document preview
│ └── Rs*.vue # Design system components
├── pages/ # File-based routing
│ ├── dms/ # DMS pages
│ └── devtool/ # Admin tools
├── stores/ # Pinia state management
├── server/ # API routes and middleware
├── prisma/ # Database schema and migrations
├── assets/ # Stylesheets and assets
└── docs/ # Documentation
```
### Building for Production
```bash ```bash
# Build application
npm run build npm run build
# Preview production build
npm run preview
# Generate static site (if applicable)
npm run generate
``` ```
## 🔐 Security Features Locally preview production build:
- **Role-Based Access Control**: Granular permission system ```bash
- **Document-level Security**: Individual document access controls npm run preview
- **Access Request Workflow**: Approval system for restricted documents ```
- **Audit Trail**: Comprehensive activity logging
- **File Type Validation**: Security through file type restrictions
- **Session Management**: Secure token-based authentication
## 🤝 Contributing Checkout the [deployment documentation](https://v3.nuxtjs.org/guide/deploy/presets) for more information.
# corradAF
1. Fork the repository This is the base project for corradAF.
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Follow the design system guidelines
4. Use standardized components (Rs library)
5. Add tests for new functionality
6. Commit changes (`git commit -m 'Add amazing feature'`)
7. Push to branch (`git push origin feature/amazing-feature`)
8. Open a Pull Request
### Development Guidelines
- Use Rs components instead of custom styling
- Follow semantic variant naming (primary, secondary, danger)
- Test in both light and dark modes
- Maintain accessibility standards
- Document new features in user/technical guides
## 📄 License
This project is part of the corradAF base project. See the license file for details.
## 🆘 Support
- **Technical Issues**: Check the [Technical Guide](docs/Technical_Guide.md)
- **User Questions**: Refer to the [User Guide](docs/User_Guide.md)
- **Component Usage**: Visit `/dms/design-system` for examples
- **Bug Reports**: Create an issue with detailed reproduction steps
---
Built with ❤️ using Nuxt.js 3, Vue.js 3, and modern web technologies.

View File

@ -1,115 +0,0 @@
<script setup>
const props = defineProps({
modelValue: {
type: [String, Number],
default: ''
},
type: {
type: String,
default: 'text'
},
placeholder: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
required: {
type: Boolean,
default: false
},
error: {
type: String,
default: ''
},
label: {
type: String,
default: ''
},
size: {
type: String,
default: 'md'
}
});
const emit = defineEmits(['update:modelValue']);
const updateValue = (event) => {
emit('update:modelValue', event.target.value);
};
</script>
<template>
<div class="rs-input-wrapper">
<label v-if="label" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{{ label }}
<span v-if="required" class="text-red-500">*</span>
</label>
<input
:value="modelValue"
@input="updateValue"
:type="type"
:placeholder="placeholder"
:disabled="disabled"
:required="required"
class="rs-input"
:class="{
'rs-input-sm': size === 'sm',
'rs-input-md': size === 'md',
'rs-input-lg': size === 'lg',
'rs-input-error': error,
'rs-input-disabled': disabled
}"
/>
<div v-if="error" class="rs-input-error-message">
{{ error }}
</div>
</div>
</template>
<style scoped>
.rs-input-wrapper {
@apply w-full;
}
.rs-input {
@apply w-full px-3 py-2 border rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors;
border-color: rgb(var(--fk-border-color));
}
.rs-input:hover {
@apply border-gray-400 dark:border-gray-500;
}
.rs-input:focus {
@apply outline-none ring-2 ring-blue-500 border-blue-500;
}
.rs-input-sm {
@apply px-2 py-1 text-sm;
}
.rs-input-md {
@apply px-3 py-2 text-sm;
}
.rs-input-lg {
@apply px-4 py-3 text-base;
}
.rs-input-error {
@apply border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500;
}
.rs-input-disabled {
@apply bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed;
}
.rs-input-error-message {
@apply text-sm text-red-500 mt-1;
}
</style>

View File

@ -1,143 +0,0 @@
<script setup>
const props = defineProps({
modelValue: {
type: [String, Number, Array],
default: ''
},
options: {
type: Array,
required: true
},
placeholder: {
type: String,
default: 'Select an option'
},
disabled: {
type: Boolean,
default: false
},
required: {
type: Boolean,
default: false
},
error: {
type: String,
default: ''
},
label: {
type: String,
default: ''
},
size: {
type: String,
default: 'md'
},
multiple: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue']);
const updateValue = (event) => {
emit('update:modelValue', event.target.value);
};
</script>
<template>
<div class="rs-select-wrapper">
<label v-if="label" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{{ label }}
<span v-if="required" class="text-red-500">*</span>
</label>
<select
:value="modelValue"
@change="updateValue"
:disabled="disabled"
:required="required"
:multiple="multiple"
class="rs-select"
:class="{
'rs-select-sm': size === 'sm',
'rs-select-md': size === 'md',
'rs-select-lg': size === 'lg',
'rs-select-error': error,
'rs-select-disabled': disabled
}"
>
<option v-if="!multiple && placeholder" value="" disabled>
{{ placeholder }}
</option>
<option
v-for="option in options"
:key="option.value || option"
:value="option.value || option"
>
{{ option.label || option }}
</option>
</select>
<div v-if="error" class="rs-select-error-message">
{{ error }}
</div>
</div>
</template>
<style scoped>
.rs-select-wrapper {
@apply w-full;
}
.rs-select {
@apply w-full px-3 py-2 border rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors appearance-none;
border-color: rgb(var(--fk-border-color));
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
}
.rs-select:hover {
@apply border-gray-400 dark:border-gray-500;
}
.rs-select:focus {
@apply outline-none ring-2 ring-blue-500 border-blue-500;
}
.rs-select-sm {
@apply px-2 py-1 text-sm;
padding-right: 2rem;
}
.rs-select-md {
@apply px-3 py-2 text-sm;
padding-right: 2.5rem;
}
.rs-select-lg {
@apply px-4 py-3 text-base;
padding-right: 3rem;
}
.rs-select-error {
@apply border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500;
}
.rs-select-disabled {
@apply bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed;
}
.rs-select-error-message {
@apply text-sm text-red-500 mt-1;
}
.rs-select[multiple] {
background-image: none;
padding-right: 0.75rem;
min-height: 6rem;
}
</style>

View File

@ -1,105 +0,0 @@
<script setup>
const props = defineProps({
modelValue: {
type: String,
default: ''
},
placeholder: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
required: {
type: Boolean,
default: false
},
error: {
type: String,
default: ''
},
label: {
type: String,
default: ''
},
rows: {
type: Number,
default: 4
},
resize: {
type: String,
default: 'vertical' // none, both, horizontal, vertical
}
});
const emit = defineEmits(['update:modelValue']);
const updateValue = (event) => {
emit('update:modelValue', event.target.value);
};
</script>
<template>
<div class="rs-textarea-wrapper">
<label v-if="label" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
{{ label }}
<span v-if="required" class="text-red-500">*</span>
</label>
<textarea
:value="modelValue"
@input="updateValue"
:placeholder="placeholder"
:disabled="disabled"
:required="required"
:rows="rows"
class="rs-textarea"
:class="{
'rs-textarea-error': error,
'rs-textarea-disabled': disabled,
'resize-none': resize === 'none',
'resize-both': resize === 'both',
'resize-x': resize === 'horizontal',
'resize-y': resize === 'vertical'
}"
></textarea>
<div v-if="error" class="rs-textarea-error-message">
{{ error }}
</div>
</div>
</template>
<style scoped>
.rs-textarea-wrapper {
@apply w-full;
}
.rs-textarea {
@apply w-full px-3 py-2 border rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-colors;
border-color: rgb(var(--fk-border-color));
min-height: 2.5rem;
}
.rs-textarea:hover {
@apply border-gray-400 dark:border-gray-500;
}
.rs-textarea:focus {
@apply outline-none ring-2 ring-blue-500 border-blue-500;
}
.rs-textarea-error {
@apply border-red-500 dark:border-red-500 focus:ring-red-500 focus:border-red-500;
}
.rs-textarea-disabled {
@apply bg-gray-100 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed;
}
.rs-textarea-error-message {
@apply text-sm text-red-500 mt-1;
}
</style>

View File

@ -175,24 +175,29 @@ const submitRequest = async () => {
<!-- Access Duration Section --> <!-- Access Duration Section -->
<div class="mb-6"> <div class="mb-6">
<rs-select <label class="block text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">Access Duration</label>
<select
v-model="accessDuration" v-model="accessDuration"
:options="durationOptions" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
label="Access Duration" >
:required="false" <option v-for="duration in durationOptions" :key="duration" :value="duration">
/> {{ duration }}
</option>
</select>
</div> </div>
<!-- Justification Section --> <!-- Justification Section -->
<div class="mb-6"> <div class="mb-6">
<rs-textarea <label class="block text-sm font-medium text-gray-900 dark:text-gray-100 mb-2">
Justification
<span class="text-red-500">*</span>
</label>
<textarea
v-model="justification" v-model="justification"
label="Justification" rows="4"
placeholder="Please explain why you need access to this document..." placeholder="Please explain why you need access to this document..."
:rows="4" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none"
:required="true" ></textarea>
resize="none"
/>
</div> </div>
<!-- Footer Note --> <!-- Footer Note -->
@ -207,7 +212,8 @@ const submitRequest = async () => {
<rs-button <rs-button
@click="closeDialog" @click="closeDialog"
:disabled="isSubmitting" :disabled="isSubmitting"
variant="secondary" variant="secondary-outline"
size="sm"
> >
Cancel Cancel
</rs-button> </rs-button>
@ -215,7 +221,7 @@ const submitRequest = async () => {
@click="submitRequest" @click="submitRequest"
:disabled="isSubmitting" :disabled="isSubmitting"
variant="primary" variant="primary"
class="flex items-center" size="sm"
> >
<svg v-if="isSubmitting" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <svg v-if="isSubmitting" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>

View File

@ -275,9 +275,9 @@ const getFieldComponent = (fieldType) => {
<div v-else> <div v-else>
<p class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2"> <p class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-2">
Drag and drop files here, or Drag and drop files here, or
<button @click="openFileDialog" class="text-blue-600 hover:text-blue-500 dark:text-blue-400 dark:hover:text-blue-300"> <rs-button @click="openFileDialog" variant="primary-text" size="sm">
browse browse
</button> </rs-button>
</p> </p>
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
Supported: {{ dmsStore.systemSettings.upload.allowedFileTypes.join(', ').toUpperCase() }} Supported: {{ dmsStore.systemSettings.upload.allowedFileTypes.join(', ').toUpperCase() }}
@ -310,12 +310,13 @@ const getFieldComponent = (fieldType) => {
<p class="text-sm text-gray-500">{{ formatFileSize(file.size) }}</p> <p class="text-sm text-gray-500">{{ formatFileSize(file.size) }}</p>
</div> </div>
</div> </div>
<button @click="removeFile(index)" <rs-button @click="removeFile(index)"
class="text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"> variant="danger-text"
size="sm">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> <path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg> </svg>
</button> </rs-button>
</div> </div>
<!-- Validation Errors --> <!-- Validation Errors -->

View File

@ -0,0 +1,346 @@
export const useDmsSettings = () => {
// Global DMS settings state
const dmsSettings = useState('dmsSettings', () => ({
// User & Access Management
access: {
userRoles: ['Admin', 'Editor', 'Viewer', 'Uploader'],
rbacEnabled: true,
userGroups: ['HR Department', 'Finance', 'IT', 'Legal'],
permissions: {
view: true,
edit: true,
delete: false,
download: true,
share: true
},
authentication: {
ssoEnabled: false,
mfaRequired: false,
ldapIntegration: false,
sessionTimeout: 8
}
},
// Document & Folder Settings
documents: {
folderHierarchy: {
maxDepth: 5,
defaultStructure: ['Department', 'Project', 'Category', 'Year'],
folderTemplates: ['Standard', 'Project-based', 'Department-based']
},
namingConventions: {
autoGenerate: true,
mandatoryFields: ['title', 'department', 'date'],
pattern: '{department}_{title}_{date}'
},
retention: {
enabled: true,
defaultDays: 2555, // 7 years
archiveBeforeDelete: true
},
versionControl: {
enabled: true,
maxVersions: 10,
autoVersioning: true
}
},
// Metadata & Tagging
metadata: {
customFields: [
{ name: 'Department', type: 'dropdown', required: true },
{ name: 'Priority', type: 'select', required: false },
{ name: 'Project Code', type: 'text', required: true },
{ name: 'Review Date', type: 'date', required: false }
],
tagging: {
predefinedTags: ['urgent', 'confidential', 'public', 'draft', 'final'],
userGeneratedTags: true,
tagSuggestions: true
},
classification: {
autoClassification: true,
rules: ['confidential-keywords', 'department-based', 'file-type']
}
},
// Workflow & Automation
workflow: {
approvalFlows: {
enabled: true,
defaultFlow: 'department-head-approval',
customFlows: ['legal-review', 'finance-approval', 'director-sign-off']
},
notifications: {
emailNotifications: true,
inAppNotifications: true,
uploadAlerts: true,
deadlineReminders: true
},
automation: {
triggers: ['document-uploaded', 'approval-completed', 'deadline-reached'],
actions: ['move-to-folder', 'send-notification', 'create-task']
}
},
// Upload & Storage Settings
upload: {
fileTypes: {
allowed: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'jpg', 'png'],
blocked: ['exe', 'bat', 'cmd']
},
fileSizeLimit: 100, // MB
quotas: {
perUser: 5000, // MB
perGroup: 50000, // MB
perProject: 100000 // MB
},
storage: {
type: 'local', // local, s3, azure, google
path: '/var/uploads/edms',
backupEnabled: true,
compressionEnabled: false
}
},
// System Settings
system: {
timezone: 'Asia/Kuala_Lumpur',
backupSchedule: 'daily',
logLevel: 'info',
maintenanceMode: false,
autoUpdates: false,
systemMonitoring: true,
performanceMetrics: true
}
}));
// Loading state
const loading = useState('dmsSettingsLoading', () => false);
const saving = useState('dmsSettingsSaving', () => false);
// Load DMS settings from API
const loadDmsSettings = async () => {
loading.value = true;
try {
const response = await $fetch("/api/dms/settings", {
method: "GET",
});
if (response && response.data) {
dmsSettings.value = { ...dmsSettings.value, ...response.data };
console.log('[useDmsSettings] Settings loaded successfully:', response.data);
}
} catch (error) {
console.error("Error loading DMS settings:", error);
throw error;
} finally {
loading.value = false;
}
};
// Update DMS settings
const updateDmsSettings = async (newSettings) => {
console.log("[useDmsSettings] updateDmsSettings called with:", JSON.parse(JSON.stringify(newSettings)));
saving.value = true;
try {
const response = await $fetch("/api/dms/settings", {
method: "POST",
body: newSettings,
});
console.log("[useDmsSettings] API response received:", JSON.parse(JSON.stringify(response)));
if (response && response.statusCode === 200) {
// Reload settings after successful update
await loadDmsSettings();
console.log("[useDmsSettings] Returning success from updateDmsSettings.");
return { success: true, data: response.data };
}
let errorMessage = "Update operation failed: No data returned from server.";
if (response && typeof response === 'object' && response !== null && 'message' in response) {
errorMessage = response.message;
}
console.log("[useDmsSettings] Returning failure from updateDmsSettings:", errorMessage);
return { success: false, error: { message: errorMessage, details: response } };
} catch (error) {
console.error("[useDmsSettings] Error in updateDmsSettings catch block:", error);
let detailedMessage = "An unexpected error occurred during update.";
if (error.data && error.data.message) {
detailedMessage = error.data.message;
} else if (error.message) {
detailedMessage = error.message;
}
console.log("[useDmsSettings] Returning failure (catch block) from updateDmsSettings:", detailedMessage);
return { success: false, error: { message: detailedMessage, details: error } };
} finally {
saving.value = false;
}
};
// Reset settings to defaults
const resetToDefaults = async () => {
const defaultSettings = {
access: {
userRoles: ['Admin', 'Editor', 'Viewer', 'Uploader'],
rbacEnabled: true,
userGroups: ['HR Department', 'Finance', 'IT', 'Legal'],
permissions: {
view: true,
edit: true,
delete: false,
download: true,
share: true
},
authentication: {
ssoEnabled: false,
mfaRequired: false,
ldapIntegration: false,
sessionTimeout: 8
}
},
documents: {
folderHierarchy: {
maxDepth: 5,
defaultStructure: ['Department', 'Project', 'Category', 'Year'],
folderTemplates: ['Standard', 'Project-based', 'Department-based']
},
namingConventions: {
autoGenerate: true,
mandatoryFields: ['title', 'department', 'date'],
pattern: '{department}_{title}_{date}'
},
retention: {
enabled: true,
defaultDays: 2555,
archiveBeforeDelete: true
},
versionControl: {
enabled: true,
maxVersions: 10,
autoVersioning: true
}
},
metadata: {
customFields: [
{ name: 'Department', type: 'dropdown', required: true },
{ name: 'Priority', type: 'select', required: false },
{ name: 'Project Code', type: 'text', required: true },
{ name: 'Review Date', type: 'date', required: false }
],
tagging: {
predefinedTags: ['urgent', 'confidential', 'public', 'draft', 'final'],
userGeneratedTags: true,
tagSuggestions: true
},
classification: {
autoClassification: true,
rules: ['confidential-keywords', 'department-based', 'file-type']
}
},
workflow: {
approvalFlows: {
enabled: true,
defaultFlow: 'department-head-approval',
customFlows: ['legal-review', 'finance-approval', 'director-sign-off']
},
notifications: {
emailNotifications: true,
inAppNotifications: true,
uploadAlerts: true,
deadlineReminders: true
},
automation: {
triggers: ['document-uploaded', 'approval-completed', 'deadline-reached'],
actions: ['move-to-folder', 'send-notification', 'create-task']
}
},
upload: {
fileTypes: {
allowed: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'jpg', 'png'],
blocked: ['exe', 'bat', 'cmd']
},
fileSizeLimit: 100,
quotas: {
perUser: 5000,
perGroup: 50000,
perProject: 100000
},
storage: {
type: 'local',
path: '/var/uploads/edms',
backupEnabled: true,
compressionEnabled: false
}
},
system: {
timezone: 'Asia/Kuala_Lumpur',
backupSchedule: 'daily',
logLevel: 'info',
maintenanceMode: false,
autoUpdates: false,
systemMonitoring: true,
performanceMetrics: true
}
};
return await updateDmsSettings(defaultSettings);
};
// Export settings to JSON
const exportSettings = () => {
const dataStr = JSON.stringify(dmsSettings.value, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportFileDefaultName = 'dms-settings.json';
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click();
};
// Import settings from JSON
const importSettings = (jsonData) => {
try {
const importedSettings = JSON.parse(jsonData);
dmsSettings.value = { ...dmsSettings.value, ...importedSettings };
return { success: true };
} catch (error) {
console.error('Error importing settings:', error);
return { success: false, error: 'Invalid JSON format' };
}
};
// Get setting by category and key
const getSetting = (category, key) => {
if (dmsSettings.value[category]) {
return dmsSettings.value[category][key];
}
return null;
};
// Update specific setting
const updateSetting = async (category, key, value) => {
if (dmsSettings.value[category]) {
dmsSettings.value[category][key] = value;
// Save to backend
return await updateDmsSettings(dmsSettings.value);
}
return { success: false, error: 'Category not found' };
};
return {
dmsSettings: readonly(dmsSettings),
loading: readonly(loading),
saving: readonly(saving),
loadDmsSettings,
updateDmsSettings,
resetToDefaults,
exportSettings,
importSettings,
getSetting,
updateSetting
};
};

1019
docs/DMS_SETTINGS.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,161 +1,656 @@
# Site Settings Feature # Site Settings System - Comprehensive Guide
## Overview ## Overview
The Site Settings feature allows administrators to customize the appearance and branding of the application through a user-friendly interface. All settings are globally applied across the entire application including SEO, meta tags, and visual elements. The Site Settings system provides administrators with comprehensive control over the visual appearance, branding, and global configuration of the Electronic Document Management System (EDMS). This system allows complete customization of the application's look and feel, SEO settings, and theme management through an intuitive web interface.
## Features ## Table of Contents
1. [System Integration](#system-integration)
2. [Features Overview](#features-overview)
3. [Accessing Site Settings](#accessing-site-settings)
4. [Configuration Categories](#configuration-categories)
5. [Basic Information Settings](#basic-information-settings)
6. [Branding & Visual Identity](#branding--visual-identity)
7. [SEO & Meta Tag Management](#seo--meta-tag-management)
8. [Theme System Integration](#theme-system-integration)
9. [Advanced Customization](#advanced-customization)
10. [API Integration](#api-integration)
11. [Technical Implementation](#technical-implementation)
12. [Best Practices](#best-practices)
13. [Troubleshooting](#troubleshooting)
## System Integration
### EDMS Ecosystem
The Site Settings system is fully integrated with the EDMS ecosystem and works alongside:
- **DMS Settings**: Document management configuration (separate system)
- **User Management**: Role-based access control for settings access
- **Theme System**: Dynamic theme switching and custom theme support
- **Component Library**: Rs component system integration
- **Global State**: Pinia store management for reactive updates
### Global Impact
Site settings affect the entire application including:
- **Header and Navigation**: Site logo, name, and branding elements
- **Loading Screens**: Custom loading logos and branding
- **Document Viewer**: Consistent branding across all interfaces
- **Authentication Pages**: Login page branding and styling
- **SEO Elements**: Meta tags, Open Graph, and social media integration
- **Theme Application**: Global color schemes and visual styling
## Features Overview
### Core Capabilities
- **Complete Branding Control**: Logos, site name, and visual identity
- **Theme Management**: Built-in themes plus custom theme support
- **SEO Optimization**: Comprehensive meta tag and social media optimization
- **Custom Styling**: CSS injection and custom theme file support
- **Real-time Preview**: Live preview of changes before applying
- **Global Integration**: Settings applied across entire application
- **Backup & Restore**: Export/import configuration for backup and migration
### Administrative Features
- **Role-Based Access**: Only administrators can modify site settings
- **Validation System**: Comprehensive validation for all settings
- **File Upload Management**: Secure file upload for logos and assets
- **Version Control**: Track changes and maintain configuration history
- **Multi-Environment Support**: Different configurations for different environments
## Accessing Site Settings
### Prerequisites
- **Administrative Privileges**: User must have admin role
- **Modern Browser**: Chrome 70+, Firefox 65+, Safari 12+, Edge 79+
- **JavaScript Enabled**: Required for interactive functionality
- **Network Access**: Stable connection for file uploads and saves
### Navigation Path
1. **Main Menu**: Navigate to **Pentadbiran** (Administration)
2. **Configuration Section**: Select **Konfigurasi** (Configuration)
3. **Site Settings**: Choose **Site Settings** from the submenu
4. **Alternative Access**: Direct URL navigation to `/admin/site-settings`
### Interface Layout
- **Tabbed Interface**: Basic Info, Branding, SEO, Advanced settings
- **Live Preview Panel**: Real-time preview of changes
- **Action Buttons**: Save, Reset, Export, Import functionality
- **Validation Feedback**: Immediate feedback on invalid settings
- **Loading States**: Visual feedback during save operations
## Configuration Categories
### 1. Basic Information ### 1. Basic Information
- **Site Name**: Customize the application name displayed globally in: - **Site Name**: Application name displayed globally
- Header and sidebar - **Site Description**: Meta description for SEO and social sharing
- Browser title and meta tags - **Font Configuration**: Typography settings and font selection
- SEO and Open Graph tags - **Display Options**: Show/hide site name in header and other locations
- Loading screen
- All pages and components
- **Site Description**: Set a description used for:
- SEO meta descriptions
- Open Graph descriptions
- Twitter Card descriptions
- **Theme Selection**: Choose from available themes:
- Standard themes (from themeList.js)
- Accessibility themes (from themeList2.js)
- Custom themes added to theme.css
### 2. Branding ### 2. Branding
- **Site Logo**: Upload a custom logo displayed in: - **Site Logo**: Main application logo for headers and navigation
- Header (horizontal layout) - **Loading Logo**: Branded loading screen logo
- Sidebar (vertical layout) - **Favicon**: Browser tab icon and bookmark icon
- Loading screen - **Login Logo**: Dedicated branding for authentication pages
- Login page
- Any component using site settings
- **Favicon**: Upload a custom favicon displayed in:
- Browser tabs
- Bookmarks
- Mobile home screen icons
### 3. Advanced Settings ### 3. SEO & Social Media
- **Custom CSS**: Add custom CSS injected into document head - **Meta Tags**: Title, description, keywords, author information
- **Custom Theme File**: Upload CSS files saved to `/assets/style/css/` - **Open Graph**: Facebook and social media sharing optimization
- **Add Custom Theme to theme.css**: Directly add themes to the main theme.css file - **Twitter Cards**: Twitter-specific meta tag configuration
- **Analytics Integration**: Google Analytics, Tag Manager, Facebook Pixel
## How to Access ### 4. Advanced Settings
- **Custom CSS**: Global CSS injection for advanced styling
- **Custom Themes**: Upload and manage custom theme files
- **Theme Editor**: Direct editing of theme.css file
- **Developer Tools**: Advanced configuration options
1. Navigate to **Pentadbiran****Konfigurasi** → **Site Settings** ## Basic Information Settings
2. Use the tabbed interface:
- **Basic Info**: Site name, description, and theme selection
- **Branding**: Logo and favicon uploads
- **Advanced**: Custom CSS and theme management
3. Use the **Live Preview** panel to see changes in real-time
4. Click **Save Changes** to apply your settings
## Technical Implementation ### Site Name Configuration
- **Purpose**: Define the application name displayed throughout the system
- **Default Value**: "corradAF" (customizable)
- **Global Impact**: Updates header, page titles, meta tags, loading screens
- **Font Size Control**: Adjustable font size (12-36px range)
- **Character Limit**: Recommended maximum of 50 characters for optimal display
### Database Schema **Display Locations**:
The settings are stored in the `site_settings` table with the following fields: - Header navigation (primary logo area)
- `siteName`, `siteDescription` - Browser page titles (prefixed to page names)
- `siteLogo`, `siteFavicon` - Loading screen branding
- `selectedTheme` - Selected theme name - SEO meta tags and social sharing
- `customCSS`, `customThemeFile` - Authentication page headers
- Legacy fields maintained for backward compatibility
### API Endpoints ### Site Description
- `GET /api/devtool/config/site-settings` - Retrieve current settings - **Purpose**: Provide descriptive text for SEO and social media sharing
- `POST /api/devtool/config/site-settings` - Update settings - **SEO Impact**: Used as meta description tag for search engines
- `POST /api/devtool/config/upload-file` - Upload files (logos, themes) - **Social Sharing**: Appears in social media link previews
- `POST /api/devtool/config/add-custom-theme` - Add custom theme to theme.css - **Character Limit**: Recommended 150-160 characters for optimal SEO
- **Multi-language Support**: Single description applies globally
### File Upload Locations ### Typography Configuration
- **Logo and Favicon files**: Saved to `public/uploads/site-settings/` - **Font Selection**: Choose from available system fonts
- **Theme CSS files**: Saved to `assets/style/css/` directory - **Font Source**: Local fonts vs. web fonts (Google Fonts integration)
- **Custom themes**: Added directly to `assets/style/css/base/theme.css` - **Font Size Controls**: Configurable sizes for different elements
- **Global Application**: Typography changes apply system-wide
### Composable ## Branding & Visual Identity
The `useSiteSettings()` composable provides:
- `siteSettings` - Reactive settings object
- `loadSiteSettings()` - Load settings from API
- `updateSiteSettings()` - Update settings
- `setTheme()` - Set theme using existing theme system
- `getCurrentTheme()` - Get current theme
- `applyThemeSettings()` - Apply theme changes to DOM
- `updateGlobalMeta()` - Update global meta tags and SEO
- `addCustomThemeToFile()` - Add custom theme to theme.css
### Global Integration ### Logo Management System
The site settings are globally integrated across:
#### Header Component #### Site Logo (Primary)
- Uses site settings for logo and name display - **Usage**: Main application logo in header and navigation
- Theme selection dropdown uses same system as site settings - **Recommended Dimensions**: 200x60px (maximum)
- Synced with site settings theme selection - **Supported Formats**: PNG, JPG, SVG (vector preferred)
- **File Size Limit**: 5MB maximum
- **Responsive Behavior**: Automatic scaling for different screen sizes
- **Fallback**: System default logo if custom logo fails to load
#### Loading Component #### Loading Logo
- Uses site logo if available, fallback to default - **Usage**: Branded loading screens and splash pages
- Displays site name in loading screen - **Recommended Dimensions**: 100x100px (square preferred)
- **Animation Support**: Static images only (CSS animations can be added)
- **Multiple Screens**: Applied to all loading states throughout application
- **Brand Consistency**: Should complement primary site logo
#### App.vue #### Favicon Configuration
- Global meta tags updated from site settings - **Usage**: Browser tab icons, bookmarks, mobile home screen icons
- Title, description, and favicon managed globally - **Required Format**: ICO format preferred (PNG acceptable)
- Theme initialization from site settings - **Dimensions**: 16x16, 32x32, 48x48px (multi-size ICO recommended)
- **Mobile Icons**: Apple touch icons and Android home screen support
- **Browser Compatibility**: Cross-browser favicon support
#### SEO and Meta Tags #### Login Page Logo
- Document title updated globally - **Usage**: Dedicated branding for authentication and login pages
- Meta descriptions for SEO - **Design Consideration**: Can be different from main site logo for branding flexibility
- Open Graph tags for social sharing - **Recommended Dimensions**: 250x80px maximum
- Twitter Card tags - **Security Context**: Appears on security-sensitive pages
- Favicon and apple-touch-icon - **Brand Trust**: Important for user trust and brand recognition
### Theme System Integration ### File Upload System
- Integrates with existing theme system (themeList.js, themeList2.js) - **Secure Upload**: File validation and virus scanning
- Theme selection in header dropdown synced with site settings - **Storage Location**: `public/uploads/site-settings/` directory
- Custom themes can be added directly to theme.css - **Backup Integration**: Uploaded files included in system backups
- Backward compatibility with existing theme structure - **Version Control**: Maintain previous versions of uploaded assets
- **CDN Support**: Compatible with content delivery networks
### Custom Theme Structure ## SEO & Meta Tag Management
Custom themes added to theme.css should follow this structure:
### Search Engine Optimization
#### Basic SEO Settings
- **SEO Title**: Custom title for search engine results
- **Meta Description**: Description displayed in search results
- **Meta Keywords**: Keyword tags (legacy but still supported)
- **Meta Author**: Content author information
- **Canonical URL**: Preferred URL for content indexing
#### Advanced SEO Configuration
- **Robots Meta Tag**: Control search engine crawling behavior
- Default: "index, follow" (allow indexing and link following)
- Options: noindex, nofollow, noarchive, nosnippet
- **Structured Data**: Schema.org markup for rich snippets
- **XML Sitemap**: Automatic sitemap generation and submission
- **Page Speed Optimization**: Settings that impact page load times
### Social Media Integration
#### Open Graph (Facebook)
- **og:title**: Social media sharing title
- **og:description**: Social media sharing description
- **og:image**: Image displayed in social media previews
- **og:type**: Content type (website, article, etc.)
- **og:url**: Canonical URL for social sharing
#### Twitter Cards
- **twitter:card**: Card type (summary, summary_large_image, app, player)
- **twitter:title**: Twitter-specific sharing title
- **twitter:description**: Twitter-specific sharing description
- **twitter:image**: Twitter preview image
- **twitter:creator**: Twitter handle of content creator
### Analytics & Tracking
#### Google Analytics Integration
- **Tracking ID**: Google Analytics measurement ID (GA4 format)
- **Enhanced Ecommerce**: Advanced tracking for document interactions
- **Custom Events**: Track document downloads, views, searches
- **Privacy Compliance**: GDPR and privacy regulation compliance
#### Google Tag Manager
- **Container ID**: GTM container identifier
- **Custom Variables**: Document metadata as GTM variables
- **Event Tracking**: Advanced event tracking through GTM
- **Third-party Integration**: Easy integration with other analytics tools
#### Facebook Pixel
- **Pixel ID**: Facebook advertising pixel identifier
- **Conversion Tracking**: Track document interactions as conversions
- **Custom Audiences**: Build audiences based on document engagement
- **Privacy Controls**: Respect user privacy preferences
## Theme System Integration
### Built-in Theme Support
#### Standard Themes
The system includes several pre-built themes from `themeList.js`:
- **biasa**: Default neutral theme with balanced colors
- **terang**: Light theme with bright, clean aesthetics
- **gelap**: Dark theme for reduced eye strain
- **biru**: Blue-dominant professional theme
- **hijau**: Green nature-inspired theme
#### Accessibility Themes
Special themes from `themeList2.js` for enhanced accessibility:
- **high-contrast**: High contrast for visual impairments
- **large-text**: Increased font sizes for readability
- **color-blind**: Color-blind friendly color palettes
- **low-vision**: Optimized for users with low vision
### Custom Theme Development
#### Theme Structure
Custom themes follow CSS custom property structure:
```css ```css
html[data-theme="your-theme-name"] { html[data-theme="custom-theme-name"] {
--color-primary: 255, 0, 0; --color-primary: 255, 0, 0;
--color-secondary: 0, 255, 0; --color-secondary: 0, 255, 0;
--color-success: 0, 255, 0; --color-success: 0, 255, 0;
--color-info: 0, 0, 255; --color-info: 0, 0, 255;
--color-warning: 255, 255, 0; --color-warning: 255, 255, 0;
--color-danger: 255, 0, 0; --color-danger: 255, 0, 0;
/* Add your theme variables here */ --color-light: 248, 249, 250;
--color-dark: 33, 37, 41;
/* Additional custom properties */
} }
``` ```
## Default Values #### Color Variable System
If no settings are configured, the system uses these defaults: - **RGB Values**: Colors defined as RGB triplets for alpha transparency support
- Site Name: "corradAF" - **Semantic Naming**: Colors named by purpose rather than appearance
- Site Description: "corradAF Base Project" - **Component Integration**: Variables used throughout Rs component system
- Selected Theme: "biasa" - **Dark Mode Support**: Automatic dark mode variants
- Logo: Default corradAF logo
- Favicon: Default favicon
## Migration Notes #### Theme File Management
- Legacy color fields (primaryColor, secondaryColor, etc.) are maintained for backward compatibility - **Upload Location**: Custom themes saved to `assets/style/css/`
- `themeMode` field is mapped to `selectedTheme` for compatibility - **Integration Method**: Direct injection into `theme.css` file
- Existing installations will automatically use default values - **Validation**: CSS syntax validation before integration
- Theme selection integrates with existing theme dropdown in header - **Backup**: Automatic backup before theme modifications
## Notes ### Theme Switching Mechanism
- Changes are applied immediately in the preview - **Real-time Application**: Themes applied immediately without page refresh
- Theme changes affect the entire application - **Header Integration**: Theme selector synchronized with site settings
- Custom CSS is injected into the document head - **User Preferences**: Individual user theme preferences (if enabled)
- Theme files are saved to `/assets/style/css/` for proper integration - **Default Theme**: System-wide default theme setting
- File uploads are validated for type and size
- Settings persist across browser sessions
- Site name and description updates are reflected globally and immediately
- All meta tags and SEO elements are automatically updated
- Logo changes are reflected in all components that use site settings
### Important Notes ## Advanced Customization
- Changes are applied immediately in the preview
- Theme changes affect the entire application ### Custom CSS Injection
- Custom CSS is injected into the document head
- Theme files are saved to `/assets/style/css/` for proper integration #### Global CSS Override
- File uploads are validated for type and size - **Injection Point**: CSS inserted into document `<head>` element
- Settings persist across browser sessions - **Priority**: Custom CSS has high specificity to override defaults
- Site name and description updates are reflected globally and immediately - **Validation**: Basic CSS syntax validation
- All meta tags and SEO elements are automatically updated - **Performance**: Minification and optimization of injected CSS
- Logo changes are reflected in all components that use site settings
#### Best Practices for Custom CSS
- **Specificity**: Use appropriate CSS specificity for overrides
- **Performance**: Minimize custom CSS for better performance
- **Maintenance**: Document custom CSS changes for future reference
- **Testing**: Test across different browsers and devices
#### CSS Framework Integration
- **TailwindCSS Compatibility**: Custom CSS works alongside TailwindCSS
- **Component System**: Respect Rs component styling patterns
- **Responsive Design**: Ensure custom CSS is responsive
- **Dark Mode**: Consider dark mode implications
### Custom Theme File Upload
#### File Requirements
- **Format**: Standard CSS files (.css extension)
- **Size Limit**: 1MB maximum file size
- **Encoding**: UTF-8 encoding required
- **Syntax**: Valid CSS syntax required
#### Integration Process
1. **File Upload**: Secure file upload to server
2. **Validation**: CSS syntax and security validation
3. **Integration**: Append to main theme.css file
4. **Backup**: Create backup of previous theme.css
5. **Application**: Theme immediately available for selection
#### Security Considerations
- **Content Filtering**: Remove potentially dangerous CSS
- **Path Restrictions**: Prevent access to restricted file paths
- **Size Limits**: Prevent resource exhaustion attacks
- **Validation**: Comprehensive CSS validation
## API Integration
### Site Settings API Endpoints
#### GET `/api/devtool/config/site-settings`
**Purpose**: Retrieve current site settings configuration
**Response Format**:
```json
{
"statusCode": 200,
"message": "Success",
"data": {
"siteName": "Custom EDMS",
"siteDescription": "Enterprise Document Management System",
"siteLogo": "/uploads/site-settings/logo.png",
"siteFavicon": "/uploads/site-settings/favicon.ico",
"selectedTheme": "professional-blue",
"customCSS": ".custom-header { background: #blue; }",
"seoTitle": "EDMS - Document Management",
"seoDescription": "Comprehensive document management solution",
"seoKeywords": "document, management, enterprise",
"seoGoogleAnalytics": "GA-XXXXXXXXX-X"
}
}
```
#### POST `/api/devtool/config/site-settings`
**Purpose**: Update site settings configuration
**Request Format**:
```json
{
"siteName": "Updated EDMS Name",
"siteDescription": "Updated description",
"selectedTheme": "dark-professional",
"customCSS": ".updated-styles { color: red; }",
"seoTitle": "Updated SEO Title"
}
```
**Features**:
- Partial updates supported (only changed fields required)
- Automatic validation of all input data
- File path validation for security
- Database persistence with timestamps
#### POST `/api/devtool/config/upload-file`
**Purpose**: Secure file upload for logos and assets
**Request Format**: Multipart form data with file and metadata
**Response Format**:
```json
{
"statusCode": 200,
"message": "File uploaded successfully",
"data": {
"filename": "uploaded-logo.png",
"path": "/uploads/site-settings/logo.png",
"size": 15420,
"type": "image/png"
}
}
```
**Security Features**:
- File type validation (images only for logos)
- File size limits enforcement
- Virus scanning integration
- Secure file storage with proper permissions
#### POST `/api/devtool/config/add-custom-theme`
**Purpose**: Add custom theme directly to theme.css file
**Request Format**:
```json
{
"themeName": "custom-corporate",
"themeCSS": "html[data-theme=\"custom-corporate\"] { --color-primary: 0, 100, 200; }"
}
```
**Features**:
- CSS validation before integration
- Automatic backup of existing theme.css
- Immediate availability after integration
- Rollback capability if integration fails
### Composable Integration
#### useSiteSettings() Composable
The `useSiteSettings()` composable provides reactive state management:
```javascript
const {
siteSettings, // Reactive settings object
loading, // Loading state
loadSiteSettings, // Load settings from API
updateSiteSettings, // Update settings
setTheme, // Apply theme changes
getCurrentTheme, // Get current theme
applyThemeSettings, // Apply theme to DOM
updateGlobalMeta, // Update meta tags
addCustomThemeToFile // Add custom theme
} = useSiteSettings();
```
#### Real-time Updates
- **Reactive State**: Changes immediately reflected in UI
- **Global Sync**: Updates synchronized across all components
- **Meta Tag Updates**: SEO meta tags updated dynamically
- **Theme Application**: Theme changes applied without page refresh
## Technical Implementation
### Database Schema
Settings stored in `site_settings` table with comprehensive field structure:
```sql
CREATE TABLE site_settings (
settingID INT PRIMARY KEY AUTO_INCREMENT,
siteName VARCHAR(255) DEFAULT 'corradAF',
siteNameFontSize INT DEFAULT 18,
siteDescription TEXT,
siteLogo VARCHAR(500),
siteLoadingLogo VARCHAR(500),
siteFavicon VARCHAR(500),
siteLoginLogo VARCHAR(500),
showSiteNameInHeader BOOLEAN DEFAULT TRUE,
customCSS LONGTEXT,
themeMode VARCHAR(100) DEFAULT 'biasa',
customThemeFile VARCHAR(500),
currentFont VARCHAR(100),
fontSource VARCHAR(100),
seoTitle VARCHAR(255),
seoDescription TEXT,
seoKeywords TEXT,
seoAuthor VARCHAR(255),
seoOgImage VARCHAR(500),
seoTwitterCard VARCHAR(100) DEFAULT 'summary_large_image',
seoCanonicalUrl VARCHAR(500),
seoRobots VARCHAR(100) DEFAULT 'index, follow',
seoGoogleAnalytics VARCHAR(255),
seoGoogleTagManager VARCHAR(255),
seoFacebookPixel VARCHAR(255),
settingCreatedDate DATETIME DEFAULT CURRENT_TIMESTAMP,
settingModifiedDate DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```
### File System Integration
- **Upload Directory**: `public/uploads/site-settings/`
- **Theme Directory**: `assets/style/css/`
- **Backup Directory**: `backups/site-settings/`
- **Permission Management**: Proper file system permissions
- **Cleanup Procedures**: Automatic cleanup of unused files
### Global Integration Points
#### Header Component
- **Logo Display**: Dynamic logo from site settings
- **Site Name**: Configurable site name display
- **Theme Selector**: Synchronized theme selection
- **Brand Colors**: Applied from theme settings
#### App.vue Global Configuration
- **Meta Tags**: Dynamically updated from settings
- **Title Management**: Global title prefix from site name
- **Theme Initialization**: Theme applied on app startup
- **Favicon**: Dynamic favicon from settings
#### Loading Components
- **Branded Loading**: Custom loading logo
- **Brand Colors**: Theme colors in loading screens
- **Consistent Experience**: Uniform branding across all loading states
## Best Practices
### Branding Guidelines
#### Logo Design
- **Vector Format**: Use SVG for scalability when possible
- **Optimization**: Optimize file sizes for web performance
- **Consistency**: Maintain consistent branding across all logos
- **Accessibility**: Ensure sufficient contrast for accessibility compliance
#### Color Schemes
- **Brand Consistency**: Align with organizational brand guidelines
- **Accessibility**: Meet WCAG contrast requirements
- **Cultural Sensitivity**: Consider cultural color associations
- **Dark Mode Support**: Provide appropriate dark mode variants
### SEO Optimization
#### Content Strategy
- **Unique Descriptions**: Unique meta descriptions for better SEO
- **Keyword Research**: Use relevant keywords in meta tags
- **Regular Updates**: Keep SEO content current and relevant
- **Performance**: Optimize for page load speed
#### Technical SEO
- **Structured Data**: Implement appropriate schema markup
- **Mobile Optimization**: Ensure mobile-friendly configuration
- **Page Speed**: Monitor and optimize loading times
- **Security**: Implement HTTPS and security headers
### Performance Considerations
#### File Optimization
- **Image Compression**: Compress logos and images appropriately
- **CSS Minification**: Minify custom CSS for better performance
- **Caching Strategy**: Implement appropriate caching headers
- **CDN Integration**: Consider CDN for static assets
#### Theme Performance
- **Minimal CSS**: Keep custom themes lightweight
- **Variable Usage**: Use CSS custom properties efficiently
- **Browser Compatibility**: Test across different browsers
- **Fallback Support**: Provide fallbacks for older browsers
### Security Best Practices
#### File Upload Security
- **File Type Validation**: Strict file type checking
- **Size Limits**: Appropriate file size restrictions
- **Virus Scanning**: Implement virus scanning for uploads
- **Path Traversal**: Prevent directory traversal attacks
#### CSS Security
- **Content Filtering**: Filter potentially dangerous CSS
- **XSS Prevention**: Prevent CSS-based XSS attacks
- **Input Validation**: Validate all CSS input thoroughly
- **Sanitization**: Sanitize user-provided CSS content
## Troubleshooting
### Common Issues
#### Settings Not Saving
**Symptoms**:
- Changes revert after page refresh
- Error messages during save operations
- Loading indicators that don't complete
**Resolution Steps**:
1. **Check Permissions**: Verify administrative access
2. **Network Issues**: Confirm stable internet connection
3. **Browser Console**: Check for JavaScript errors
4. **Server Status**: Verify server is operational
5. **Database Connection**: Check database connectivity
#### Logos Not Displaying
**Symptoms**:
- Broken image icons instead of logos
- Default logos showing instead of custom ones
- Images not loading properly
**Resolution Steps**:
1. **File Path**: Verify correct file paths in settings
2. **File Permissions**: Check server file permissions
3. **File Format**: Ensure supported image formats
4. **File Size**: Check if files exceed size limits
5. **Browser Cache**: Clear browser cache and reload
#### Theme Not Applying
**Symptoms**:
- Theme selection not taking effect
- Colors not changing as expected
- Custom themes not available
**Resolution Steps**:
1. **CSS Validation**: Check for CSS syntax errors
2. **Theme File**: Verify theme file integration
3. **Browser Compatibility**: Test with different browsers
4. **Cache Issues**: Clear browser and server cache
5. **CSS Conflicts**: Check for CSS specificity issues
### Performance Issues
#### Slow Loading
**Causes**:
- Large logo files
- Excessive custom CSS
- Network connectivity issues
- Server performance problems
**Optimization**:
1. **Image Optimization**: Compress logo files
2. **CSS Optimization**: Minimize custom CSS
3. **Caching**: Implement proper caching strategies
4. **CDN**: Use content delivery networks
5. **Server Optimization**: Monitor server performance
#### Memory Issues
**Symptoms**:
- Browser becoming unresponsive
- High memory usage
- Slow interface response
**Resolution**:
1. **File Size**: Reduce logo file sizes
2. **CSS Efficiency**: Optimize CSS selectors
3. **Browser Update**: Use latest browser versions
4. **System Resources**: Monitor system memory
5. **Cache Management**: Regular cache clearing
### Support Resources
#### Documentation
- **Technical Guide**: Comprehensive system documentation
- **User Guide**: End-user documentation
- **API Documentation**: Developer integration guides
- **Change Log**: System updates and changes
#### Contact Support
- **Internal IT**: Contact system administrators
- **Help Desk**: Organizational support procedures
- **Community**: User forums and knowledge bases
- **Vendor Support**: External support options
#### Information to Provide
When reporting issues:
- **Error Messages**: Exact error text
- **Browser Information**: Browser type and version
- **Steps to Reproduce**: Detailed reproduction steps
- **Screenshots**: Visual documentation of issues
- **System Details**: Relevant configuration information
This comprehensive guide provides complete coverage of the Site Settings system, enabling administrators to effectively customize and manage the visual appearance and global configuration of the EDMS application.

File diff suppressed because it is too large Load Diff

View File

@ -4,13 +4,13 @@
1. [Introduction](#introduction) 1. [Introduction](#introduction)
2. [Getting Started](#getting-started) 2. [Getting Started](#getting-started)
3. [Navigation](#navigation) 3. [Navigation](#navigation)
4. [Document Organization](#document-organization) 4. [DMS Interface](#dms-interface)
5. [Working with Documents](#working-with-documents) 5. [Access Level System](#access-level-system)
6. [Access Control](#access-control) 6. [Document Organization](#document-organization)
7. [Search and Filtering](#search-and-filtering) 7. [Working with Documents](#working-with-documents)
8. [Document Viewer](#document-viewer) 8. [DMS Settings (Administrators)](#dms-settings-administrators)
9. [My Documents](#my-documents) 9. [Search and Filtering](#search-and-filtering)
10. [Settings Administration](#settings-administration) 10. [Document Viewer](#document-viewer)
11. [Troubleshooting](#troubleshooting) 11. [Troubleshooting](#troubleshooting)
## Introduction ## Introduction
@ -18,9 +18,10 @@
The Electronic Document Management System (EDMS) is a modern web-based platform designed to efficiently organize, store, and manage digital documents in organizational environments. The system provides a secure, hierarchical structure for document storage with comprehensive role-based access control and advanced document management capabilities. The Electronic Document Management System (EDMS) is a modern web-based platform designed to efficiently organize, store, and manage digital documents in organizational environments. The system provides a secure, hierarchical structure for document storage with comprehensive role-based access control and advanced document management capabilities.
### Key Features ### Key Features
- **Hierarchical Organization**: Documents are organized in a Cabinet → Drawer → Folder → Subfolder structure - **Access Level Organization**: Documents categorized as All, Public, Private, and Personal with color-coded tabs
- **Hierarchical Structure**: Documents organized in a Cabinet → Drawer → Folder → Subfolder structure
- **Multiple View Modes**: List, Grid, and Details views for browsing documents - **Multiple View Modes**: List, Grid, and Details views for browsing documents
- **Access Control**: Public, Private, and Personal document categories with granular permission management - **Advanced Settings Management**: Comprehensive configuration system for administrators
- **Document Viewer**: Built-in viewer supporting multiple file formats (PDF, images, text files, spreadsheets) - **Document Viewer**: Built-in viewer supporting multiple file formats (PDF, images, text files, spreadsheets)
- **Search Functionality**: Advanced search across document titles, descriptions, and metadata - **Search Functionality**: Advanced search across document titles, descriptions, and metadata
- **Upload Management**: Drag-and-drop file upload with comprehensive metadata assignment - **Upload Management**: Drag-and-drop file upload with comprehensive metadata assignment
@ -55,27 +56,32 @@ The main dashboard provides:
### Main Navigation Menu ### Main Navigation Menu
Access the EDMS through the main navigation menu: Access the EDMS through the main navigation menu:
- **Dashboard**: System overview and quick access to recent items - **Dashboard**: System overview and quick access to recent items
- **DMS**: Main document management interface - **DMS**: Main document management interface with access level tabs
- **My Documents**: Personal document collection and workspace - **DMS Settings**: Administrative configuration panel (admin only)
- **Upload Document**: Dedicated document upload interface - **ERD**: Entity Relationship Diagram viewer for system structure
### EDMS Interface Layout ### EDMS Interface Layout
The EDMS interface consists of three main areas: The EDMS interface consists of several main areas:
1. **Tree Navigation (Left Panel)** 1. **Access Level Tabs (Top)**
- **All Documents**: Complete view of all accessible documents (blue folder icon)
- **Public**: Documents available to all users (green unlock icon)
- **Private**: Restricted documents requiring special permissions (red lock icon)
- **Personal**: Individual user documents (purple user icon)
2. **Tree Navigation (Left Panel - when available)**
- Hierarchical view of cabinets, drawers, and folders - Hierarchical view of cabinets, drawers, and folders
- Expandable/collapsible folder structure - Expandable/collapsible folder structure
- Color-coded access indicators for different permission levels - Color-coded access indicators for different permission levels
- Lock icons for restricted items requiring special access - Lock icons for restricted items requiring special access
2. **Content Area (Center)** 3. **Content Area (Center)**
- Document listing with multiple view modes (List, Grid, Details) - Document listing with multiple view modes (List, Grid, Details)
- Breadcrumb navigation showing current location - Breadcrumb navigation showing current location
- Search and filter tools for finding documents - Search and filter tools for finding documents
- View mode controls and sorting options - View mode controls and sorting options
- Document tabs for filtering by access type
3. **Details Panel (Optional Right Panel)** 4. **Details Panel (Optional Right Panel)**
- Document properties and metadata information - Document properties and metadata information
- Quick actions and document tools - Quick actions and document tools
- Access control information and permissions - Access control information and permissions
@ -87,6 +93,82 @@ The EDMS interface consists of three main areas:
- Helps maintain orientation in deep folder structures - Helps maintain orientation in deep folder structures
- Displays full path from root to current location - Displays full path from root to current location
## DMS Interface
### Access Level Tabs
The main DMS interface features a modern tabbed design for easy navigation between different document categories:
#### All Documents Tab
- **Icon**: Blue folder icon
- **Purpose**: Shows all documents you have permission to access
- **Color Scheme**: Blue theme with active indicator
- **Use Case**: Comprehensive view for finding any accessible document
#### Public Tab
- **Icon**: Green unlock icon
- **Purpose**: Displays documents accessible to all system users
- **Color Scheme**: Green theme indicating open access
- **Use Case**: Company policies, public announcements, shared resources
#### Private Tab
- **Icon**: Red lock icon
- **Purpose**: Shows restricted documents requiring special permissions
- **Color Scheme**: Red theme indicating restricted access
- **Use Case**: Confidential documents, sensitive information, executive files
#### Personal Tab
- **Icon**: Purple user icon
- **Purpose**: Displays your personal documents and private workspace
- **Color Scheme**: Purple theme indicating personal ownership
- **Use Case**: Individual workspace, draft documents, personal files
### Visual Design Features
- **Active Tab Indicators**: Current tab highlighted with "Active" badge
- **Hover Effects**: Smooth transitions when hovering over tabs
- **Loading States**: Visual feedback during system operations
- **Error Handling**: Clear error messages with retry options
## Access Level System
### Understanding Access Levels
#### Public Documents
- **Accessibility**: Visible and accessible to all system users
- **Visual Indicator**: Green color theme with unlock icon
- **Permissions**: No special permissions or approvals required
- **Best For**:
- Company policies and procedures
- Public announcements
- Shared templates and forms
- General information documents
#### Private Documents
- **Accessibility**: Restricted access requiring specific permissions
- **Visual Indicator**: Red color theme with lock icon
- **Permissions**: May require access request and approval workflow
- **Best For**:
- Confidential business information
- HR records and sensitive data
- Financial documents
- Executive communications
- Legal documents
#### Personal Documents
- **Accessibility**: Documents owned and controlled by individual users
- **Visual Indicator**: Purple color theme with user icon
- **Permissions**: Full control over sharing and access
- **Best For**:
- Individual workspace documents
- Draft documents before publication
- Personal notes and references
- Work-in-progress files
### Access Level Switching
- Click any access level tab to filter documents by that category
- Visual feedback shows which tab is currently active
- Document count may vary between tabs based on your permissions
- Some tabs may be empty if no documents exist in that category
## Document Organization ## Document Organization
### Hierarchical Structure ### Hierarchical Structure
@ -106,8 +188,9 @@ Documents are organized in a four-level hierarchy designed for maximum flexibili
### Visual Indicators ### Visual Indicators
The system uses color coding and icons to indicate access levels: The system uses color coding and icons to indicate access levels:
- **Green**: Public documents (open access for all users) - **Green**: Public documents (open access for all users)
- **Blue**: Personal documents (owned by you) - **Blue**: All accessible documents (comprehensive view)
- **Red**: Private documents (restricted access) - **Red**: Private documents (restricted access)
- **Purple**: Personal documents (owned by you)
- **Lock Icon**: Documents you cannot currently access - **Lock Icon**: Documents you cannot currently access
- **Template Icon**: Document templates available for use - **Template Icon**: Document templates available for use
@ -117,7 +200,7 @@ The system uses color coding and icons to indicate access levels:
#### Single File Upload #### Single File Upload
1. Navigate to the desired location in the hierarchy 1. Navigate to the desired location in the hierarchy
2. Click the "Upload" button or use the dedicated "Upload Document" page 2. Click the "Upload" button or use the dedicated upload interface
3. Select file(s) using the file picker or drag-and-drop interface 3. Select file(s) using the file picker or drag-and-drop interface
4. Fill in comprehensive document metadata: 4. Fill in comprehensive document metadata:
- Document title and description - Document title and description
@ -136,16 +219,17 @@ The system uses color coding and icons to indicate access levels:
5. Review and confirm successful uploads 5. Review and confirm successful uploads
### Document Metadata ### Document Metadata
For each document, you can specify comprehensive metadata: For each document, you can specify comprehensive metadata based on system configuration:
- **Title**: Descriptive document name/title - **Title**: Descriptive document name/title
- **Description**: Brief summary of document content and purpose - **Description**: Brief summary of document content and purpose
- **Keywords**: Search terms for improved discoverability (comma-separated) - **Keywords**: Search terms for improved discoverability (comma-separated)
- **Category**: Document type (Technical Specification, Contract, Report, Policy, etc.) - **Category**: Document type (configurable in DMS settings)
- **Status**: Current state (Draft, Under Review, Approved, Archived, etc.) - **Status**: Current state (Draft, Under Review, Approved, Archived, etc.)
- **Department**: Responsible department or team - **Department**: Responsible department or team
- **Access Level**: Determines who can view and interact with the document - **Access Level**: Determines who can view and interact with the document
- **Retention Period**: How long the document should be retained - **Retention Period**: How long the document should be retained
- **Version**: Document version number and change notes - **Version**: Document version number and change notes
- **Custom Fields**: Additional metadata fields as configured by administrators
### Viewing Documents ### Viewing Documents
1. Click on any document to view its details and metadata 1. Click on any document to view its details and metadata
@ -158,392 +242,218 @@ For each document, you can specify comprehensive metadata:
- **Move Documents**: Drag and drop to reorganize into different locations - **Move Documents**: Drag and drop to reorganize into different locations
- **Delete Documents**: Remove documents with confirmation and audit trail - **Delete Documents**: Remove documents with confirmation and audit trail
- **Copy Documents**: Duplicate documents to multiple locations - **Copy Documents**: Duplicate documents to multiple locations
- **Version Control**: Track document versions and view change history - **Version Control**: Track document versions and view change history (if enabled)
- **Share Documents**: Generate sharing links and manage access permissions - **Share Documents**: Generate sharing links and manage access permissions
## Access Control ## DMS Settings (Administrators)
### Understanding Access Levels
#### Public Documents
- Visible and accessible to all system users
- No special permissions or approvals required
- Green color indicator for easy identification
- Suitable for general information and policies
#### Private Documents
- Restricted access requiring specific permissions
- Red color indicator with lock icon
- May require access request and approval workflow
- Ideal for sensitive or confidential information
#### Personal Documents
- Documents owned and controlled by individual users
- Blue color indicator showing ownership
- Full control over sharing and permissions
- Private workspace for individual document management
### Requesting Access
When you encounter a private document you cannot access:
1. **Identify Restricted Item**: Look for red color coding and lock icon
2. **Initiate Access Request**: Click the "Request Access" button
3. **Complete Request Form**:
- **Document Information**: Pre-filled with document title and file details
- **Access Type**: Choose appropriate permission level:
- View Only: Read-only access to document content
- Download: View and download permissions for offline access
- Print: View and print permissions for physical copies
- Full Access: Complete access rights including editing
- **Access Duration**: Select appropriate timeframe:
- 7 days, 14 days, 30 days, 60 days, 90 days, or Permanent
- **Justification**: Detailed explanation of why access is needed (required field)
4. **Submit Request**: Click "Submit Request" to send for approval
5. **Monitor Status**: Track request status and wait for approval from document owner or administrator
### Access Request Status
- **Pending**: Request submitted and awaiting review by approvers
- **Approved**: Access granted for specified duration and permission level
- **Rejected**: Request denied with explanation from approver
- **Expired**: Previously approved access has reached its expiration date
## Search and Filtering
### Basic Search
1. Use the search box in the top navigation bar
2. Enter keywords, document titles, or content descriptions
3. Press Enter or click the search icon to execute search
4. Results display with matching documents and highlighted search terms
5. Refine results using additional filters and sorting options
### Advanced Search Options
- **Filter by Type**: Documents, folders, cabinets, or specific file formats
- **Filter by Category**: Technical, Administrative, Financial, HR, etc.
- **Filter by Date Range**: Creation date, modification date, or access date
- **Filter by Owner**: Documents by specific users or departments
- **Filter by Access Level**: Public, private, personal, or template documents
- **Filter by Status**: Draft, approved, archived, or under review
- **Filter by File Format**: PDF, Word, Excel, images, etc.
### Search Tips for Better Results
- Use quotation marks for exact phrase matching
- Combine multiple keywords with spaces for broader results
- Search includes document titles, descriptions, metadata, and content (where supported)
- Use wildcards (*) for partial word matching
- Utilize Boolean operators (AND, OR, NOT) for complex searches
### Document Tabs for Quick Filtering
Filter documents by category using convenient tabs:
- **All**: Display all accessible documents in current location
- **Public**: Show only public documents available to all users
- **Private**: Display private documents you have access to
- **Personal**: Show your personal documents and workspace items
## Document Viewer
### Supported File Formats
The built-in document viewer supports multiple formats:
- **PDF Documents**: Full preview with zoom, navigation, and text selection
- **Images**: JPG, PNG, GIF, BMP with zoom and pan functionality
- **Text Files**: TXT, RTF with formatted preview and search
- **Microsoft Office**: DOC, DOCX, XLS, XLSX, PPT, PPTX (basic preview)
- **Other Formats**: Download option provided for unsupported formats
### Viewer Features and Controls
- **Zoom Controls**: Zoom in/out from 25% to 400% for optimal readability
- **Page Navigation**: Navigate through multi-page documents with thumbnails
- **Full Screen Mode**: Maximize viewer for immersive document experience
- **Download Function**: Save document locally for offline access
- **Print Support**: Print document directly from viewer interface
- **Search in Document**: Find specific text within document content
- **Rotation Controls**: Rotate document pages for better viewing
### Viewer Interface Controls
- **Zoom Options**: 25%, 50%, 75%, 100%, 125%, 150%, 200%, Fit to Width, Fit to Page
- **Navigation**: Previous/Next page buttons, page number input, thumbnail sidebar
- **View Options**: Single page, continuous scroll, two-page spread
- **Tools**: Text selection, annotation tools (if enabled), measurement tools
- **Close**: Return to document list or previous view
## My Documents
### Personal Document Workspace
The "My Documents" section provides a personalized workspace showing:
- Documents you've created and own
- Documents you've recently accessed or modified
- Documents shared with you by other users
- Recent document activity and version history
- Personal folders and organizational structure
### Organization and Management Features
- **Quick Search**: Search specifically within your personal documents
- **Sort Options**: Sort by name, date created, date modified, file type, or size
- **View Modes**: Switch between list, grid, or detailed view layouts
- **Bulk Actions**: Select and manage multiple documents simultaneously
- **Folder Creation**: Create personal folders for document organization
- **Favorites**: Mark frequently used documents for quick access
### Personal Document Statistics and Insights
View comprehensive information about your document usage:
- Total number of documents owned and accessible
- Storage space used and available quota
- Recent upload activity and document creation trends
- Document categories and type distribution
- Most accessed documents and usage patterns
## Settings Administration
### Overview ### Overview
The EDMS Settings module provides administrators with comprehensive control over system configuration and behavior. Access to settings is restricted to users with administrative privileges and affects system-wide operations. Administrators have access to comprehensive DMS settings through the **DMS Settings** page, allowing complete customization of the document management system behavior.
### Accessing Settings ### Accessing DMS Settings
1. Navigate to **DMS****Settings** from the main navigation menu 1. Navigate to **DMS****Settings** in the main menu
2. Select a settings category from the left sidebar navigation 2. Requires administrative privileges to access
3. Configure settings within each category using standardized form components 3. Settings are organized into six main categories with tabbed navigation
4. Save changes to apply configurations across the system
### Settings Categories ### Settings Categories
The EDMS settings are organized into 5 core categories for streamlined administration: #### 1. User & Access Management 🔐
- **User Roles**: Define and manage custom user roles (Admin, Editor, Viewer, Uploader, etc.)
- **Access Permissions**: Configure granular permissions for view, edit, delete, download, and share
- **Authentication Settings**:
- Enable/disable Single Sign-On (SSO)
- Require Multi-Factor Authentication (MFA)
- LDAP/Active Directory integration
- Session timeout configuration (1-24 hours)
#### 🔐 User & Access Management #### 2. Document & Folder Settings 📁
Configure user permissions, roles, and authentication settings: - **Naming Conventions**:
- Auto-generate document names
- Configure naming patterns (e.g., `{department}_{title}_{date}`)
- Set mandatory fields for document metadata
- **Version Control**:
- Enable/disable document versioning
- Set maximum number of versions to retain
- Configure automatic versioning behavior
**User Roles & Permissions** #### 3. Metadata & Tagging 📝
- Define user roles (Admin, Editor, Viewer, Uploader) - **Custom Metadata Fields**:
- Set granular permissions for each role: - Add/remove custom fields (text, dropdown, date, number, multi-select)
- View Documents: Read-only access to document content - Set required fields for document uploads
- Edit Documents: Modify document metadata and properties - Configure field types and validation rules
- Delete Documents: Remove documents with audit trail - **Tagging System**:
- Download Documents: Save documents for offline access - Manage predefined tags (urgent, confidential, public, draft, final, etc.)
- Share Documents: Generate sharing links and manage access - Enable/disable user-generated tags
- Manage user groups by department or project - Configure tag suggestions and auto-completion
- Enable Role-Based Access Control (RBAC) system-wide
**Authentication Settings** #### 4. Workflow & Automation 🔄
- Single Sign-On (SSO) integration with external providers - **Approval Workflows**:
- Multi-Factor Authentication (MFA) enforcement - Enable/disable approval workflows
- LDAP/Active Directory integration for enterprise environments - Configure default approval flow (department-head-approval, legal-review, etc.)
- Session timeout configuration (1-24 hours) - Set up custom approval processes
- Password policies and security requirements - **Notifications**:
- Email notifications for document activities
- In-app notifications and alerts
- Upload alerts and deadline reminders
#### 📁 Document & Folder Settings #### 5. Upload & Storage Settings 📤
Configure document organization and structure: - **File Type Management**:
- Allowed file types (pdf, doc, docx, xls, xlsx, ppt, pptx, txt, jpg, png, etc.)
- Blocked file types (exe, bat, cmd, etc.)
- Configure MIME type validation
- **Storage Quotas**:
- Maximum file size limit (MB)
- Per-user storage quota
- Per-group storage quota
- Per-project storage quota
**Folder Hierarchy** #### 6. System Settings 📅
- Set maximum folder depth (1-10 levels) - **General Configuration**:
- Choose default folder structure templates: - System timezone (Asia/Kuala_Lumpur, UTC, etc.)
- Standard: General-purpose organization - Backup schedule (hourly, daily, weekly, monthly)
- Project-based: Project-focused hierarchy - System log level (debug, info, warning, error)
- Department-based: Organizational structure alignment - **System Maintenance**:
- Custom: User-defined structure - Enable/disable maintenance mode
- Configure folder creation permissions and restrictions - Configure automatic updates
- System monitoring and performance metrics
**Document Naming Conventions**
- Enable automatic document name generation
- Define mandatory metadata fields for document creation
- Set naming patterns using variables:
- `{department}`: Document department/owner
- `{title}`: Document title or name
- `{date}`: Creation or upload date
- `{project}`: Project code or identifier
- `{category}`: Document category/type
**Document Retention & Version Control**
- Enable document retention policies with configurable retention periods
- Set automatic archiving before deletion
- Configure version control system:
- Maximum versions to retain (1-50)
- Automatic versioning on document updates
- Version history tracking and comparison tools
#### 📝 Metadata & Tagging
Configure document metadata and classification:
**Custom Metadata Fields**
Define custom fields for enhanced document organization:
- Field types: Text, Dropdown, Date, Number, Multi-select
- Required field enforcement for document uploads
- Department-specific metadata schemas
- Dynamic field validation and formatting rules
**Predefined Metadata Fields**
- Department: Organizational unit assignment
- Priority: Document importance level (Low, Medium, High, Critical)
- Project Code: Project or initiative identifier
- Review Date: Scheduled review or expiration date
**Tagging System**
- Configure predefined tags: urgent, confidential, public, draft, final
- Enable user-generated tags for flexible categorization
- Tag suggestion system for consistent tagging
- Auto-classification based on:
- Keyword recognition in document content
- Department-based classification rules
- File type and format analysis
#### 📤 Upload & Storage Settings
Configure file upload restrictions and storage management:
**File Type Management**
- **Allowed File Types**: Specify permitted file formats
- Default: pdf, doc, docx, xls, xlsx, ppt, pptx, txt, jpg, png
- Configurable list for organizational requirements
- **Blocked File Types**: Prevent potentially harmful files
- Default: exe, bat, cmd (executable files)
- Customizable blacklist for security compliance
**File Size & Storage Quotas**
- Maximum file size per upload (typically 100MB)
- User storage quotas (default: 5GB per user)
- Group storage quotas (default: 50GB per group)
- Project storage quotas (default: 100GB per project)
- System-wide storage monitoring and alerts
**Storage Configuration**
- Storage type selection (Local, AWS S3, Azure, Google Cloud)
- Storage path configuration for file organization
- Backup settings and automated backup scheduling
- Compression settings for storage optimization
#### 📅 System Settings
Configure global system behavior and preferences:
**General System Configuration**
- **System Timezone**: Set default timezone for timestamps
- Options: Asia/Kuala_Lumpur, UTC, America/New_York, Europe/London
- Affects document timestamps, scheduling, and user interface
- **Backup Schedule**: Configure automated system backups
- Options: Hourly, Daily, Weekly, Monthly
- Affects data protection and recovery capabilities
- **Log Level**: Set system logging verbosity
- Options: Debug, Info, Warning, Error
- Affects troubleshooting and system monitoring
**System Monitoring & Maintenance**
- **Maintenance Mode**: Enable system-wide maintenance mode
- Displays maintenance message to users
- Restricts access during system updates
- **Automatic Updates**: Configure automatic system updates
- Security patches and feature updates
- Scheduled during low-usage periods
- **System Monitoring**: Enable performance monitoring
- Track system performance metrics
- Monitor user activity and system health
- Generate usage reports and analytics
### Settings Management Features ### Settings Management Features
#### Import/Export Configuration #### Save & Load Settings
- **Export Settings**: Save current configuration as JSON file - **Auto-save**: Settings are saved immediately when modified
- Backup settings before major changes - **Loading States**: Visual feedback during save operations
- Transfer settings between environments - **Error Handling**: Clear error messages if save fails
- Documentation and compliance purposes - **Success Confirmation**: Confirmation messages with auto-dismiss
- **Import Settings**: Load configuration from JSON file
- Restore previous configurations #### Import/Export Functionality
- Deploy settings across multiple systems - **Export Settings**: Download complete settings as JSON file for backup
- Standardize configurations across environments - **Import Settings**: Upload JSON file to restore settings
- **Settings Migration**: Easy transfer between environments
- **Backup Management**: Regular backups of configuration
#### Reset to Defaults #### Reset to Defaults
- **Category Reset**: Reset individual setting categories to default values - **One-click Reset**: Reset all settings to system defaults
- **System Reset**: Reset entire system configuration to factory defaults - **Confirmation Dialog**: Prevents accidental resets
- **Selective Reset**: Choose specific settings to reset while preserving others - **Selective Reset**: Reset individual setting categories (future enhancement)
- **Default Values**: Sensible defaults for all configuration options
#### Settings Validation #### Dynamic Field Management
- **Real-time Validation**: Form validation prevents invalid configurations - **Add Custom Fields**: Create new metadata fields with validation
- **Dependency Checking**: Settings are validated for interdependencies - **Remove Fields**: Delete unused custom fields
- **Conflict Resolution**: System identifies and resolves setting conflicts - **Reorder Fields**: Arrange custom fields in preferred order
- **Preview Mode**: Test settings before applying system-wide - **Field Types**: Support for text, dropdown, date, number, and multi-select fields
### Best Practices for Settings Management ### Configuration Best Practices
#### Security Considerations #### Security Settings
- Regularly review user permissions and access levels - Enable MFA for sensitive environments
- Enable MFA for administrative accounts - Configure appropriate session timeouts
- Use strong session timeout values for security - Set up proper user roles and permissions
- Regularly audit user roles and group memberships - Regularly review access control settings
#### Storage Management #### Storage Management
- Monitor storage quotas and usage patterns - Set realistic file size limits based on storage capacity
- Implement retention policies for compliance - Configure appropriate quotas for users and groups
- Regular backup verification and testing - Enable compression for large files when possible
- Plan for storage scaling based on usage growth - Set up regular backup schedules
#### System Performance #### Workflow Optimization
- Monitor log levels to balance debugging needs with performance - Configure approval workflows based on organizational structure
- Schedule maintenance during off-peak hours - Enable relevant notifications to keep users informed
- Regular cleanup of temporary files and old versions - Set up appropriate retention policies
- Performance monitoring for system optimization - Configure automatic tagging rules for efficiency
#### Documentation & Compliance ## Search and Filtering
- Document all configuration changes with justification
- Maintain change logs for audit purposes
- Regular export of settings for backup and documentation
- Review settings for compliance with organizational policies
### Troubleshooting Settings Issues ### Search Functionality
- **Global Search**: Search across all accessible documents
- **Tab-Specific Search**: Search within specific access level tabs
- **Keyword Search**: Search document titles, descriptions, and metadata
- **Advanced Filters**: Filter by date, document type, department, etc.
#### Common Settings Problems ### Search Tips
- **Permission Conflicts**: Resolve conflicting role assignments - Use quotation marks for exact phrase searches
- **Upload Failures**: Check file type restrictions and size limits - Combine multiple keywords for more specific results
- **Authentication Issues**: Verify SSO and authentication settings - Use the predefined tags for quick filtering
- **Storage Problems**: Monitor quotas and storage allocation - Take advantage of custom metadata fields for precise searches
#### Settings Recovery ## Document Viewer
- Use exported settings files to restore configurations
- Reset specific categories if issues persist ### Supported File Types
- Contact system administrator for complex configuration issues The built-in document viewer supports:
- Review audit logs for recent configuration changes - **PDF Documents**: Full PDF viewing with zoom and navigation
- **Microsoft Office**: Word, Excel, PowerPoint documents
- **Images**: JPG, PNG, GIF, and other common image formats
- **Text Files**: Plain text, CSV, and other text-based formats
### Viewer Features
- **Zoom Controls**: Zoom in/out for better readability
- **Page Navigation**: Navigate through multi-page documents
- **Full Screen Mode**: Distraction-free document viewing
- **Download Option**: Download documents for offline access
- **Print Support**: Direct printing from the viewer
## Troubleshooting ## Troubleshooting
### Common Issues and Solutions ### Common Issues
#### Cannot Access Document #### Documents Not Loading
- **Check Access Level**: Verify document permissions and look for lock icons 1. Check your internet connection
- **Request Access**: Use the access request feature for private documents 2. Verify you have permission to access the document
- **Contact Administrator**: Reach out for urgent access needs or system issues 3. Try refreshing the page or switching tabs
- **Verify Account Status**: Ensure your user account is active and properly configured 4. Contact administrator if the issue persists
#### Upload Problems #### Upload Failures
- **File Size Limits**: Check maximum file size restrictions (typically 50MB) 1. Check file size limits (configured in DMS settings)
- **File Format Support**: Ensure file format is supported by the system 2. Verify file type is allowed (check blocked file types in settings)
- **Network Connection**: Verify stable internet connection for large uploads 3. Ensure you have upload permissions in current location
- **Upload Permissions**: Confirm you have upload permissions for the target location 4. Try uploading one file at a time if bulk upload fails
- **Browser Issues**: Try clearing cache or using a different browser
#### Search Not Working Properly #### Search Not Working
- **Check Spelling**: Verify search terms are spelled correctly 1. Clear browser cache and cookies
- **Try Alternative Keywords**: Use different or broader search terms 2. Try different search terms or keywords
- **Clear Active Filters**: Remove any filters that might be limiting results 3. Check if you're searching in the correct access level tab
- **Refresh Application**: Reload the page to reset search functionality 4. Verify documents exist in the location you're searching
- **Check Indexing Status**: Some documents may take time to be indexed for search
#### Document Viewer Issues #### Access Denied Errors
- **Browser Compatibility**: Ensure you're using a supported modern browser 1. Verify you have appropriate permissions for the document/folder
- **Enable JavaScript**: Verify JavaScript is enabled in browser settings 2. Check if the document is in the correct access level category
- **Clear Browser Cache**: Clear cache and cookies to resolve display issues 3. Request access from document owner or administrator
- **Plugin Requirements**: Install any required browser plugins for specific file types 4. Contact administrator if permissions seem incorrect
- **Download Alternative**: Use download option if viewer is not functioning
### Getting Help and Support ### Performance Issues
- **Contact IT Support**: Reach out to technical support for system issues
- **Check System Status**: Verify overall system availability and maintenance schedules
- **User Training**: Request additional training sessions for advanced features
- **Documentation**: Refer to technical guide for detailed system information
- **Community Resources**: Access user forums and knowledge bases if available
### Best Practices for Optimal System Use #### Slow Loading
1. **Systematic Organization**: Create and maintain clear, logical folder structures 1. Check internet connection speed
2. **Descriptive Naming**: Use meaningful, descriptive document titles and filenames 2. Clear browser cache and temporary files
3. **Regular Maintenance**: Periodically review and clean up outdated documents 3. Close unnecessary browser tabs
4. **Backup Strategy**: Keep local copies of critical documents as backup 4. Contact administrator about server performance
5. **Consistent Conventions**: Follow organizational naming and filing conventions
6. **Metadata Accuracy**: Keep document metadata current and accurate #### Browser Compatibility
7. **Access Control Respect**: Follow organizational policies for document sharing and access - Use modern browsers (Chrome 70+, Firefox 65+, Safari 12+, Edge 79+)
8. **Version Management**: Use version control features to track document changes - Enable JavaScript in browser settings
9. **Security Awareness**: Be mindful of document sensitivity and appropriate access levels - Update browser to latest version
10. **Regular Updates**: Stay informed about system updates and new features - Disable conflicting browser extensions
### Getting Help
#### Contact Information
- **System Administrator**: Contact your IT department
- **User Support**: Check internal documentation or help desk
- **Technical Issues**: Report to system administrator with:
- Error messages (exact text)
- Steps to reproduce the issue
- Browser and version information
- Screenshots if applicable
#### Best Practices
- **Regular Backups**: Keep local copies of important documents
- **Organize Properly**: Use consistent naming and folder structures
- **Tag Documents**: Use relevant tags for easy searching
- **Stay Updated**: Keep track of system updates and new features
- **Follow Security**: Use strong passwords and follow access control policies
This user guide provides comprehensive coverage of the EDMS interface and functionality, designed to help users effectively navigate and utilize the document management system. The system is designed for ease of use while providing powerful document management capabilities.
--- ---

View File

@ -4,14 +4,7 @@ export default [
"description": "Document Management System", "description": "Document Management System",
"child": [ "child": [
{ {
"title": "ERD", "title": "Document Explorer",
"path": "/dms/erd",
"icon": "material-symbols:database-sharp",
"child": [],
"meta": {}
},
{
"title": "Document Management",
"path": "/dms", "path": "/dms",
"icon": "ic:outline-folder", "icon": "ic:outline-folder",
"child": [], "child": [],

View File

@ -8,14 +8,11 @@ import RsCollapseItem from "../../../components/RsCollapseItem.vue";
import RsDropdown from "../../../components/RsDropdown.vue"; import RsDropdown from "../../../components/RsDropdown.vue";
import RsDropdownItem from "../../../components/RsDropdownItem.vue"; import RsDropdownItem from "../../../components/RsDropdownItem.vue";
import RsFieldset from "../../../components/RsFieldset.vue"; import RsFieldset from "../../../components/RsFieldset.vue";
import RsInput from "../../../components/RsInput.vue";
import RsModal from "../../../components/RsModal.vue"; import RsModal from "../../../components/RsModal.vue";
import RsProgressBar from "../../../components/RsProgressBar.vue"; import RsProgressBar from "../../../components/RsProgressBar.vue";
import RsSelect from "../../../components/RsSelect.vue";
import RsTab from "../../../components/RsTab.vue"; import RsTab from "../../../components/RsTab.vue";
import RsTabItem from "../../../components/RsTabItem.vue"; import RsTabItem from "../../../components/RsTabItem.vue";
import RsTable from "../../../components/RsTable.vue"; import RsTable from "../../../components/RsTable.vue";
import RsTextarea from "../../../components/RsTextarea.vue";
import RsWizard from "../../../components/RsWizard.vue"; import RsWizard from "../../../components/RsWizard.vue";
export { export {
@ -29,13 +26,10 @@ export {
RsDropdown, RsDropdown,
RsDropdownItem, RsDropdownItem,
RsFieldset, RsFieldset,
RsInput,
RsModal, RsModal,
RsProgressBar, RsProgressBar,
RsSelect,
RsTab, RsTab,
RsTabItem, RsTabItem,
RsTable, RsTable,
RsTextarea,
RsWizard, RsWizard,
}; };

View File

@ -1,277 +0,0 @@
<script setup>
import { ref } from 'vue';
const examples = ref({
input: 'Sample text',
select: 'option2',
textarea: 'Sample textarea content',
error: '',
disabled: false
});
const selectOptions = [
{ value: 'option1', label: 'Option 1' },
{ value: 'option2', label: 'Option 2' },
{ value: 'option3', label: 'Option 3' }
];
const buttonVariants = ['primary', 'secondary', 'info', 'success', 'warning', 'danger'];
const buttonSizes = ['sm', 'md', 'lg'];
</script>
<template>
<div class="design-system-page p-6">
<LayoutsBreadcrumb />
<rs-card>
<template #header>
<h1 class="text-2xl font-bold">🎨 DMS Design System</h1>
</template>
<template #body>
<div class="space-y-8">
<!-- Design Principles -->
<section>
<h2 class="text-xl font-semibold mb-4">Design Principles</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<rs-card>
<template #body>
<h3 class="font-medium mb-2">🎯 Consistency</h3>
<p class="text-sm text-gray-600">All components follow the same design patterns and naming conventions</p>
</template>
</rs-card>
<rs-card>
<template #body>
<h3 class="font-medium mb-2">🔧 Modularity</h3>
<p class="text-sm text-gray-600">Components are reusable and can be composed together</p>
</template>
</rs-card>
<rs-card>
<template #body>
<h3 class="font-medium mb-2">🌙 Dark Mode</h3>
<p class="text-sm text-gray-600">All components support both light and dark themes</p>
</template>
</rs-card>
</div>
</section>
<!-- Buttons -->
<section>
<h2 class="text-xl font-semibold mb-4">Buttons</h2>
<div class="space-y-4">
<div>
<h3 class="font-medium mb-2">Variants</h3>
<div class="flex flex-wrap gap-2">
<rs-button v-for="variant in buttonVariants" :key="variant" :variant="variant">
{{ variant.charAt(0).toUpperCase() + variant.slice(1) }}
</rs-button>
</div>
</div>
<div>
<h3 class="font-medium mb-2">Sizes</h3>
<div class="flex flex-wrap gap-2 items-center">
<rs-button v-for="size in buttonSizes" :key="size" :size="size" variant="primary">
{{ size.toUpperCase() }}
</rs-button>
</div>
</div>
<div>
<h3 class="font-medium mb-2">States</h3>
<div class="flex flex-wrap gap-2">
<rs-button variant="primary">Normal</rs-button>
<rs-button variant="primary" :disabled="true">Disabled</rs-button>
</div>
</div>
</div>
</section>
<!-- Form Components -->
<section>
<h2 class="text-xl font-semibold mb-4">Form Components</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Input -->
<div>
<h3 class="font-medium mb-3">Input Field</h3>
<rs-input
v-model="examples.input"
label="Input Label"
placeholder="Enter text here..."
:required="true"
/>
<rs-input
v-model="examples.input"
label="With Error"
placeholder="Enter text here..."
error="This field is required"
/>
<rs-input
v-model="examples.input"
label="Disabled"
placeholder="Enter text here..."
:disabled="true"
/>
</div>
<!-- Select -->
<div>
<h3 class="font-medium mb-3">Select Dropdown</h3>
<rs-select
v-model="examples.select"
:options="selectOptions"
label="Select Option"
placeholder="Choose an option"
:required="true"
/>
<rs-select
v-model="examples.select"
:options="selectOptions"
label="Multiple Select"
:multiple="true"
/>
</div>
<!-- Textarea -->
<div class="md:col-span-2">
<h3 class="font-medium mb-3">Textarea</h3>
<rs-textarea
v-model="examples.textarea"
label="Message"
placeholder="Enter your message..."
:rows="4"
:required="true"
/>
</div>
</div>
</section>
<!-- Cards -->
<section>
<h2 class="text-xl font-semibold mb-4">Cards</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<rs-card>
<template #header>
Card with Header
</template>
<template #body>
This is a card with a header section.
</template>
</rs-card>
<rs-card>
<template #body>
This is a basic card with only body content.
</template>
</rs-card>
<rs-card>
<template #body>
This card has both body and footer.
</template>
<template #footer>
<rs-button size="sm" variant="primary">Action</rs-button>
</template>
</rs-card>
</div>
</section>
<!-- Modals -->
<section>
<h2 class="text-xl font-semibold mb-4">Modals</h2>
<p class="text-gray-600 mb-4">Modals should use RsModal component with standardized header, body, and footer structure.</p>
<div class="bg-gray-100 dark:bg-gray-800 p-4 rounded-lg">
<pre class="text-sm"><code>&lt;rs-modal :visible="showModal" @close="closeModal"&gt;
&lt;template #header&gt;
Modal Title
&lt;/template&gt;
&lt;template #body&gt;
Modal content here
&lt;/template&gt;
&lt;template #footer&gt;
&lt;rs-button variant="secondary"&gt;Cancel&lt;/rs-button&gt;
&lt;rs-button variant="primary"&gt;Confirm&lt;/rs-button&gt;
&lt;/template&gt;
&lt;/rs-modal&gt;</code></pre>
</div>
</section>
<!-- Color System -->
<section>
<h2 class="text-xl font-semibold mb-4">Color System</h2>
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
<div class="text-center">
<div class="w-full h-12 bg-primary rounded mb-2"></div>
<span class="text-sm">Primary</span>
</div>
<div class="text-center">
<div class="w-full h-12 bg-secondary rounded mb-2"></div>
<span class="text-sm">Secondary</span>
</div>
<div class="text-center">
<div class="w-full h-12 bg-info rounded mb-2"></div>
<span class="text-sm">Info</span>
</div>
<div class="text-center">
<div class="w-full h-12 bg-success rounded mb-2"></div>
<span class="text-sm">Success</span>
</div>
<div class="text-center">
<div class="w-full h-12 bg-warning rounded mb-2"></div>
<span class="text-sm">Warning</span>
</div>
<div class="text-center">
<div class="w-full h-12 bg-danger rounded mb-2"></div>
<span class="text-sm">Danger</span>
</div>
</div>
</section>
<!-- Usage Guidelines -->
<section>
<h2 class="text-xl font-semibold mb-4">Usage Guidelines</h2>
<div class="space-y-4">
<rs-card>
<template #body>
<h3 class="font-medium mb-2"> Do</h3>
<ul class="list-disc pl-6 space-y-1 text-sm">
<li>Use RsButton, RsInput, RsSelect, RsTextarea for all form elements</li>
<li>Use RsModal for all dialog components</li>
<li>Follow consistent spacing and sizing patterns</li>
<li>Use semantic variant names (primary, secondary, danger, etc.)</li>
</ul>
</template>
</rs-card>
<rs-card>
<template #body>
<h3 class="font-medium mb-2"> Don't</h3>
<ul class="list-disc pl-6 space-y-1 text-sm">
<li>Use manual button styling with px-4 py-2 classes</li>
<li>Create custom input styles without using Rs components</li>
<li>Use hardcoded colors instead of CSS variables</li>
<li>Mix different component styling approaches</li>
</ul>
</template>
</rs-card>
</div>
</section>
</div>
</template>
</rs-card>
</div>
</template>
<style scoped>
pre {
overflow-x: auto;
}
code {
color: rgb(var(--text-color));
}
</style>

View File

@ -1,8 +1,5 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, onMounted, nextTick } from 'vue';
import { useDmsStore } from '~/stores/dms';
import DMSNavigation from '~/components/dms/navigation/DMSNavigation.vue';
import DMSExplorer from '~/components/dms/explorer/DMSExplorer.vue';
// Define page metadata // Define page metadata
definePageMeta({ definePageMeta({
@ -17,122 +14,90 @@ definePageMeta({
], ],
}); });
// Set up store // Import DMS components dynamically to handle potential import errors
const dmsStore = useDmsStore(); let DMSExplorer = null;
let useDmsStore = null;
// Local state // Basic state
const showFileViewer = ref(false);
const currentDocument = ref(null);
const searchQuery = ref('');
const isSearching = ref(false);
const currentPath = ref('JKR Cawangan Kota Bharu, Kelantan');
const viewMode = ref('explorer'); // explorer, cabinets, list
const selectedItem = ref(null);
const activeTab = ref('all'); const activeTab = ref('all');
const isLoading = ref(true);
const hasError = ref(false);
const errorMessage = ref('');
const componentsLoaded = ref(false);
// File selection state // Tab definitions with icons
const selectedFiles = ref([]); const tabs = [
const isSelecting = ref(false); {
id: 'all',
// Toggle file selection label: 'All Documents',
const toggleFileSelection = (file) => { icon: 'folder',
const index = selectedFiles.value.findIndex(f => f.id === file.id); color: 'blue'
if (index === -1) { },
selectedFiles.value.push(file); {
} else { id: 'public',
selectedFiles.value.splice(index, 1); label: 'Public',
icon: 'unlock',
color: 'green'
},
{
id: 'private',
label: 'Private',
icon: 'lock',
color: 'red'
},
{
id: 'personal',
label: 'Personal',
icon: 'user',
color: 'purple'
} }
};
// Clear selection
const clearSelection = () => {
selectedFiles.value = [];
isSelecting.value = false;
};
// Select all files
const selectAllFiles = () => {
selectedFiles.value = [...dmsStore.currentItems];
isSelecting.value = true;
};
// Check if a file is selected
const isFileSelected = (file) => {
return selectedFiles.value.some(f => f.id === file.id);
};
// Toggle view mode
const changeViewMode = (mode) => {
viewMode.value = mode;
};
// View a file
const viewFile = (file) => {
if (isSelecting.value) {
toggleFileSelection(file);
return;
}
currentDocument.value = file;
showFileViewer.value = true;
};
// Navigate to a location
const navigateTo = (path) => {
currentPath.value = path;
// In a real app, we would fetch the contents of this location
clearSelection();
};
// Search functionality
const handleSearch = async () => {
if (!searchQuery.value.trim()) return;
isSearching.value = true;
await dmsStore.searchDocuments(searchQuery.value);
isSearching.value = false;
};
// Clear search
const clearSearch = () => {
searchQuery.value = '';
dmsStore.clearSearch();
};
// Format file size
const formatFileSize = (size) => {
if (!size) return '0 B';
if (typeof size === 'string') {
// If already formatted (like "4MB"), return as is
if (size.endsWith('B')) return size;
// Try to parse the size if it's a number in string form
const parsed = parseFloat(size);
if (isNaN(parsed)) return size;
size = parsed;
}
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let i = 0;
while (size >= 1024 && i < units.length - 1) {
size /= 1024;
i++;
}
return `${size.toFixed(2)} ${units[i]}`;
};
// Document category tabs
const documentTabs = [
{ id: 'all', label: 'All Documents', icon: 'folder' },
{ id: 'public', label: 'Public', icon: 'unlock' },
{ id: 'private', label: 'Private', icon: 'lock' },
{ id: 'personal', label: 'Personal', icon: 'user' }
]; ];
// Handle events from explorer // Change active tab
const changeTab = (tabId) => {
activeTab.value = tabId;
};
// Get SVG icon function
const getSvgIcon = (iconName, size = 20) => {
const icons = {
folder: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>`,
unlock: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path></svg>`,
lock: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>`,
user: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>`
};
return icons[iconName] || icons.folder;
};
// Load components dynamically
const loadComponents = async () => {
try {
isLoading.value = true;
hasError.value = false;
// Import components
const dmsStoreModule = await import('~/stores/dms');
const dmsExplorerModule = await import('~/components/dms/explorer/DMSExplorer.vue');
useDmsStore = dmsStoreModule.useDmsStore;
DMSExplorer = dmsExplorerModule.default;
componentsLoaded.value = true;
await nextTick();
} catch (error) {
console.error('Failed to load DMS components:', error);
hasError.value = true;
errorMessage.value = `Failed to load DMS components: ${error.message}`;
} finally {
isLoading.value = false;
}
};
// Event handlers (placeholder functions for when components load)
const handleItemSelected = (item) => { const handleItemSelected = (item) => {
selectedItem.value = item; console.log('Item selected:', item);
}; };
const handleViewModeChanged = (mode) => { const handleViewModeChanged = (mode) => {
@ -140,23 +105,12 @@ const handleViewModeChanged = (mode) => {
}; };
const handlePathChanged = (path) => { const handlePathChanged = (path) => {
currentPath.value = path; console.log('Path changed to:', path);
};
// Get SVG icon
const getSvgIcon = (iconType, size = 16) => {
const icons = {
folder: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>`,
unlock: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path></svg>`,
lock: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>`,
user: `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>`
};
return icons[iconType] || icons.folder;
}; };
// Lifecycle hooks // Lifecycle hooks
onMounted(() => { onMounted(() => {
// Any initialization logic loadComponents();
}); });
</script> </script>
@ -167,34 +121,86 @@ onMounted(() => {
<rs-card class="h-full"> <rs-card class="h-full">
<template #body> <template #body>
<div class="dms-layout h-full flex flex-col"> <div class="dms-layout h-full flex flex-col">
<!-- Document Category Tabs -->
<div class="tabs-header border-b border-gray-200 dark:border-gray-700 p-4 bg-white dark:bg-gray-800"> <!-- Loading State -->
<div class="flex items-center space-x-1"> <div v-if="isLoading" class="flex items-center justify-center h-full">
<button <div class="text-center">
v-for="tab in documentTabs" <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
:key="tab.id" <p class="text-gray-600 dark:text-gray-400">Loading Document Management System...</p>
@click="activeTab = tab.id"
class="flex items-center space-x-2 px-4 py-2 rounded-lg text-sm font-medium transition-colors"
:class="activeTab === tab.id
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/20 dark:text-blue-300'
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-700'"
>
<span v-html="getSvgIcon(tab.icon, 16)"></span>
<span>{{ tab.label }}</span>
</button>
</div> </div>
</div> </div>
<!-- Explorer Component --> <!-- Error State -->
<div class="explorer-container flex-1 overflow-hidden"> <div v-else-if="hasError" class="flex items-center justify-center h-full">
<DMSExplorer <div class="text-center p-6">
:initial-path="'/'" <div class="text-red-500 text-5xl mb-4"></div>
:view-mode="'list'" <h2 class="text-xl font-semibold text-red-600 mb-2">Error Loading DMS</h2>
:active-document-tab="activeTab" <p class="text-gray-600 dark:text-gray-400 mb-4">{{ errorMessage }}</p>
@item-selected="handleItemSelected" <rs-button @click="loadComponents" variant="primary">
@view-mode-changed="handleViewModeChanged" Retry
@path-changed="handlePathChanged" </rs-button>
/> </div>
</div>
<!-- Main Content -->
<div v-else class="dms-content h-full flex flex-col">
<!-- Enhanced Access Level Tabs -->
<div class="access-tabs bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<div class="flex items-center px-6 py-4">
<div class="flex space-x-1">
<button
v-for="tab in tabs"
:key="tab.id"
@click="changeTab(tab.id)"
class="access-tab flex items-center space-x-2 px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200"
:class="[
activeTab === tab.id
? `bg-${tab.color}-100 text-${tab.color}-700 border border-${tab.color}-200 shadow-sm dark:bg-${tab.color}-900/20 dark:text-${tab.color}-300 dark:border-${tab.color}-800`
: 'text-gray-600 hover:text-gray-900 hover:bg-white dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-700 border border-transparent hover:border-gray-200 dark:hover:border-gray-600'
]"
>
<span
v-html="getSvgIcon(tab.icon)"
:class="[
activeTab === tab.id
? `text-${tab.color}-600 dark:text-${tab.color}-400`
: 'text-gray-500 dark:text-gray-500'
]"
></span>
<span>{{ tab.label }}</span>
<span
v-if="activeTab === tab.id"
:class="`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-${tab.color}-100 text-${tab.color}-800 dark:bg-${tab.color}-900/30 dark:text-${tab.color}-300`"
>
Active
</span>
</button>
</div>
</div>
</div>
<!-- Content Area -->
<div class="content-area flex-1">
<!-- DMS Explorer Component -->
<component
v-if="componentsLoaded && DMSExplorer"
:is="DMSExplorer"
:initial-path="'/'"
:view-mode="'list'"
:active-document-tab="activeTab"
@item-selected="handleItemSelected"
@view-mode-changed="handleViewModeChanged"
@path-changed="handlePathChanged"
/>
<!-- Fallback Content -->
<div v-else class="text-center py-12">
<h2 class="text-2xl font-semibold mb-4">{{ tabs.find(t => t.id === activeTab)?.label }}</h2>
<p class="text-gray-600 dark:text-gray-400">
Document explorer is loading...
</p>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -211,16 +217,81 @@ onMounted(() => {
height: 100%; height: 100%;
} }
.explorer-container { .content-area {
min-height: 0; min-height: 0;
overflow: hidden;
} }
/* Ensure smooth transitions */ .dms-content {
.tabs-header button { height: 100%;
transition: all 0.2s ease;
} }
.tabs-header button:hover { .access-tabs {
flex-shrink: 0;
}
.access-tab {
position: relative;
overflow: hidden;
}
.access-tab:hover {
transform: translateY(-1px); transform: translateY(-1px);
} }
.access-tab.active {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* Dynamic color classes */
.bg-blue-100 { background-color: rgb(219 234 254); }
.text-blue-700 { color: rgb(29 78 216); }
.border-blue-200 { border-color: rgb(191 219 254); }
.text-blue-600 { color: rgb(37 99 235); }
.bg-blue-100 { background-color: rgb(219 234 254); }
.text-blue-800 { color: rgb(30 64 175); }
.bg-green-100 { background-color: rgb(220 252 231); }
.text-green-700 { color: rgb(21 128 61); }
.border-green-200 { border-color: rgb(187 247 208); }
.text-green-600 { color: rgb(22 163 74); }
.text-green-800 { color: rgb(22 101 52); }
.bg-red-100 { background-color: rgb(254 226 226); }
.text-red-700 { color: rgb(185 28 28); }
.border-red-200 { border-color: rgb(254 202 202); }
.text-red-600 { color: rgb(220 38 38); }
.text-red-800 { color: rgb(153 27 27); }
.bg-purple-100 { background-color: rgb(243 232 255); }
.text-purple-700 { color: rgb(126 34 206); }
.border-purple-200 { border-color: rgb(233 213 255); }
.text-purple-600 { color: rgb(147 51 234); }
.text-purple-800 { color: rgb(107 33 168); }
/* Dark mode colors */
.dark .bg-blue-900\/20 { background-color: rgba(30, 58, 138, 0.2); }
.dark .text-blue-300 { color: rgb(147 197 253); }
.dark .border-blue-800 { border-color: rgb(30 64 175); }
.dark .text-blue-400 { color: rgb(96 165 250); }
.dark .bg-blue-900\/30 { background-color: rgba(30, 58, 138, 0.3); }
.dark .bg-green-900\/20 { background-color: rgba(20, 83, 45, 0.2); }
.dark .text-green-300 { color: rgb(134 239 172); }
.dark .border-green-800 { border-color: rgb(22 101 52); }
.dark .text-green-400 { color: rgb(74 222 128); }
.dark .bg-green-900\/30 { background-color: rgba(20, 83, 45, 0.3); }
.dark .bg-red-900\/20 { background-color: rgba(127, 29, 29, 0.2); }
.dark .text-red-300 { color: rgb(252 165 165); }
.dark .border-red-800 { border-color: rgb(153 27 27); }
.dark .text-red-400 { color: rgb(248 113 113); }
.dark .bg-red-900\/30 { background-color: rgba(127, 29, 29, 0.3); }
.dark .bg-purple-900\/20 { background-color: rgba(88, 28, 135, 0.2); }
.dark .text-purple-300 { color: rgb(196 181 253); }
.dark .border-purple-800 { border-color: rgb(107 33 168); }
.dark .text-purple-400 { color: rgb(168 85 247); }
.dark .bg-purple-900\/30 { background-color: rgba(88, 28, 135, 0.3); }
</style> </style>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, reactive, computed } from 'vue'; import { ref, reactive, computed, onMounted, watch } from 'vue';
// Define page metadata // Define page metadata
definePageMeta({ definePageMeta({
@ -18,11 +18,18 @@ definePageMeta({
], ],
}); });
// Basic loading and error states
const isLoading = ref(true);
const isSaving = ref(false);
const saveError = ref('');
const saveSuccess = ref('');
// Settings categories // Settings categories
const settingsCategories = [ const settingsCategories = [
{ id: 'access', name: 'User & Access Management', icon: '🔐' }, { id: 'access', name: 'User & Access Management', icon: '🔐' },
{ id: 'documents', name: 'Document & Folder Settings', icon: '📁' }, { id: 'documents', name: 'Document & Folder Settings', icon: '📁' },
{ id: 'metadata', name: 'Metadata & Tagging', icon: '📝' }, { id: 'metadata', name: 'Metadata & Tagging', icon: '📝' },
{ id: 'workflow', name: 'Workflow & Automation', icon: '🔄' },
{ id: 'upload', name: 'Upload & Storage Settings', icon: '📤' }, { id: 'upload', name: 'Upload & Storage Settings', icon: '📤' },
{ id: 'system', name: 'System Settings', icon: '📅' } { id: 'system', name: 'System Settings', icon: '📅' }
]; ];
@ -30,9 +37,8 @@ const settingsCategories = [
// Current active category // Current active category
const activeCategory = ref('access'); const activeCategory = ref('access');
// Settings data structure // Local reactive settings for form manipulation with default structure
const settings = reactive({ const settings = reactive({
// User & Access Management
access: { access: {
userRoles: ['Admin', 'Editor', 'Viewer', 'Uploader'], userRoles: ['Admin', 'Editor', 'Viewer', 'Uploader'],
rbacEnabled: true, rbacEnabled: true,
@ -51,8 +57,6 @@ const settings = reactive({
sessionTimeout: 8 sessionTimeout: 8
} }
}, },
// Document & Folder Settings
documents: { documents: {
folderHierarchy: { folderHierarchy: {
maxDepth: 5, maxDepth: 5,
@ -66,7 +70,7 @@ const settings = reactive({
}, },
retention: { retention: {
enabled: true, enabled: true,
defaultDays: 2555, // 7 years defaultDays: 2555,
archiveBeforeDelete: true archiveBeforeDelete: true
}, },
versionControl: { versionControl: {
@ -75,8 +79,6 @@ const settings = reactive({
autoVersioning: true autoVersioning: true
} }
}, },
// Metadata & Tagging
metadata: { metadata: {
customFields: [ customFields: [
{ name: 'Department', type: 'dropdown', required: true }, { name: 'Department', type: 'dropdown', required: true },
@ -94,28 +96,41 @@ const settings = reactive({
rules: ['confidential-keywords', 'department-based', 'file-type'] rules: ['confidential-keywords', 'department-based', 'file-type']
} }
}, },
workflow: {
// Upload & Storage Settings approvalFlows: {
enabled: true,
defaultFlow: 'department-head-approval',
customFlows: ['legal-review', 'finance-approval', 'director-sign-off']
},
notifications: {
emailNotifications: true,
inAppNotifications: true,
uploadAlerts: true,
deadlineReminders: true
},
automation: {
triggers: ['document-uploaded', 'approval-completed', 'deadline-reached'],
actions: ['move-to-folder', 'send-notification', 'create-task']
}
},
upload: { upload: {
fileTypes: { fileTypes: {
allowed: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'jpg', 'png'], allowed: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'jpg', 'png'],
blocked: ['exe', 'bat', 'cmd'] blocked: ['exe', 'bat', 'cmd']
}, },
fileSizeLimit: 100, // MB fileSizeLimit: 100,
quotas: { quotas: {
perUser: 5000, // MB perUser: 5000,
perGroup: 50000, // MB perGroup: 50000,
perProject: 100000 // MB perProject: 100000
}, },
storage: { storage: {
type: 'local', // local, s3, azure, google type: 'local',
path: '/var/uploads/edms', path: '/var/uploads/edms',
backupEnabled: true, backupEnabled: true,
compressionEnabled: false compressionEnabled: false
} }
}, },
// System Settings
system: { system: {
timezone: 'Asia/Kuala_Lumpur', timezone: 'Asia/Kuala_Lumpur',
backupSchedule: 'daily', backupSchedule: 'daily',
@ -127,67 +142,137 @@ const settings = reactive({
} }
}); });
// Computed properties
const currentSettings = computed(() => {
return settings[activeCategory.value];
});
// Computed properties for array-to-string conversions // Computed properties for array-to-string conversions
const predefinedTagsString = computed({ const predefinedTagsString = computed({
get: () => settings.metadata.tagging.predefinedTags.join(', '), get: () => settings.metadata?.tagging?.predefinedTags?.join(', ') || '',
set: (value) => { set: (value) => {
settings.metadata.tagging.predefinedTags = value.split(',').map(tag => tag.trim()).filter(tag => tag.length > 0); settings.metadata.tagging.predefinedTags = value.split(',').map(tag => tag.trim()).filter(tag => tag.length > 0);
} }
}); });
const allowedFileTypesString = computed({ const allowedFileTypesString = computed({
get: () => settings.upload.fileTypes.allowed.join(', '), get: () => settings.upload?.fileTypes?.allowed?.join(', ') || '',
set: (value) => { set: (value) => {
settings.upload.fileTypes.allowed = value.split(',').map(type => type.trim()).filter(type => type.length > 0); settings.upload.fileTypes.allowed = value.split(',').map(type => type.trim()).filter(type => type.length > 0);
} }
}); });
const blockedFileTypesString = computed({ const blockedFileTypesString = computed({
get: () => settings.upload.fileTypes.blocked.join(', '), get: () => settings.upload?.fileTypes?.blocked?.join(', ') || '',
set: (value) => { set: (value) => {
settings.upload.fileTypes.blocked = value.split(',').map(type => type.trim()).filter(type => type.length > 0); settings.upload.fileTypes.blocked = value.split(',').map(type => type.trim()).filter(type => type.length > 0);
} }
}); });
// Methods // Load settings from API
const loadSettings = async () => {
try {
isLoading.value = true;
saveError.value = '';
const response = await $fetch('/api/dms/settings', {
method: 'GET'
});
if (response && response.data) {
// Merge loaded settings with defaults
Object.assign(settings, response.data);
console.log('Settings loaded successfully:', response.data);
}
} catch (error) {
console.error('Error loading settings:', error);
saveError.value = 'Failed to load settings. Using defaults.';
} finally {
isLoading.value = false;
}
};
// Save settings to API
const saveSettings = async () => { const saveSettings = async () => {
try { try {
// In a real app, this would make an API call isSaving.value = true;
console.log('Saving settings:', settings); saveError.value = '';
saveSuccess.value = '';
// Show success message const response = await $fetch('/api/dms/settings', {
alert('Settings saved successfully!'); method: 'POST',
body: settings
});
if (response && response.statusCode === 200) {
saveSuccess.value = 'Settings saved successfully!';
setTimeout(() => {
saveSuccess.value = '';
}, 3000);
} else {
saveError.value = 'Failed to save settings. Please try again.';
}
} catch (error) { } catch (error) {
console.error('Error saving settings:', error); console.error('Error saving settings:', error);
alert('Error saving settings. Please try again.'); saveError.value = 'Error saving settings. Please try again.';
} finally {
isSaving.value = false;
} }
}; };
const resetToDefaults = () => { // Reset to defaults
const resetToDefaultsConfirm = async () => {
if (confirm('Are you sure you want to reset all settings to defaults? This action cannot be undone.')) { if (confirm('Are you sure you want to reset all settings to defaults? This action cannot be undone.')) {
// Reset logic would go here try {
console.log('Resetting to defaults'); isSaving.value = true;
saveError.value = '';
saveSuccess.value = '';
// Reset to default values
settings.access = {
userRoles: ['Admin', 'Editor', 'Viewer', 'Uploader'],
rbacEnabled: true,
userGroups: ['HR Department', 'Finance', 'IT', 'Legal'],
permissions: {
view: true,
edit: true,
delete: false,
download: true,
share: true
},
authentication: {
ssoEnabled: false,
mfaRequired: false,
ldapIntegration: false,
sessionTimeout: 8
}
};
// Save the reset settings
await saveSettings();
saveSuccess.value = 'Settings reset to defaults successfully!';
} catch (error) {
console.error('Error resetting settings:', error);
saveError.value = 'Error resetting settings. Please try again.';
}
} }
}; };
const exportSettings = () => { // Export settings
const dataStr = JSON.stringify(settings, null, 2); const exportSettingsFile = () => {
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); try {
const dataStr = JSON.stringify(settings, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportFileDefaultName = 'edms-settings.json'; const exportFileDefaultName = 'dms-settings.json';
const linkElement = document.createElement('a'); const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri); linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName); linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click(); linkElement.click();
} catch (error) {
console.error('Error exporting settings:', error);
saveError.value = 'Error exporting settings.';
}
}; };
const importSettings = (event) => { // Import settings
const importSettingsFile = (event) => {
const file = event.target.files[0]; const file = event.target.files[0];
if (file) { if (file) {
const reader = new FileReader(); const reader = new FileReader();
@ -195,9 +280,13 @@ const importSettings = (event) => {
try { try {
const importedSettings = JSON.parse(e.target.result); const importedSettings = JSON.parse(e.target.result);
Object.assign(settings, importedSettings); Object.assign(settings, importedSettings);
alert('Settings imported successfully!'); saveSuccess.value = 'Settings imported successfully!';
setTimeout(() => {
saveSuccess.value = '';
}, 3000);
} catch (error) { } catch (error) {
alert('Error importing settings. Please check the file format.'); console.error('Error importing settings:', error);
saveError.value = 'Error importing settings. Please check the file format.';
} }
}; };
reader.readAsText(file); reader.readAsText(file);
@ -219,8 +308,10 @@ const removeCustomField = (index) => {
const addUserRole = () => { const addUserRole = () => {
const roleName = prompt('Enter new role name:'); const roleName = prompt('Enter new role name:');
if (roleName && !settings.access.userRoles.includes(roleName)) { if (roleName && roleName.trim()) {
settings.access.userRoles.push(roleName); if (!settings.access.userRoles.includes(roleName.trim())) {
settings.access.userRoles.push(roleName.trim());
}
} }
}; };
@ -230,6 +321,11 @@ const removeUserRole = (role) => {
settings.access.userRoles.splice(index, 1); settings.access.userRoles.splice(index, 1);
} }
}; };
// Load settings on mount
onMounted(async () => {
await loadSettings();
});
</script> </script>
<template> <template>
@ -241,46 +337,66 @@ const removeUserRole = (role) => {
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">DMS Settings</h1> <h1 class="text-2xl font-bold text-gray-900 dark:text-gray-100">DMS Settings</h1>
<div class="flex space-x-2"> <div class="flex space-x-2">
<rs-button @click="exportSettings" class="!bg-gray-100 !text-gray-700 border"> <rs-button @click="exportSettingsFile" variant="secondary-outline" size="sm" :disabled="isLoading || isSaving">
<Icon name="ic:outline-download" class="mr-2" /> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7,10 12,15 17,10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
Export Export
</rs-button> </rs-button>
<label class="cursor-pointer"> <label class="cursor-pointer">
<input type="file" @change="importSettings" accept=".json" class="hidden" /> <input type="file" @change="importSettingsFile" accept=".json" class="hidden" :disabled="isLoading || isSaving" />
<rs-button class="!bg-gray-100 !text-gray-700 border"> <rs-button variant="secondary-outline" size="sm" :disabled="isLoading || isSaving">
<Icon name="ic:outline-upload" class="mr-2" /> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17,8 12,3 7,8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
Import Import
</rs-button> </rs-button>
</label> </label>
<rs-button @click="resetToDefaults" class="!bg-red-100 !text-red-700 border border-red-200"> <rs-button @click="resetToDefaultsConfirm" variant="danger-outline" size="sm" :disabled="isLoading || isSaving">
<Icon name="ic:outline-refresh" class="mr-2" /> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2"><polyline points="1,4 1,10 7,10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path></svg>
Reset Reset
</rs-button> </rs-button>
<rs-button @click="saveSettings" class="!bg-blue-600 !text-white"> <rs-button @click="saveSettings" variant="primary" size="sm" :disabled="isLoading || isSaving">
<Icon name="ic:outline-save" class="mr-2" /> <svg v-if="isSaving" class="animate-spin mr-2 h-4 w-4" viewBox="0 0 24 24">
Save Settings <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17,21 17,13 7,13 7,21"></polyline><polyline points="7,3 7,8 15,8"></polyline></svg>
{{ isSaving ? 'Saving...' : 'Save Settings' }}
</rs-button> </rs-button>
</div> </div>
</div> </div>
<!-- Success/Error Messages -->
<div v-if="saveSuccess" class="mt-4 p-3 bg-green-100 border border-green-400 text-green-700 rounded">
{{ saveSuccess }}
</div>
<div v-if="saveError" class="mt-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
{{ saveError }}
</div>
</template> </template>
<template #body> <template #body>
<div class="settings-layout flex h-full"> <!-- Loading State -->
<div v-if="isLoading" class="flex items-center justify-center h-64">
<div class="text-center">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
<p class="text-gray-600 dark:text-gray-400">Loading DMS settings...</p>
</div>
</div>
<!-- Settings Content -->
<div v-else class="settings-layout flex h-full">
<!-- Settings Navigation --> <!-- Settings Navigation -->
<div class="settings-nav w-80 border-r border-gray-200 dark:border-gray-700 p-4 bg-gray-50 dark:bg-gray-800"> <div class="settings-nav w-80 border-r border-gray-200 dark:border-gray-700 p-4 bg-gray-50 dark:bg-gray-800">
<div class="space-y-2"> <div class="space-y-2">
<button <rs-button
v-for="category in settingsCategories" v-for="category in settingsCategories"
:key="category.id" :key="category.id"
@click="activeCategory = category.id" @click="activeCategory = category.id"
class="w-full flex items-center space-x-3 px-4 py-3 text-left rounded-lg transition-colors" :variant="activeCategory === category.id ? 'primary' : 'secondary-text'"
:class="activeCategory === category.id size="md"
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/20 dark:text-blue-300' class="w-full justify-start"
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'"
> >
<span class="text-lg">{{ category.icon }}</span> <span class="text-lg mr-3">{{ category.icon }}</span>
<span class="font-medium text-sm">{{ category.name }}</span> <span class="font-medium text-sm">{{ category.name }}</span>
</button> </rs-button>
</div> </div>
</div> </div>
@ -301,11 +417,14 @@ const removeUserRole = (role) => {
<div class="space-y-2"> <div class="space-y-2">
<div v-for="role in settings.access.userRoles" :key="role" class="flex items-center justify-between bg-gray-50 dark:bg-gray-700 px-3 py-2 rounded"> <div v-for="role in settings.access.userRoles" :key="role" class="flex items-center justify-between bg-gray-50 dark:bg-gray-700 px-3 py-2 rounded">
<span>{{ role }}</span> <span>{{ role }}</span>
<button @click="removeUserRole(role)" class="text-red-500 hover:text-red-700"> <rs-button @click="removeUserRole(role)" variant="danger-text" size="sm">
<Icon name="ic:outline-delete" size="16" /> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3,6 5,6 21,6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
</button> </rs-button>
</div> </div>
<button @click="addUserRole" class="text-blue-600 hover:text-blue-800 text-sm">+ Add Role</button> <rs-button @click="addUserRole" variant="primary-text" size="sm">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-1"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
Add Role
</rs-button>
</div> </div>
</div> </div>
<div> <div>
@ -369,30 +488,6 @@ const removeUserRole = (role) => {
<div> <div>
<h2 class="text-xl font-semibold mb-4">📁 Document & Folder Settings</h2> <h2 class="text-xl font-semibold mb-4">📁 Document & Folder Settings</h2>
<!-- Folder Hierarchy -->
<div class="bg-white dark:bg-gray-800 rounded-lg border p-6 mb-6">
<h3 class="text-lg font-medium mb-4">Folder Hierarchies</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium mb-2">Maximum Folder Depth</label>
<input type="number" v-model="settings.documents.folderHierarchy.maxDepth"
class="w-full px-3 py-2 border border-gray-300 rounded-md" min="1" max="10" />
</div>
<div>
<rs-select
v-model="settings.documents.folderHierarchy.folderTemplates[0]"
:options="[
{ value: 'Standard', label: 'Standard' },
{ value: 'Project-based', label: 'Project-based' },
{ value: 'Department-based', label: 'Department-based' },
{ value: 'Custom', label: 'Custom' }
]"
label="Folder Template"
/>
</div>
</div>
</div>
<!-- Naming Conventions --> <!-- Naming Conventions -->
<div class="bg-white dark:bg-gray-800 rounded-lg border p-6 mb-6"> <div class="bg-white dark:bg-gray-800 rounded-lg border p-6 mb-6">
<h3 class="text-lg font-medium mb-4">Document Naming Conventions</h3> <h3 class="text-lg font-medium mb-4">Document Naming Conventions</h3>
@ -402,11 +497,10 @@ const removeUserRole = (role) => {
Auto-generate document names Auto-generate document names
</label> </label>
<div> <div>
<rs-input <label class="block text-sm font-medium mb-2">Naming Pattern</label>
v-model="settings.documents.namingConventions.pattern" <input type="text" v-model="settings.documents.namingConventions.pattern"
label="Naming Pattern" class="w-full px-3 py-2 border border-gray-300 rounded-md"
placeholder="{department}_{title}_{date}" placeholder="{department}_{title}_{date}" />
/>
</div> </div>
</div> </div>
</div> </div>
@ -459,13 +553,14 @@ const removeUserRole = (role) => {
<input type="checkbox" v-model="field.required" class="mr-2" /> <input type="checkbox" v-model="field.required" class="mr-2" />
Required Required
</label> </label>
<button @click="removeCustomField(index)" class="text-red-500 hover:text-red-700"> <rs-button @click="removeCustomField(index)" variant="danger-text" size="sm">
<Icon name="ic:outline-delete" size="20" /> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3,6 5,6 21,6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
</button> </rs-button>
</div> </div>
<button @click="addCustomField" class="text-blue-600 hover:text-blue-800"> <rs-button @click="addCustomField" variant="primary-text" size="sm">
+ Add Custom Field <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-1"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
</button> Add Custom Field
</rs-button>
</div> </div>
</div> </div>
@ -482,12 +577,10 @@ const removeUserRole = (role) => {
Enable Tag Suggestions Enable Tag Suggestions
</label> </label>
<div> <div>
<rs-textarea <label class="block text-sm font-medium mb-2">Predefined Tags</label>
v-model="predefinedTagsString" <textarea v-model="predefinedTagsString"
label="Predefined Tags" class="w-full px-3 py-2 border border-gray-300 rounded-md h-20"
placeholder="urgent, confidential, public, draft, final" placeholder="urgent, confidential, public, draft, final"></textarea>
:rows="3"
/>
</div> </div>
</div> </div>
</div> </div>
@ -601,18 +694,57 @@ const removeUserRole = (role) => {
</div> </div>
</div> </div>
<!-- Placeholder for other categories --> <!-- Workflow Settings -->
<div v-if="!['access', 'documents', 'metadata', 'upload', 'system'].includes(activeCategory)" class="space-y-8"> <div v-if="activeCategory === 'workflow'" class="space-y-8">
<div class="text-center py-12"> <div>
<div class="text-6xl mb-4"> <h2 class="text-xl font-semibold mb-4">🔄 Workflow & Automation</h2>
{{ settingsCategories.find(c => c.id === activeCategory)?.icon }}
<!-- Approval Flows -->
<div class="bg-white dark:bg-gray-800 rounded-lg border p-6 mb-6">
<h3 class="text-lg font-medium mb-4">Approval Workflows</h3>
<div class="space-y-4">
<label class="flex items-center">
<input type="checkbox" v-model="settings.workflow.approvalFlows.enabled" class="mr-2" />
Enable Approval Workflows
</label>
<div>
<label class="block text-sm font-medium mb-2">Default Approval Flow</label>
<select v-model="settings.workflow.approvalFlows.defaultFlow" class="w-full px-3 py-2 border border-gray-300 rounded-md">
<option value="department-head-approval">Department Head Approval</option>
<option value="legal-review">Legal Review</option>
<option value="finance-approval">Finance Approval</option>
<option value="director-sign-off">Director Sign-off</option>
</select>
</div>
</div>
</div>
<!-- Notifications -->
<div class="bg-white dark:bg-gray-800 rounded-lg border p-6">
<h3 class="text-lg font-medium mb-4">Notification Settings</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-3">
<label class="flex items-center">
<input type="checkbox" v-model="settings.workflow.notifications.emailNotifications" class="mr-2" />
Email Notifications
</label>
<label class="flex items-center">
<input type="checkbox" v-model="settings.workflow.notifications.inAppNotifications" class="mr-2" />
In-App Notifications
</label>
</div>
<div class="space-y-3">
<label class="flex items-center">
<input type="checkbox" v-model="settings.workflow.notifications.uploadAlerts" class="mr-2" />
Upload Alerts
</label>
<label class="flex items-center">
<input type="checkbox" v-model="settings.workflow.notifications.deadlineReminders" class="mr-2" />
Deadline Reminders
</label>
</div>
</div>
</div> </div>
<h2 class="text-xl font-semibold mb-2">
{{ settingsCategories.find(c => c.id === activeCategory)?.name }}
</h2>
<p class="text-gray-600 dark:text-gray-400">
Settings for this category are being developed and will be available in the next update.
</p>
</div> </div>
</div> </div>

View File

@ -12,52 +12,94 @@ model audit {
auditIP String? @db.VarChar(255) auditIP String? @db.VarChar(255)
auditURL String? @db.VarChar(255) auditURL String? @db.VarChar(255)
auditURLMethod String? @db.VarChar(255) auditURLMethod String? @db.VarChar(255)
auditURLPayload String? @db.VarChar(255) auditURLPayload String? @db.Text
auditCreatedDate DateTime? @db.DateTime(0) auditCreatedDate DateTime? @default(now()) @db.DateTime(0)
auditAction String? @db.VarChar(255)
auditDetails String? @db.Text
auditUserID Int?
auditUsername String? @db.VarChar(255)
user user? @relation(fields: [auditUserID], references: [userID])
@@index([auditUserID], map: "FK_audit_user")
} }
model lookup { model organization {
lookupID Int @id @default(autoincrement()) org_id Int @id @default(autoincrement())
lookupOrder Int? org_name String @unique(map: "organization_unique") @db.VarChar(255)
lookupTitle String? @db.VarChar(255) org_address1 String? @db.VarChar(255)
lookupRefCode String? @db.VarChar(255) org_address2 String? @db.VarChar(255)
lookupValue String? @db.VarChar(255) org_postcode Int?
lookupType String? @db.VarChar(255) org_state String? @db.VarChar(100)
lookupStatus String? @db.VarChar(255) org_country String? @db.VarChar(100)
lookupCreatedDate DateTime? @db.DateTime(0) org_active Int?
lookupModifiedDate DateTime? @db.DateTime(0) departments department[]
}
model department {
dp_id Int @id @default(autoincrement())
dp_name String @db.VarChar(255)
org_id Int
cabinets cabinets[]
organization organization @relation(fields: [org_id], references: [org_id], onDelete: Cascade, onUpdate: NoAction, map: "department_organization_FK")
users sys_user[]
@@index([org_id], map: "department_organization_FK")
}
model cabinets {
cb_id Int @id @default(autoincrement())
cb_name String @unique(map: "cabinet_master_unique") @db.VarChar(255)
cb_parent_id Int?
cb_private Int? @default(0)
cb_owner Int?
dp_id Int?
created_at DateTime? @db.DateTime(0)
modified_at DateTime? @db.DateTime(0)
department department? @relation(fields: [dp_id], references: [dp_id], onDelete: NoAction, onUpdate: NoAction, map: "cabinets_department_FK")
@@index([dp_id], map: "cabinets_department_FK")
}
model sys_user {
su_id Int @id @default(autoincrement())
su_username String @unique(map: "sys_user_unique") @db.VarChar(100)
su_name String @db.VarChar(255)
su_nric Int @unique(map: "sys_user_unique_1")
su_dob DateTime @db.Date
su_email String? @db.VarChar(255)
su_password String @db.VarChar(255)
dp_id Int
su_active Int? @default(1)
su_lock Int? @default(0)
su_org_id Int
department department @relation(fields: [dp_id], references: [dp_id], onDelete: NoAction, onUpdate: NoAction, map: "sys_user_department_FK")
@@index([dp_id], map: "sys_user_department_FK")
} }
model role { model role {
roleID Int @id @default(autoincrement()) roleID Int @id @default(autoincrement())
roleName String? @db.VarChar(255) roleName String? @db.VarChar(255)
roleDescription String? @db.VarChar(255) roleDescription String? @db.VarChar(255)
roleStatus String? @db.VarChar(255) roleStatus String? @db.VarChar(255)
roleCreatedDate DateTime? @db.DateTime(0) roleCreatedDate DateTime? @db.DateTime(0)
roleModifiedDate DateTime? @db.DateTime(0) roleModifiedDate DateTime? @db.DateTime(0)
userrole userrole[] userrole userrole[]
permissions AccessPermission[]
} }
model user { model user {
userID Int @id @default(autoincrement()) userID Int @id @default(autoincrement())
userSecretKey String? @db.VarChar(255) userSecretKey String? @db.VarChar(255)
userUsername String? @db.VarChar(255) userUsername String? @db.VarChar(255)
userPassword String? @db.VarChar(255) userPassword String? @db.VarChar(255)
userFullName String? @db.VarChar(255) userFullName String? @db.VarChar(255)
userEmail String? @db.VarChar(255) userEmail String? @db.VarChar(255)
userPhone String? @db.VarChar(255) userPhone String? @db.VarChar(255)
userStatus String? @db.VarChar(255) userStatus String? @db.VarChar(255)
userCreatedDate DateTime? @db.DateTime(0) userCreatedDate DateTime? @db.DateTime(0)
userModifiedDate DateTime? @db.DateTime(0) userModifiedDate DateTime? @db.DateTime(0)
audit audit[]
userrole userrole[] userrole userrole[]
accessRequests AccessRequest[]
permissions AccessPermission[]
documents Document[] @relation("DocumentCreator")
cabinets Cabinet[] @relation("CabinetCreator")
drawers Drawer[] @relation("DrawerCreator")
folders Folder[] @relation("FolderCreator")
subfolders Subfolder[] @relation("SubfolderCreator")
} }
model userrole { model userrole {
@ -73,198 +115,105 @@ model userrole {
} }
model site_settings { model site_settings {
settingID Int @id @default(autoincrement()) settingID Int @id @default(autoincrement())
siteName String? @db.VarChar(255) siteName String? @default("corradAF") @db.VarChar(255)
siteNameFontSize Int? @default(18) siteNameFontSize Int? @default(18)
siteDescription String? @db.Text siteDescription String? @db.Text
siteLogo String? @db.VarChar(500) siteLogo String? @db.VarChar(500)
siteLoadingLogo String? @db.VarChar(500) siteLoadingLogo String? @db.VarChar(500)
siteFavicon String? @db.VarChar(500) siteFavicon String? @db.VarChar(500)
showSiteNameInHeader Boolean? @default(true) siteLoginLogo String? @db.VarChar(500)
primaryColor String? @db.VarChar(50) showSiteNameInHeader Boolean? @default(true)
secondaryColor String? @db.VarChar(50) customCSS String? @db.LongText
successColor String? @db.VarChar(50) themeMode String? @default("biasa") @db.VarChar(100)
infoColor String? @db.VarChar(50) customThemeFile String? @db.VarChar(500)
warningColor String? @db.VarChar(50) currentFont String? @db.VarChar(100)
dangerColor String? @db.VarChar(50) fontSource String? @db.VarChar(100)
customCSS String? @db.Text seoTitle String? @db.VarChar(255)
themeMode String? @db.VarChar(50) seoDescription String? @db.Text
customThemeFile String? @db.VarChar(500) seoKeywords String? @db.Text
currentFont String? @db.VarChar(255) seoAuthor String? @db.VarChar(255)
fontSource String? @db.VarChar(500) seoOgImage String? @db.VarChar(500)
seoTitle String? @db.VarChar(255) seoTwitterCard String? @default("summary_large_image") @db.VarChar(100)
seoDescription String? @db.Text seoCanonicalUrl String? @db.VarChar(500)
seoKeywords String? @db.Text seoRobots String? @default("index, follow") @db.VarChar(100)
seoAuthor String? @db.VarChar(255) seoGoogleAnalytics String? @db.VarChar(255)
seoOgImage String? @db.VarChar(500) seoGoogleTagManager String? @db.VarChar(255)
seoTwitterCard String? @default("summary_large_image") @db.VarChar(50) seoFacebookPixel String? @db.VarChar(255)
seoCanonicalUrl String? @db.VarChar(500) settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
seoRobots String? @default("index, follow") @db.VarChar(100) settingModifiedDate DateTime? @default(now()) @db.DateTime(0)
seoGoogleAnalytics String? @db.VarChar(255)
seoGoogleTagManager String? @db.VarChar(255)
seoFacebookPixel String? @db.VarChar(255)
settingCreatedDate DateTime? @db.DateTime(0)
settingModifiedDate DateTime? @db.DateTime(0)
siteLoginLogo String? @db.VarChar(500)
} }
// DMS Models model dms_settings {
settingID Int @id @default(autoincrement())
// User & Access Management
userRoles String? @db.Text
rbacEnabled Boolean? @default(true)
userGroups String? @db.Text
permissionView Boolean? @default(true)
permissionEdit Boolean? @default(true)
permissionDelete Boolean? @default(false)
permissionDownload Boolean? @default(true)
permissionShare Boolean? @default(true)
ssoEnabled Boolean? @default(false)
mfaRequired Boolean? @default(false)
ldapIntegration Boolean? @default(false)
sessionTimeout Int? @default(8)
model Cabinet { // Document & Folder Settings
id Int @id @default(autoincrement()) folderMaxDepth Int? @default(5)
name String @db.VarChar(255) folderDefaultStructure String? @db.Text
description String? @db.Text folderTemplates String? @db.Text
createdAt DateTime @default(now()) @db.DateTime(0) namingAutoGenerate Boolean? @default(true)
updatedAt DateTime @updatedAt @db.DateTime(0) namingMandatoryFields String? @db.Text
createdBy Int namingPattern String? @default("{department}_{title}_{date}") @db.VarChar(255)
status String @default("active") @db.VarChar(50) retentionEnabled Boolean? @default(true)
user user @relation("CabinetCreator", fields: [createdBy], references: [userID]) retentionDefaultDays Int? @default(2555)
drawers Drawer[] retentionArchiveBeforeDelete Boolean? @default(true)
permissions AccessPermission[] versionControlEnabled Boolean? @default(true)
versionControlMaxVersions Int? @default(10)
versionControlAutoVersioning Boolean? @default(true)
@@index([createdBy]) // Metadata & Tagging
} metadataCustomFields String? @db.LongText
taggingPredefinedTags String? @db.Text
model Drawer { taggingUserGeneratedTags Boolean? @default(true)
id Int @id @default(autoincrement()) taggingTagSuggestions Boolean? @default(true)
name String @db.VarChar(255) classificationAutoEnabled Boolean? @default(true)
description String? @db.Text classificationRules String? @db.Text
cabinetId Int
createdAt DateTime @default(now()) @db.DateTime(0) // Workflow & Automation
updatedAt DateTime @updatedAt @db.DateTime(0) workflowApprovalEnabled Boolean? @default(true)
createdBy Int workflowDefaultFlow String? @default("department-head-approval") @db.VarChar(255)
status String @default("active") @db.VarChar(50) workflowCustomFlows String? @db.Text
cabinet Cabinet @relation(fields: [cabinetId], references: [id], onDelete: Cascade) notificationEmail Boolean? @default(true)
user user @relation("DrawerCreator", fields: [createdBy], references: [userID]) notificationInApp Boolean? @default(true)
folders Folder[] notificationUploadAlerts Boolean? @default(true)
permissions AccessPermission[] notificationDeadlineReminders Boolean? @default(true)
automationTriggers String? @db.Text
@@index([cabinetId]) automationActions String? @db.Text
@@index([createdBy])
} // Upload & Storage Settings
uploadAllowedFileTypes String? @db.Text
model Folder { uploadBlockedFileTypes String? @db.Text
id Int @id @default(autoincrement()) uploadFileSizeLimit Int? @default(100)
name String @db.VarChar(255) uploadQuotaPerUser Int? @default(5000)
description String? @db.Text uploadQuotaPerGroup Int? @default(50000)
drawerId Int uploadQuotaPerProject Int? @default(100000)
createdAt DateTime @default(now()) @db.DateTime(0) storageType String? @default("local") @db.VarChar(100)
updatedAt DateTime @updatedAt @db.DateTime(0) storagePath String? @default("/var/uploads/edms") @db.VarChar(500)
createdBy Int storageBackupEnabled Boolean? @default(true)
status String @default("active") @db.VarChar(50) storageCompressionEnabled Boolean? @default(false)
drawer Drawer @relation(fields: [drawerId], references: [id], onDelete: Cascade)
user user @relation("FolderCreator", fields: [createdBy], references: [userID]) // System Settings
subfolders Subfolder[] systemTimezone String? @default("Asia/Kuala_Lumpur") @db.VarChar(100)
documents Document[] @relation("FolderDocuments") systemBackupSchedule String? @default("daily") @db.VarChar(100)
permissions AccessPermission[] systemLogLevel String? @default("info") @db.VarChar(100)
systemMaintenanceMode Boolean? @default(false)
@@index([drawerId]) systemAutoUpdates Boolean? @default(false)
@@index([createdBy]) systemMonitoring Boolean? @default(true)
} systemPerformanceMetrics Boolean? @default(true)
model Subfolder { settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
id Int @id @default(autoincrement()) settingModifiedDate DateTime? @default(now()) @db.DateTime(0)
name String @db.VarChar(255)
description String? @db.Text
folderId Int
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
createdBy Int
status String @default("active") @db.VarChar(50)
folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
user user @relation("SubfolderCreator", fields: [createdBy], references: [userID])
documents Document[] @relation("SubfolderDocuments")
permissions AccessPermission[]
@@index([folderId])
@@index([createdBy])
}
model Document {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
description String? @db.Text
fileSize Int @default(0)
fileType String @db.VarChar(100)
fileExtension String @db.VarChar(20)
filePath String @db.VarChar(500)
version Int @default(1)
isTemplate Boolean @default(false)
isPublic Boolean @default(false)
folderId Int?
subfolderId Int?
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
createdBy Int
status String @default("active") @db.VarChar(50)
folder Folder? @relation("FolderDocuments", fields: [folderId], references: [id], onDelete: SetNull)
subfolder Subfolder? @relation("SubfolderDocuments", fields: [subfolderId], references: [id], onDelete: SetNull)
user user @relation("DocumentCreator", fields: [createdBy], references: [userID])
accessRequests AccessRequest[]
permissions AccessPermission[]
versions DocumentVersion[]
@@index([folderId])
@@index([subfolderId])
@@index([createdBy])
}
model DocumentVersion {
id Int @id @default(autoincrement())
documentId Int
version Int
filePath String @db.VarChar(500)
fileSize Int @default(0)
createdAt DateTime @default(now()) @db.DateTime(0)
createdBy Int
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
@@index([documentId])
}
model AccessRequest {
id Int @id @default(autoincrement())
documentId Int
userId Int
requestedLevel String @db.VarChar(50) // view, download, print, edit, full
justification String? @db.Text
status String @default("pending") @db.VarChar(50) // pending, approved, rejected
responseNote String? @db.Text
requestedAt DateTime @default(now()) @db.DateTime(0)
respondedAt DateTime? @db.DateTime(0)
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
user user @relation(fields: [userId], references: [userID])
@@index([documentId])
@@index([userId])
}
model AccessPermission {
id Int @id @default(autoincrement())
userId Int?
roleId Int?
documentId Int?
cabinetId Int?
drawerId Int?
folderId Int?
subfolderId Int?
permissionLevel String @db.VarChar(50) // view, download, print, edit, full
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
expiresAt DateTime? @db.DateTime(0)
user user? @relation(fields: [userId], references: [userID], onDelete: SetNull)
role role? @relation(fields: [roleId], references: [roleID], onDelete: SetNull)
document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
cabinet Cabinet? @relation(fields: [cabinetId], references: [id], onDelete: Cascade)
drawer Drawer? @relation(fields: [drawerId], references: [id], onDelete: Cascade)
folder Folder? @relation(fields: [folderId], references: [id], onDelete: Cascade)
subfolder Subfolder? @relation(fields: [subfolderId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([roleId])
@@index([documentId])
@@index([cabinetId])
@@index([drawerId])
@@index([folderId])
@@index([subfolderId])
} }

350
server/api/dms/settings.js Normal file
View File

@ -0,0 +1,350 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default defineEventHandler(async (event) => {
const method = getMethod(event);
try {
if (method === "GET") {
// Get DMS settings
let settings = await prisma.dms_settings.findFirst({
orderBy: { settingID: "desc" },
});
// If no settings exist, create default ones
if (!settings) {
settings = await prisma.dms_settings.create({
data: {
settingCreatedDate: new Date(),
settingModifiedDate: new Date(),
},
});
}
// Transform database fields to frontend structure
const transformedSettings = {
// User & Access Management
access: {
userRoles: settings.userRoles ? settings.userRoles.split(',') : ['Admin', 'Editor', 'Viewer', 'Uploader'],
rbacEnabled: settings.rbacEnabled ?? true,
userGroups: settings.userGroups ? settings.userGroups.split(',') : ['HR Department', 'Finance', 'IT', 'Legal'],
permissions: {
view: settings.permissionView ?? true,
edit: settings.permissionEdit ?? true,
delete: settings.permissionDelete ?? false,
download: settings.permissionDownload ?? true,
share: settings.permissionShare ?? true
},
authentication: {
ssoEnabled: settings.ssoEnabled ?? false,
mfaRequired: settings.mfaRequired ?? false,
ldapIntegration: settings.ldapIntegration ?? false,
sessionTimeout: settings.sessionTimeout ?? 8
}
},
// Document & Folder Settings
documents: {
folderHierarchy: {
maxDepth: settings.folderMaxDepth ?? 5,
defaultStructure: settings.folderDefaultStructure ? settings.folderDefaultStructure.split(',') : ['Department', 'Project', 'Category', 'Year'],
folderTemplates: settings.folderTemplates ? settings.folderTemplates.split(',') : ['Standard', 'Project-based', 'Department-based']
},
namingConventions: {
autoGenerate: settings.namingAutoGenerate ?? true,
mandatoryFields: settings.namingMandatoryFields ? settings.namingMandatoryFields.split(',') : ['title', 'department', 'date'],
pattern: settings.namingPattern ?? '{department}_{title}_{date}'
},
retention: {
enabled: settings.retentionEnabled ?? true,
defaultDays: settings.retentionDefaultDays ?? 2555,
archiveBeforeDelete: settings.retentionArchiveBeforeDelete ?? true
},
versionControl: {
enabled: settings.versionControlEnabled ?? true,
maxVersions: settings.versionControlMaxVersions ?? 10,
autoVersioning: settings.versionControlAutoVersioning ?? true
}
},
// Metadata & Tagging
metadata: {
customFields: settings.metadataCustomFields ? JSON.parse(settings.metadataCustomFields) : [
{ name: 'Department', type: 'dropdown', required: true },
{ name: 'Priority', type: 'select', required: false },
{ name: 'Project Code', type: 'text', required: true },
{ name: 'Review Date', type: 'date', required: false }
],
tagging: {
predefinedTags: settings.taggingPredefinedTags ? settings.taggingPredefinedTags.split(',') : ['urgent', 'confidential', 'public', 'draft', 'final'],
userGeneratedTags: settings.taggingUserGeneratedTags ?? true,
tagSuggestions: settings.taggingTagSuggestions ?? true
},
classification: {
autoClassification: settings.classificationAutoEnabled ?? true,
rules: settings.classificationRules ? settings.classificationRules.split(',') : ['confidential-keywords', 'department-based', 'file-type']
}
},
// Workflow & Automation
workflow: {
approvalFlows: {
enabled: settings.workflowApprovalEnabled ?? true,
defaultFlow: settings.workflowDefaultFlow ?? 'department-head-approval',
customFlows: settings.workflowCustomFlows ? settings.workflowCustomFlows.split(',') : ['legal-review', 'finance-approval', 'director-sign-off']
},
notifications: {
emailNotifications: settings.notificationEmail ?? true,
inAppNotifications: settings.notificationInApp ?? true,
uploadAlerts: settings.notificationUploadAlerts ?? true,
deadlineReminders: settings.notificationDeadlineReminders ?? true
},
automation: {
triggers: settings.automationTriggers ? settings.automationTriggers.split(',') : ['document-uploaded', 'approval-completed', 'deadline-reached'],
actions: settings.automationActions ? settings.automationActions.split(',') : ['move-to-folder', 'send-notification', 'create-task']
}
},
// Upload & Storage Settings
upload: {
fileTypes: {
allowed: settings.uploadAllowedFileTypes ? settings.uploadAllowedFileTypes.split(',') : ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'jpg', 'png'],
blocked: settings.uploadBlockedFileTypes ? settings.uploadBlockedFileTypes.split(',') : ['exe', 'bat', 'cmd']
},
fileSizeLimit: settings.uploadFileSizeLimit ?? 100,
quotas: {
perUser: settings.uploadQuotaPerUser ?? 5000,
perGroup: settings.uploadQuotaPerGroup ?? 50000,
perProject: settings.uploadQuotaPerProject ?? 100000
},
storage: {
type: settings.storageType ?? 'local',
path: settings.storagePath ?? '/var/uploads/edms',
backupEnabled: settings.storageBackupEnabled ?? true,
compressionEnabled: settings.storageCompressionEnabled ?? false
}
},
// System Settings
system: {
timezone: settings.systemTimezone ?? 'Asia/Kuala_Lumpur',
backupSchedule: settings.systemBackupSchedule ?? 'daily',
logLevel: settings.systemLogLevel ?? 'info',
maintenanceMode: settings.systemMaintenanceMode ?? false,
autoUpdates: settings.systemAutoUpdates ?? false,
systemMonitoring: settings.systemMonitoring ?? true,
performanceMetrics: settings.systemPerformanceMetrics ?? true
}
};
return {
statusCode: 200,
message: "Success",
data: transformedSettings,
};
}
if (method === "POST") {
let body;
try {
body = await readBody(event);
} catch (bodyError) {
console.error("Error reading request body:", bodyError);
return {
statusCode: 400,
message: "Invalid request body",
error: bodyError.message,
};
}
// Validate required fields
if (!body || typeof body !== 'object') {
return {
statusCode: 400,
message: "Request body must be a valid JSON object",
};
}
// Check if settings exist
const existingSettings = await prisma.dms_settings.findFirst();
// Transform frontend structure to database fields
const dbData = {
settingModifiedDate: new Date()
};
// User & Access Management
if (body.access) {
if (body.access.userRoles) dbData.userRoles = body.access.userRoles.join(',');
if (body.access.rbacEnabled !== undefined) dbData.rbacEnabled = body.access.rbacEnabled;
if (body.access.userGroups) dbData.userGroups = body.access.userGroups.join(',');
if (body.access.permissions) {
if (body.access.permissions.view !== undefined) dbData.permissionView = body.access.permissions.view;
if (body.access.permissions.edit !== undefined) dbData.permissionEdit = body.access.permissions.edit;
if (body.access.permissions.delete !== undefined) dbData.permissionDelete = body.access.permissions.delete;
if (body.access.permissions.download !== undefined) dbData.permissionDownload = body.access.permissions.download;
if (body.access.permissions.share !== undefined) dbData.permissionShare = body.access.permissions.share;
}
if (body.access.authentication) {
if (body.access.authentication.ssoEnabled !== undefined) dbData.ssoEnabled = body.access.authentication.ssoEnabled;
if (body.access.authentication.mfaRequired !== undefined) dbData.mfaRequired = body.access.authentication.mfaRequired;
if (body.access.authentication.ldapIntegration !== undefined) dbData.ldapIntegration = body.access.authentication.ldapIntegration;
if (body.access.authentication.sessionTimeout !== undefined) dbData.sessionTimeout = body.access.authentication.sessionTimeout;
}
}
// Document & Folder Settings
if (body.documents) {
if (body.documents.folderHierarchy) {
if (body.documents.folderHierarchy.maxDepth !== undefined) dbData.folderMaxDepth = body.documents.folderHierarchy.maxDepth;
if (body.documents.folderHierarchy.defaultStructure) dbData.folderDefaultStructure = body.documents.folderHierarchy.defaultStructure.join(',');
if (body.documents.folderHierarchy.folderTemplates) dbData.folderTemplates = body.documents.folderHierarchy.folderTemplates.join(',');
}
if (body.documents.namingConventions) {
if (body.documents.namingConventions.autoGenerate !== undefined) dbData.namingAutoGenerate = body.documents.namingConventions.autoGenerate;
if (body.documents.namingConventions.mandatoryFields) dbData.namingMandatoryFields = body.documents.namingConventions.mandatoryFields.join(',');
if (body.documents.namingConventions.pattern !== undefined) dbData.namingPattern = body.documents.namingConventions.pattern;
}
if (body.documents.retention) {
if (body.documents.retention.enabled !== undefined) dbData.retentionEnabled = body.documents.retention.enabled;
if (body.documents.retention.defaultDays !== undefined) dbData.retentionDefaultDays = body.documents.retention.defaultDays;
if (body.documents.retention.archiveBeforeDelete !== undefined) dbData.retentionArchiveBeforeDelete = body.documents.retention.archiveBeforeDelete;
}
if (body.documents.versionControl) {
if (body.documents.versionControl.enabled !== undefined) dbData.versionControlEnabled = body.documents.versionControl.enabled;
if (body.documents.versionControl.maxVersions !== undefined) dbData.versionControlMaxVersions = body.documents.versionControl.maxVersions;
if (body.documents.versionControl.autoVersioning !== undefined) dbData.versionControlAutoVersioning = body.documents.versionControl.autoVersioning;
}
}
// Metadata & Tagging
if (body.metadata) {
if (body.metadata.customFields) dbData.metadataCustomFields = JSON.stringify(body.metadata.customFields);
if (body.metadata.tagging) {
if (body.metadata.tagging.predefinedTags) dbData.taggingPredefinedTags = body.metadata.tagging.predefinedTags.join(',');
if (body.metadata.tagging.userGeneratedTags !== undefined) dbData.taggingUserGeneratedTags = body.metadata.tagging.userGeneratedTags;
if (body.metadata.tagging.tagSuggestions !== undefined) dbData.taggingTagSuggestions = body.metadata.tagging.tagSuggestions;
}
if (body.metadata.classification) {
if (body.metadata.classification.autoClassification !== undefined) dbData.classificationAutoEnabled = body.metadata.classification.autoClassification;
if (body.metadata.classification.rules) dbData.classificationRules = body.metadata.classification.rules.join(',');
}
}
// Workflow & Automation
if (body.workflow) {
if (body.workflow.approvalFlows) {
if (body.workflow.approvalFlows.enabled !== undefined) dbData.workflowApprovalEnabled = body.workflow.approvalFlows.enabled;
if (body.workflow.approvalFlows.defaultFlow !== undefined) dbData.workflowDefaultFlow = body.workflow.approvalFlows.defaultFlow;
if (body.workflow.approvalFlows.customFlows) dbData.workflowCustomFlows = body.workflow.approvalFlows.customFlows.join(',');
}
if (body.workflow.notifications) {
if (body.workflow.notifications.emailNotifications !== undefined) dbData.notificationEmail = body.workflow.notifications.emailNotifications;
if (body.workflow.notifications.inAppNotifications !== undefined) dbData.notificationInApp = body.workflow.notifications.inAppNotifications;
if (body.workflow.notifications.uploadAlerts !== undefined) dbData.notificationUploadAlerts = body.workflow.notifications.uploadAlerts;
if (body.workflow.notifications.deadlineReminders !== undefined) dbData.notificationDeadlineReminders = body.workflow.notifications.deadlineReminders;
}
if (body.workflow.automation) {
if (body.workflow.automation.triggers) dbData.automationTriggers = body.workflow.automation.triggers.join(',');
if (body.workflow.automation.actions) dbData.automationActions = body.workflow.automation.actions.join(',');
}
}
// Upload & Storage Settings
if (body.upload) {
if (body.upload.fileTypes) {
if (body.upload.fileTypes.allowed) dbData.uploadAllowedFileTypes = body.upload.fileTypes.allowed.join(',');
if (body.upload.fileTypes.blocked) dbData.uploadBlockedFileTypes = body.upload.fileTypes.blocked.join(',');
}
if (body.upload.fileSizeLimit !== undefined) dbData.uploadFileSizeLimit = body.upload.fileSizeLimit;
if (body.upload.quotas) {
if (body.upload.quotas.perUser !== undefined) dbData.uploadQuotaPerUser = body.upload.quotas.perUser;
if (body.upload.quotas.perGroup !== undefined) dbData.uploadQuotaPerGroup = body.upload.quotas.perGroup;
if (body.upload.quotas.perProject !== undefined) dbData.uploadQuotaPerProject = body.upload.quotas.perProject;
}
if (body.upload.storage) {
if (body.upload.storage.type !== undefined) dbData.storageType = body.upload.storage.type;
if (body.upload.storage.path !== undefined) dbData.storagePath = body.upload.storage.path;
if (body.upload.storage.backupEnabled !== undefined) dbData.storageBackupEnabled = body.upload.storage.backupEnabled;
if (body.upload.storage.compressionEnabled !== undefined) dbData.storageCompressionEnabled = body.upload.storage.compressionEnabled;
}
}
// System Settings
if (body.system) {
if (body.system.timezone !== undefined) dbData.systemTimezone = body.system.timezone;
if (body.system.backupSchedule !== undefined) dbData.systemBackupSchedule = body.system.backupSchedule;
if (body.system.logLevel !== undefined) dbData.systemLogLevel = body.system.logLevel;
if (body.system.maintenanceMode !== undefined) dbData.systemMaintenanceMode = body.system.maintenanceMode;
if (body.system.autoUpdates !== undefined) dbData.systemAutoUpdates = body.system.autoUpdates;
if (body.system.systemMonitoring !== undefined) dbData.systemMonitoring = body.system.systemMonitoring;
if (body.system.performanceMetrics !== undefined) dbData.systemPerformanceMetrics = body.system.performanceMetrics;
}
let settings;
if (existingSettings) {
// Update existing settings
settings = await prisma.dms_settings.update({
where: { settingID: existingSettings.settingID },
data: dbData,
});
} else {
// Create new settings
settings = await prisma.dms_settings.create({
data: {
...dbData,
settingCreatedDate: new Date(),
},
});
}
return {
statusCode: 200,
message: "DMS settings updated successfully",
data: { settingID: settings.settingID },
};
}
return {
statusCode: 405,
message: "Method not allowed",
};
} catch (error) {
console.error("DMS settings API error:", error);
// Provide more specific error messages
if (error.code === 'P2002') {
return {
statusCode: 400,
message: "Duplicate entry error",
error: error.message,
};
}
if (error.code === 'P2025') {
return {
statusCode: 404,
message: "Record not found",
error: error.message,
};
}
if (error.code && error.code.startsWith('P')) {
return {
statusCode: 400,
message: "Database error",
error: error.message,
code: error.code,
};
}
return {
statusCode: 500,
message: "Internal server error",
error: error.message,
};
} finally {
await prisma.$disconnect();
}
});