Compare commits
10 Commits
amalia/15-
...
amalia/02-
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b58492680 | |||
| f990e2c82e | |||
| bf9ef48a70 | |||
| 97ae638472 | |||
| 183d40580d | |||
| 60702256a3 | |||
| 9e11208a13 | |||
| 0077ebda05 | |||
| e5b95a828d | |||
| 1f6791e9bd |
204
QWEN.md
Normal file
204
QWEN.md
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
# Sistem Desa Mandiri - Project Documentation
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Sistem Desa Mandiri is a comprehensive web application built with Next.js to assist with village-level administration and information management. The application provides various features to support village activities, including announcements, discussions, project management, and population administration.
|
||||||
|
|
||||||
|
### Key Features
|
||||||
|
- **User Management**: Manage member data and access rights
|
||||||
|
- **Announcements**: Distribute important information to all village residents
|
||||||
|
- **Discussions**: Forum for discussions among villagers or village officials
|
||||||
|
- **Project & Task Management**: Track progress of ongoing village projects and tasks
|
||||||
|
- **Documentation**: Centralized location for storing and managing important documents
|
||||||
|
- **Push Notifications**: Send real-time notifications to user devices
|
||||||
|
|
||||||
|
### Technology Stack
|
||||||
|
- **Framework**: Next.js 14
|
||||||
|
- **UI Framework**: Mantine
|
||||||
|
- **Database ORM**: Prisma
|
||||||
|
- **Styling**: Tailwind CSS, CSS Modules
|
||||||
|
- **State Management**: Hookstate
|
||||||
|
- **Push Notifications**: Web Push
|
||||||
|
- **Authentication**: Custom cookie-based authentication system
|
||||||
|
- **Icons**: Tabler Icons React
|
||||||
|
- **Rich Text Editor**: TipTap
|
||||||
|
- **Charts**: Recharts, ECharts
|
||||||
|
- **Date Handling**: Day.js, Moment.js
|
||||||
|
- **File Upload**: Multer
|
||||||
|
- **Server Framework**: Elysia.js
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
sistem-desa-mandiri/
|
||||||
|
├── src/
|
||||||
|
│ ├── app/ # Next.js app router pages
|
||||||
|
│ │ ├── (application)/ # Main application routes
|
||||||
|
│ │ ├── (auth)/ # Authentication routes
|
||||||
|
│ │ ├── api/ # API routes
|
||||||
|
│ │ └── ... # Other route groups
|
||||||
|
│ ├── module/ # Feature modules organized by domain
|
||||||
|
│ │ ├── _global/ # Global components and utilities
|
||||||
|
│ │ ├── announcement/ # Announcement feature
|
||||||
|
│ │ ├── auth/ # Authentication feature
|
||||||
|
│ │ ├── discussion/ # Discussion forum
|
||||||
|
│ │ ├── document/ # Document management
|
||||||
|
│ │ ├── project/ # Project management
|
||||||
|
│ │ ├── user/ # User management
|
||||||
|
│ │ └── ... # Other feature modules
|
||||||
|
│ ├── lib/ # Utility functions and libraries
|
||||||
|
│ ├── types/ # TypeScript type definitions
|
||||||
|
├── public/ # Static assets
|
||||||
|
├── .env.test # Environment variables template
|
||||||
|
├── next.config.mjs # Next.js configuration
|
||||||
|
├── package.json # Dependencies and scripts
|
||||||
|
├── README.md # Project documentation
|
||||||
|
├── tailwind.config.ts # Tailwind CSS configuration
|
||||||
|
└── tsconfig.json # TypeScript configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
### Module Organization
|
||||||
|
The application follows a modular architecture where each feature is contained in its own module directory under `/src/module/`. Each module typically contains:
|
||||||
|
- `api/` - API functions and server actions
|
||||||
|
- `ui/` - User interface components
|
||||||
|
- `hooks/` - Custom React hooks
|
||||||
|
- `types/` - Type definitions specific to the module
|
||||||
|
- `utils/` - Utility functions
|
||||||
|
|
||||||
|
## Building and Running
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Node.js (version 20.x or higher)
|
||||||
|
- Bun (recommended) or other package managers like npm/yarn/pnpm
|
||||||
|
- Database (PostgreSQL, MySQL, or SQLite)
|
||||||
|
|
||||||
|
### Installation Steps
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/username/sistem-desa-mandiri.git
|
||||||
|
cd sistem-desa-mandiri
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install dependencies:
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Setup environment variables:
|
||||||
|
```bash
|
||||||
|
cp .env.test .env
|
||||||
|
```
|
||||||
|
Edit the `.env` file and fill in the required variables, especially `DATABASE_URL`.
|
||||||
|
|
||||||
|
4. Run Prisma migrations:
|
||||||
|
```bash
|
||||||
|
npx prisma migrate dev
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Seed the database (optional):
|
||||||
|
```bash
|
||||||
|
npx prisma db seed
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Run the development server:
|
||||||
|
```bash
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
The application will run at https://localhost:3000
|
||||||
|
|
||||||
|
### Available Scripts
|
||||||
|
- `dev`: Runs the development server with HTTPS
|
||||||
|
- `build`: Creates a production build of the application
|
||||||
|
- `start`: Runs the production server
|
||||||
|
- `lint`: Runs the linter to check code quality
|
||||||
|
- `prisma:seed`: Runs the database seeding script
|
||||||
|
|
||||||
|
## Development Conventions
|
||||||
|
|
||||||
|
### Coding Standards
|
||||||
|
- Follow Next.js conventions for file-based routing
|
||||||
|
- Use TypeScript for type safety
|
||||||
|
- Maintain consistent component structure within modules
|
||||||
|
- Use Mantine components for UI elements
|
||||||
|
- Follow accessibility best practices
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
- Components: PascalCase (e.g., `UserProfile.tsx`)
|
||||||
|
- Functions: camelCase (e.g., `getUserData`)
|
||||||
|
- Constants: UPPER_SNAKE_CASE (e.g., `MAX_FILE_SIZE`)
|
||||||
|
- Modules: lowercase with hyphens if needed (e.g., `discussion-general`)
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
- Use Hookstate for global state management
|
||||||
|
- Use React hooks for component-local state
|
||||||
|
- Store persistent data in cookies or localStorage as appropriate
|
||||||
|
|
||||||
|
### API Design
|
||||||
|
- Organize API routes by feature in the `/src/app/api/` directory
|
||||||
|
- Use RESTful conventions where possible
|
||||||
|
- Implement proper error handling and validation
|
||||||
|
- Secure endpoints with appropriate authentication checks
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Unit tests should be co-located with the code they test
|
||||||
|
- Integration tests should be in the `/tests/` directory
|
||||||
|
- Follow the testing pyramid: many unit tests, fewer integration tests, minimal end-to-end tests
|
||||||
|
|
||||||
|
## Key Dependencies
|
||||||
|
|
||||||
|
### Core Dependencies
|
||||||
|
- `next`: React framework for production applications
|
||||||
|
- `react`, `react-dom`: UI library
|
||||||
|
- `@mantine/core`: Component library with accessible components
|
||||||
|
- `@prisma/client`: Database toolkit
|
||||||
|
- `web-push`: Web Push protocol implementation
|
||||||
|
- `elysia`: Fast, lightweight web framework
|
||||||
|
- `@hookstate/core`: State management solution
|
||||||
|
|
||||||
|
### UI Dependencies
|
||||||
|
- `@mantine/carousel`: Carousel component
|
||||||
|
- `@mantine/charts`: Chart components
|
||||||
|
- `@mantine/form`: Form management
|
||||||
|
- `@mantine/notifications`: Notification system
|
||||||
|
- `@mantine/tiptap`: Rich text editor components
|
||||||
|
- `@tabler/icons-react`: Icon library
|
||||||
|
- `@tiptap/react`: Rich text editor
|
||||||
|
- `recharts`: Charting library
|
||||||
|
- `echarts-for-react`: Alternative charting library
|
||||||
|
|
||||||
|
### Utilities
|
||||||
|
- `dayjs`: Date manipulation library
|
||||||
|
- `lodash`: Utility functions
|
||||||
|
- `crypto-js`: Cryptographic algorithms
|
||||||
|
- `iron-session`: Session management
|
||||||
|
- `jose`: JavaScript Object Signing and Encryption
|
||||||
|
- `multer`: File upload middleware
|
||||||
|
- `firebase-admin`: Firebase admin SDK
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### Modular Design
|
||||||
|
The application follows a modular design where each feature is encapsulated in its own module directory. This promotes separation of concerns and makes the codebase easier to maintain and scale.
|
||||||
|
|
||||||
|
### API Layer
|
||||||
|
API routes are organized by feature in the `/src/app/api/` directory. Each feature has its own subdirectory containing related API endpoints. This makes it easy to locate and maintain API functionality.
|
||||||
|
|
||||||
|
### Component Organization
|
||||||
|
Components are organized within their respective module directories. Common components that are shared across multiple modules are placed in the `_global` module.
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
- Client-side state is managed using React hooks and Hookstate
|
||||||
|
- Server-side data fetching is done through Next.js API routes
|
||||||
|
- Database interactions are handled through Prisma ORM
|
||||||
|
- Authentication is implemented using cookies and server actions
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
The application is designed to be deployed as a Next.js application. It can be deployed to platforms like Vercel, Netlify, or any hosting service that supports Node.js applications.
|
||||||
|
|
||||||
|
For production deployment:
|
||||||
|
1. Run `bun run build` to create an optimized production build
|
||||||
|
2. Run `bun start` to start the production server
|
||||||
|
3. Configure environment variables for the production environment
|
||||||
|
4. Set up SSL certificates for secure connections
|
||||||
|
5. Configure database connection for production environment
|
||||||
@@ -423,6 +423,7 @@ model DivisionDisscussion {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
DivisionDisscussionComment DivisionDisscussionComment[]
|
DivisionDisscussionComment DivisionDisscussionComment[]
|
||||||
|
DivisionDiscussionFile DivisionDiscussionFile[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model DivisionDisscussionComment {
|
model DivisionDisscussionComment {
|
||||||
@@ -437,6 +438,17 @@ model DivisionDisscussionComment {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
model DivisionDiscussionFile {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
DivisionDisscussion DivisionDisscussion @relation(fields: [idDiscussion], references: [id])
|
||||||
|
idDiscussion String
|
||||||
|
name String
|
||||||
|
extension String
|
||||||
|
idStorage String?
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
model DivisionDocumentFolderFile {
|
model DivisionDocumentFolderFile {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { countTime, prisma } from "@/module/_global";
|
import { countTime, DIR, funUploadFile, prisma } from "@/module/_global";
|
||||||
import { funGetUserById } from "@/module/auth";
|
import { funGetUserById } from "@/module/auth";
|
||||||
import { createLogUserMobile } from "@/module/user";
|
import { createLogUserMobile } from "@/module/user";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
@@ -70,6 +70,21 @@ export async function GET(request: Request, context: { params: { id: string } })
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
return NextResponse.json({ success: true, message: "Berhasil mendapatkan komentar", data: omitMember }, { status: 200 });
|
return NextResponse.json({ success: true, message: "Berhasil mendapatkan komentar", data: omitMember }, { status: 200 });
|
||||||
|
} else if (cat == "file") {
|
||||||
|
const data = await prisma.divisionDiscussionFile.findMany({
|
||||||
|
where: {
|
||||||
|
idDiscussion: id,
|
||||||
|
isActive: true
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
idStorage: true,
|
||||||
|
name: true,
|
||||||
|
extension: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, message: "Berhasil mendapatkan file", data: data }, { status: 200 });
|
||||||
} else {
|
} else {
|
||||||
const data = await prisma.divisionDisscussion.findUnique({
|
const data = await prisma.divisionDisscussion.findUnique({
|
||||||
where: {
|
where: {
|
||||||
@@ -212,7 +227,13 @@ export async function PUT(request: Request, context: { params: { id: string } })
|
|||||||
export async function POST(request: Request, context: { params: { id: string } }) {
|
export async function POST(request: Request, context: { params: { id: string } }) {
|
||||||
try {
|
try {
|
||||||
const { id } = context.params
|
const { id } = context.params
|
||||||
const { title, desc, user } = (await request.json())
|
const body = await request.formData()
|
||||||
|
const dataBody = body.get("data")
|
||||||
|
const cekFile = body.has("file0")
|
||||||
|
|
||||||
|
// const { title, desc, user } = (await request.json())
|
||||||
|
const { title, desc, user, oldFile } = JSON.parse(dataBody as string)
|
||||||
|
|
||||||
|
|
||||||
const userMobile = await funGetUserById({ id: String(user) })
|
const userMobile = await funGetUserById({ id: String(user) })
|
||||||
|
|
||||||
@@ -239,6 +260,41 @@ export async function POST(request: Request, context: { params: { id: string } }
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (oldFile.length > 0) {
|
||||||
|
for (let index = 0; index < oldFile.length; index++) {
|
||||||
|
const element = oldFile[index];
|
||||||
|
if (element.delete) {
|
||||||
|
await prisma.divisionDiscussionFile.delete({
|
||||||
|
where: {
|
||||||
|
id: element.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cekFile) {
|
||||||
|
body.delete("data")
|
||||||
|
for (var pair of body.entries()) {
|
||||||
|
if (String(pair[0]).substring(0, 4) == "file") {
|
||||||
|
const file = body.get(pair[0]) as File
|
||||||
|
const fExt = file.name.split(".").pop()
|
||||||
|
const fName = decodeURIComponent(file.name.replace("." + fExt, ""))
|
||||||
|
const upload = await funUploadFile({ file: file, dirId: DIR.discussionDivision })
|
||||||
|
if (upload.success) {
|
||||||
|
await prisma.divisionDiscussionFile.create({
|
||||||
|
data: {
|
||||||
|
idStorage: upload.data.id,
|
||||||
|
idDiscussion: id,
|
||||||
|
name: fName,
|
||||||
|
extension: String(fExt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// create log user
|
// create log user
|
||||||
const log = await createLogUserMobile({ act: 'UPDATE', desc: 'User mengupdate data diskusi', table: 'divisionDisscussion', data: id, user: userMobile.id })
|
const log = await createLogUserMobile({ act: 'UPDATE', desc: 'User mengupdate data diskusi', table: 'divisionDisscussion', data: id, user: userMobile.id })
|
||||||
return NextResponse.json({ success: true, message: "Berhasil mengedit diskusi" }, { status: 200 });
|
return NextResponse.json({ success: true, message: "Berhasil mengedit diskusi" }, { status: 200 });
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { funSendWebPush, prisma } from "@/module/_global";
|
import { DIR, funSendWebPush, funUploadFile, prisma } from "@/module/_global";
|
||||||
import { funGetUserById } from "@/module/auth";
|
import { funGetUserById } from "@/module/auth";
|
||||||
import { createLogUserMobile } from "@/module/user";
|
import { createLogUserMobile } from "@/module/user";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
@@ -102,7 +102,14 @@ export async function GET(request: Request) {
|
|||||||
// CREATE DISCUSSION
|
// CREATE DISCUSSION
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
try {
|
try {
|
||||||
const { idDivision, desc, user } = (await request.json());
|
const body = await request.formData()
|
||||||
|
const dataBody = body.get("data")
|
||||||
|
const cekFile = body.has("file0")
|
||||||
|
|
||||||
|
// const { idDivision, desc, user } = (await request.json());
|
||||||
|
const { idDivision, desc, user } = JSON.parse(String(dataBody));
|
||||||
|
|
||||||
|
|
||||||
const userMobile = await funGetUserById({ id: String(user) })
|
const userMobile = await funGetUserById({ id: String(user) })
|
||||||
|
|
||||||
if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") {
|
if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") {
|
||||||
@@ -135,6 +142,29 @@ export async function POST(request: Request) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (cekFile) {
|
||||||
|
body.delete("data")
|
||||||
|
for (var pair of body.entries()) {
|
||||||
|
if (String(pair[0]).substring(0, 4) == "file") {
|
||||||
|
const file = body.get(pair[0]) as File
|
||||||
|
const fExt = file.name.split(".").pop()
|
||||||
|
const fName = decodeURIComponent(file.name.replace("." + fExt, ""))
|
||||||
|
const upload = await funUploadFile({ file: file, dirId: DIR.discussionDivision })
|
||||||
|
if (upload.success) {
|
||||||
|
await prisma.divisionDiscussionFile.create({
|
||||||
|
data: {
|
||||||
|
idStorage: upload.data.id,
|
||||||
|
idDiscussion: data.id,
|
||||||
|
name: fName,
|
||||||
|
extension: String(fExt)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const memberDivision = await prisma.divisionMember.findMany({
|
const memberDivision = await prisma.divisionMember.findMany({
|
||||||
where: {
|
where: {
|
||||||
idDivision: idDivision
|
idDivision: idDivision
|
||||||
|
|||||||
@@ -33,13 +33,21 @@ export async function GET(request: Request, context: { params: { id: string } })
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (kategori == "jumlah") {
|
if (kategori == "jumlah") {
|
||||||
|
const tahunFilter = new Date().getFullYear().toString();
|
||||||
|
const startTahun = new Date(`${tahunFilter}-01-01T00:00:00.000Z`);
|
||||||
|
const endTahun = new Date(`${parseInt(tahunFilter) + 1}-01-01T00:00:00.000Z`);
|
||||||
|
|
||||||
const tugas = await prisma.divisionProject.count({
|
const tugas = await prisma.divisionProject.count({
|
||||||
where: {
|
where: {
|
||||||
idDivision: String(id),
|
idDivision: String(id),
|
||||||
status: {
|
status: {
|
||||||
lte: 1
|
lte: 1
|
||||||
},
|
},
|
||||||
isActive: true
|
isActive: true,
|
||||||
|
createdAt: {
|
||||||
|
gte: startTahun,
|
||||||
|
lt: endTahun
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export async function GET(request: Request) {
|
|||||||
const name = searchParams.get('search');
|
const name = searchParams.get('search');
|
||||||
const status = searchParams.get('status');
|
const status = searchParams.get('status');
|
||||||
const idGroup = searchParams.get("group");
|
const idGroup = searchParams.get("group");
|
||||||
|
const tahun = searchParams.get("year");
|
||||||
const page = searchParams.get('page');
|
const page = searchParams.get('page');
|
||||||
const kategori = searchParams.get('cat');
|
const kategori = searchParams.get('cat');
|
||||||
const user = searchParams.get('user');
|
const user = searchParams.get('user');
|
||||||
@@ -25,7 +26,7 @@ export async function GET(request: Request) {
|
|||||||
return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 });
|
return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 });
|
||||||
}
|
}
|
||||||
|
|
||||||
let grup
|
let grup, tahunFilter = String(tahun)
|
||||||
const dataSkip = Number(page) * 10 - 10;
|
const dataSkip = Number(page) * 10 - 10;
|
||||||
const roleUser = userMobile.idUserRole
|
const roleUser = userMobile.idUserRole
|
||||||
const villageId = userMobile.idVillage
|
const villageId = userMobile.idVillage
|
||||||
@@ -37,6 +38,14 @@ export async function GET(request: Request) {
|
|||||||
grup = idGroup
|
grup = idGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tahun == "null" || tahun == undefined || tahun == "" || tahun == "undefined") {
|
||||||
|
tahunFilter = new Date().getFullYear().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTahun = new Date(`${tahunFilter}-01-01T00:00:00.000Z`);
|
||||||
|
const endTahun = new Date(`${parseInt(tahunFilter) + 1}-01-01T00:00:00.000Z`);
|
||||||
|
|
||||||
|
|
||||||
const cek = await prisma.group.count({
|
const cek = await prisma.group.count({
|
||||||
where: {
|
where: {
|
||||||
id: grup,
|
id: grup,
|
||||||
@@ -58,7 +67,11 @@ export async function GET(request: Request) {
|
|||||||
contains: (name == undefined || name == "null") ? "" : name,
|
contains: (name == undefined || name == "null") ? "" : name,
|
||||||
mode: "insensitive"
|
mode: "insensitive"
|
||||||
},
|
},
|
||||||
status: (status == "0" || status == "1" || status == "2" || status == "3") ? Number(status) : 0
|
status: (status == "0" || status == "1" || status == "2" || status == "3") ? Number(status) : 0,
|
||||||
|
createdAt: {
|
||||||
|
gte: startTahun,
|
||||||
|
lt: endTahun
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -78,6 +91,10 @@ export async function GET(request: Request) {
|
|||||||
some: {
|
some: {
|
||||||
idUser: String(userId)
|
idUser: String(userId)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
gte: startTahun,
|
||||||
|
lt: endTahun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,7 +156,7 @@ export async function GET(request: Request) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
return NextResponse.json({ success: true, message: "Berhasil mendapatkan kegiatan", data: omitData, filter, total: totalData }, { status: 200 });
|
return NextResponse.json({ success: true, message: "Berhasil mendapatkan kegiatan", data: omitData, filter, tahun: tahunFilter, total: totalData }, { status: 200 });
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
46
src/app/api/mobile/project/tahun/route.ts
Normal file
46
src/app/api/mobile/project/tahun/route.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { prisma } from "@/module/_global";
|
||||||
|
import { funGetUserById } from "@/module/auth";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const user = searchParams.get('user');
|
||||||
|
const userMobile = await funGetUserById({ id: String(user) })
|
||||||
|
|
||||||
|
if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") {
|
||||||
|
return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const villageId = userMobile.idVillage
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
|
const data = await prisma.project.findMany({
|
||||||
|
where: {
|
||||||
|
isActive: true,
|
||||||
|
idVillage: villageId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
createdAt: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const dataYear = data.map((item: any) => item.createdAt.getFullYear())
|
||||||
|
// Hapus duplikat pakai Set
|
||||||
|
const uniqueYears = [...new Set(dataYear)];
|
||||||
|
|
||||||
|
// Tambahkan tahun sekarang kalau belum ada
|
||||||
|
if (!uniqueYears.includes(currentYear)) {
|
||||||
|
uniqueYears.push(currentYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (opsional) urutkan dari terbaru ke lama
|
||||||
|
uniqueYears.sort((a, b) => b - a);
|
||||||
|
|
||||||
|
const formattedData = uniqueYears.map(year => ({
|
||||||
|
id: String(year),
|
||||||
|
name: String(year)
|
||||||
|
}));
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, message: "Success", data: formattedData }, { status: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
@@ -16,6 +16,11 @@ export async function GET(request: Request) {
|
|||||||
const page = searchParams.get('page');
|
const page = searchParams.get('page');
|
||||||
const user = searchParams.get('user');
|
const user = searchParams.get('user');
|
||||||
const dataSkip = Number(page) * 10 - 10;
|
const dataSkip = Number(page) * 10 - 10;
|
||||||
|
const tahun = searchParams.get("year");
|
||||||
|
|
||||||
|
const tahunFilter = tahun ? tahun : new Date().getFullYear().toString();
|
||||||
|
const startTahun = new Date(`${tahunFilter}-01-01T00:00:00.000Z`);
|
||||||
|
const endTahun = new Date(`${parseInt(tahunFilter) + 1}-01-01T00:00:00.000Z`);
|
||||||
|
|
||||||
const userMobile = await funGetUserById({ id: String(user) })
|
const userMobile = await funGetUserById({ id: String(user) })
|
||||||
if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") {
|
if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") {
|
||||||
@@ -43,6 +48,10 @@ export async function GET(request: Request) {
|
|||||||
title: {
|
title: {
|
||||||
contains: (name == undefined || name == "null") ? "" : name,
|
contains: (name == undefined || name == "null") ? "" : name,
|
||||||
mode: "insensitive"
|
mode: "insensitive"
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
gte: startTahun,
|
||||||
|
lt: endTahun
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
@@ -87,6 +96,10 @@ export async function GET(request: Request) {
|
|||||||
title: {
|
title: {
|
||||||
contains: (name == undefined || name == "null") ? "" : name,
|
contains: (name == undefined || name == "null") ? "" : name,
|
||||||
mode: "insensitive"
|
mode: "insensitive"
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
gte: startTahun,
|
||||||
|
lt: endTahun
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
41
src/app/api/mobile/task/tahun/route.ts
Normal file
41
src/app/api/mobile/task/tahun/route.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { prisma } from "@/module/_global";
|
||||||
|
import { funGetUserById } from "@/module/auth";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const user = searchParams.get('user');
|
||||||
|
const divisi = searchParams.get('division');
|
||||||
|
const userMobile = await funGetUserById({ id: String(user) })
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
|
if (userMobile.id == "null" || userMobile.id == undefined || userMobile.id == "") {
|
||||||
|
return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await prisma.divisionProject.findMany({
|
||||||
|
where: {
|
||||||
|
isActive: true,
|
||||||
|
idDivision: String(divisi),
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
createdAt: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const dataYear = data.map((item: any) => item.createdAt.getFullYear())
|
||||||
|
// Hapus duplikat pakai Set
|
||||||
|
const uniqueYears = [...new Set(dataYear)];
|
||||||
|
|
||||||
|
// Tambahkan tahun sekarang kalau belum ada
|
||||||
|
if (!uniqueYears.includes(currentYear)) {
|
||||||
|
uniqueYears.push(currentYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (opsional) urutkan dari terbaru ke lama
|
||||||
|
uniqueYears.sort((a, b) => b - a);
|
||||||
|
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, message: "Success", data: uniqueYears }, { status: 200 });
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,7 +13,8 @@ export const DIR = {
|
|||||||
user: "cm0x8dbwn0005bp5tgmfcthzw",
|
user: "cm0x8dbwn0005bp5tgmfcthzw",
|
||||||
banner: "cm1sxex19004938bjvyaq8vta",
|
banner: "cm1sxex19004938bjvyaq8vta",
|
||||||
announcement: "cmkdfkze4005hkhjgunsroi4t",
|
announcement: "cmkdfkze4005hkhjgunsroi4t",
|
||||||
discussion: "cmkf5h7ic006jkhjgyrkog7ut"
|
discussion: "cmkf5h7ic006jkhjgyrkog7ut",
|
||||||
|
discussionDivision: "cmkdfktfm005fkhjggjvnqly5"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const keyWibu = 'padahariminggukuturutayahkekotanaikdelmanistimewakududukdimuka'
|
export const keyWibu = 'padahariminggukuturutayahkekotanaikdelmanistimewakududukdimuka'
|
||||||
|
|||||||
@@ -8,15 +8,7 @@
|
|||||||
"gender": "F"
|
"gender": "F"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "devLukman",
|
"id": "devMalik",
|
||||||
"idAdminRole": "dev",
|
|
||||||
"name": "Lukman",
|
|
||||||
"phone": "6287701790942",
|
|
||||||
"email": "lukman@bip.com",
|
|
||||||
"gender": "M"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "devLukman",
|
|
||||||
"idAdminRole": "dev",
|
"idAdminRole": "dev",
|
||||||
"name": "Malik",
|
"name": "Malik",
|
||||||
"phone": "6289697338821",
|
"phone": "6289697338821",
|
||||||
|
|||||||
Reference in New Issue
Block a user